450 likes | 595 Views
HKUST Summer Programming Course 2008. Templates ~ their instantiation and specialization. Overview. Introduction to templates Function templates Class templates Class member template Template separate compilation Applications. Template. Introduction. Template – code reuse, again.
E N D
HKUST SummerProgramming Course 2008 Templates ~ their instantiation and specialization
Overview • Introduction to templates • Function templates • Class templates • Class member template • Template separate compilation • Applications
Template Introduction
Template – code reuse, again • Inheritance is sometimes very helpful to reuse code, BUT it cannot be used to customize “type”, for example: class IntList { public: int& get(int v); // other methods … }; • You can’t inherit this class to have a List of strings, for example. The get() method must return an integer reference.
Idea: making type a parameter • With template, you can make type a parameter. • It would be good if we can write code like this: T max(T v1, T v2) { if (v1 > v2) { return v1; } else { return v2; } } • Notice T is a type parameter that can be substituted with whatever type. • But the type has to be comparable. (i.e. has the operator> defined) • You can write max(4, 5), in this case it will return 5 as an int. • You can also write max(3.3, 2.7), in this case it will return 3.3, which is a double.
Template in details (1) • A template is a piece of code that contains some type variable. • The type variable is called generic type. • It will be replaced, at compilation time, to a particular type. • The process of replacement is called instantiation. • After replacement, the code is free of type variable, and therefore just like any normal code, it can be compiled and executed. • So, what code can be used as template? • A template can be a function template, where a the piece of code is a function, or otherwise • It could be a class template, where is the type variable is used all over the class. • Sometimes, when only a method want to be template, we can also define class member template.
Template in details (2) • In C++, template also allows different types to be instantiated differently, in case there is difficulty to write a piece of code to fit all possible types. • That is called template specialization. You usually don’t want to specialize template when it is unnecessary.
Function Template Basic Syntax Instantiation Specialization The need and impossibility for partial specialization
Function template (1) • Code example speak a thousand words, let get down to an example. 1: template<class T> 2: T maximum (T v1, T v2) { 3: if (v1 > v2) { 4: return v1; 5: } else { 6: return v2; 7: } 8: }
Function template (2) • Line 1: template<class T> • This line declares the coming block of code (in this case, the function max) is a template, it has a single type parameter named T. • Line 2 ~ 8: just imagine T is replaced by int, then the code can be clearly understood. It is just a simple implementation of the maximum(int, int) function.
Function Template Instantiation (1) • When template code are invoked, they’re instantiated • The type parameter is substituted with an appropriate type • Then the code is appended to the main code and compiled. • What is the appropriate type?
What is the appropriate type? • The basic idea is, every type in the parameter list need to matches with the template parameter. • NO automatic casting is done. • e.g. • maximum(3, 4.5) CANNOT compile. • maximum(3, 2) can compile, where T instantiated as int. • maximum(3.5, 4.2) can compile, where T instantiated as double.
Explicit Instantiation • What if I really want to call: • maximum(3, 4.5)? • You can call it this way: • maximum<double>(3, 4.5), or • maximum((double)3, 4.5) • The use of maximum<double> is called explicit instantiation. The programmer explicitly specify how the template should be instantiated.
More than a single template parameter • More than one template parameter. • With this program, I can call max(3, 4.5) template<class T, class U> void maximum (T a, U b) { if (a > b) { cout << a << endl; } else { cout << a << endl; } }
Specialization (1) • What if some people implemented a class called Money, which ONLY support a function: • bool larger(const Money& another); • In this case, you cannot call like this: • Money m1(3), m2(2) • maximum(m1, m2); • The compilation fails: • no operator> defined for class Money. • If you can’t alter the Money class (because it was written by your boss), then you will need to use specialization.
Specialization (2) – the Money classThat’s what your boss have written. class Money { public: Money(double value) : _value(value) {} double value() const { return _value; } bool larger(const Money& m) const { return _value > m._value; } private: const double _value; }; ostream& operator<<(ostream& os, const Money& m) { os << “$” << m.value(); }
Specialization (3) • You can explicitly write the instantiated version, to avoid the compiler instantiate something that does NOT compile. • Since the Money class does NOT support the comparison operators, then template<> Money max<Money>(Money x, Money y) { if (x.larger(y)) { return x; } else { return y; } } • The first template<> is required by the compiler, although it isn’t very meaningful in function templates.
The concept of partial specialization • Now, we are more greedy, could we write a function that compare Money and integer? • Of course, your boss is still there, you can’t write the global operator function. • Now, let’s try: template<> void maximumPrint<Money, int> (Money a, int b) { if (a.value() > b) { cout << a << endl; } else { cout << b << endl; } } • Notice the use of cout instead of return, it is done because of the return type.
How about other comparisons? • Now the code work when Money compares with integer, it cannot compare with double, it also cannot compare with long. • What is the ideal? Specialize once and make these all work by ONLY specialize the first parameter. Which is something like this: template<class T> void maximumPrint<Money, T>(Money a, T b) { if (a.value() > b) { cout << a << endl; } else { cout << b << endl; } } • This is impossible in C++ - we will solve it later, after we learn more. • error: partial specialization `maximumPrint<Money, T>' of function template
Class Template Basic Syntax Instantiation Specialization Partial specialization
Class Template • Making the whole class a template • Having a type variable usable throughout the whole class. • The idea is very similar with function template. • Let’s look at an example.
Templated Array Class template<class T> class Array { public: Array(int s) { storage = new T[_size = s]; } ~Array() { delete[] storage; } T& get(int i) { return storage[i]; } const T& get(int i) const { return storage[i]; } void process(); private: T* storage; int _size; }; template<class T> void Array<T>::process() { for (int i = 0; i < _size; i++) { storage[i].process(); } }
Syntax Highlight • Similar with function templates, the declaration of a template class start with the template parameter list template<class T> • In a template class, method do NOT necessarily inline. The method process() is written outside the class. • In each template class method implementation, the SAME template parameter list should be used in process()
Class Member Instantiation • Class fields are ALWAYS instantiated when an object is constructed, BUT • member functions are NOT, they are instantiated ONLY when they are called. • Example: class V {public: void process() { cout << “Hello” << endl; }}; • This is valid!! • Array<int> a(3); cout << (a.get(1) = 7) << endl; • Because the Array<int>::process() is NOT called and hence NOT instantiated. • This is also valid, for sure • Array<V> a(3); a.process();
Template Induced Interface • In order to successfully use the Array class, the template argument should conform to some standard. • For example, a process() member function must be defined in order to call process() BUT: • There is NO restriction on the return type. • A template induced interface is more flexible than an abstract base class (ABC)
Class Template Specialization (1) • Similar to function template specialization, class template can also be specialization. • Say, for example, that you cannot change class U (again, that is written by your boss), but the process function of U sadly requires a dummy integer parameter. • We could have specialize the class template to solve this problem
Class Template Specialization (2)The class U – written by your boss again class U { public: void process(int x) { // ... do something, not interesting ... } };
Template member specialization • We can specialize the template member by: void Array<U>::process() { for (int i = 0; i < _size; i++) { storage[i].process(0); } }
Class Template Partial SpecializationThe basic class template • Unlike function template, class template can be partially specialized. • We will try to use this to solve the money comparison problem we encountered before. template<class T, class U> class Maximum { public: Maximum(T a, U b) { if (a > b) { cout << a << endl; } else { cout << b << endl; } } private: };
The partial specializationComparing Money with numerical type template<class U> class Maximum<Money, U> { public: Maximum(Money a, U b) { if (a.value() > b) { cout << a << endl; } else { cout << b << endl; } } private: };
The complete specializationComparing two money objects template<> class Maximum<Money, Money> { public: Maximum(Money a, Money b) { if (a.larger(b)) { cout << a << endl; } else { cout << b << endl; } } };
Testing Code int main() { Money m1(5.5), m2(4.0); Maximum<int, int>(1, 2); Maximum<Money, int>(m1, 7); Maximum<Money, Money>(m1, m2); return 0; }
Class Member Template • There is situation there is only a method you want to template it. Honestly, this is a rare situation. • The template declaration of this type is very similar to a function template, as an example: class Simple { public: template<class T> bool larger(const T& a, const T& b) { … } };
Class Template Partial Specialization • In template specialization, we solve problem that a particular class template argument cannot be changed to satisfy the template induced interface. • In case of multiple template argument, we can specialize a single type parameter and leave the remaining unchanged. • Example: • U& Array::<U, class T> get(const T& t) { … } • This is NOT possible with function templates. • NO function template partial specialization.
Template Template Separate Compilation
Template Separate Compilation • Templates need to be instantiated at compile time, therefore, the template must be available at the same time when the template is instantiated. • Object file do NOT contain the template source, that why it is difficult to separate compile templates. • Simple answer: • Template CANNOT be compiled separately. • Complex answer: • Template CAN be compiled separately by the ANSI specification using the export keyword
Precompiled Headers • If the template is large, then using it incurs large overhead because the same template is compiled again and again. • Some compilers support compiling the header once and store the compiled header. • http://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html
Template Applications
Application of Template • Standard Template Library (STL) • A set of library classes, widely used, will be covered in next set of slides. • auto_ptr • An implementation of smart pointer that can point to any type of object. • Various type of casting • static_cast • dynamic_cast • const_cast • http://www.coding-zone.co.uk/cpp/articles/050101casting.shtml
auto_ptr (1) • A standard smart pointer implementation in C++. • Usage: void createUseAndDelete() { auto_ptr<T> pt(new T); pt->print(); } • The new object is AUTOMATICALLY deleted. That’s why we call it auto_ptr. • Of course, assumed T has a print method.
auto_ptr (2) • What if you do NOT want to delete the object. • Option 1: Do NOT use auto_ptr! • Obvious! Isn’t it? • Option 2: Use the release() method of auto_ptr. • T* pt2 = pt.release(); • Now you will need to delete pt2 yourself, the object will NOT automatically. • Typically used when the object is conditionally cached. • If you want to know more about auto_ptr • http://www.gotw.ca/publications/using_auto_ptr_effectively.htm
Advanced Casting • Casting in C style is NOT safe. • Slicing might occurs • Casting to incorrect type might happens. • It is dangerous to cast away const in C style casting. • const char* k = “x”; • char* p = (char*)k; • *p = ‘z’; // Leads to RUNTIME error • Casting can be template function object! • Allows casting away const! • Allow runtime checking of type.
static_cast • Static checking is performed before casting, if unsuccessful will lead to COMPILE error. • Example: • const char* k = “x”; • char* p = static_cast<char*>(k); // ERROR • class B : public A { … }; class C { .. }; • static_cast<A*>(new B); // compiles • static_cast<B*>(new A); // compiles • static_cast<A*>(new C); // NOT compiles • Notice, 2nd case is NOT correct, but still compile, because there is NO way for the compiler to check that at COMPILE time. Static means compile time checking.
dynamic_cast • Overcome checking problem, delay the type check untils runtime. • Example: • class A {virtual void x();}; • class B : public A {}; class C : public A{}; • A* b = new B(); • A* c = new C(); • dynamic_cast<B*>(b); // Compiles • dynamic_cast<B*>(c); // Compiles • Virtual function? • Dynamic cast only work ONLY on polymorphic class, which has at least one virtual function. • Case 2, still compile? • It will return NULL, because the condition can be known only at runtime, the code can only response at runtime.
const_cast: cast away const safely #include <iostream> using namespace std; class A { public: int getX() const { return x; } void setX(int _x) { this->x = _x; } private: int x; }; int main() { const A* a = new A(); A* b = const_cast<A*>(a); b->setX(20); cout << a->getX() << endl; delete b; return 0; }