290 likes | 408 Views
Genericity. C++ Templates. Parametric Polymorphism. Allows definitions to be parametrized at compile-time Such a definition is actually a “function” that returns a new program element(method, class, etc.) Template parameters = The arguments of this “function” AKA: type parameters
E N D
Genericity • C++ Templates
Parametric Polymorphism • Allows definitions to be parametrized at compile-time • Such a definition is actually a “function” that returns a new program element(method, class, etc.) • Template parameters = The arguments of this “function” • AKA: type parameters • Instantiation of a template == evaluating the “function”
Template Instantiation in C++ • The compiler recompiles the template definition • Substitutes the formal template parameters with the actual parameters • Generates new object code • Almost no compilation when the definition is read • Most compilation work is at each instantiation point • The template definition must be part of the source code • That is: #included from an .h file • Supporting separate compilation of templates is too difficult in practice
C++ Function Templates • A function can have type parameters template<typename T> T* create() { return new T(); } void f() { string* sp = create<string>(); } • we use create() thru explicit instantiation • We specify the actual types inside a pair of < > • In some cases the compiler can deduce the template parameters template<typename T> void print(T& t) { cout << t << endl; } void g() { print(5); } • We use print() thru implicit instantiation • Possible if a type parameter is also a value parameter • Make overload resolution more difficult • Introduced later
C++ Class Templates template<class Type> class Stack { Type buff[50]; int sp; public: Stack(void): sp(0) {} void push(const Type &e) { buff[sp++] = e;} Type pop(void) { return buff[--sp]; } int empty(void) const { return sp == 0;} int full(void) const{ return sp == 50; } }; template <class T> Stack<T>::push(T val){ if (sp >= size) { error("Stack is full"); return; } buff[sp++] = val; }
Kind of Parameters Generics are “routines executed at compile time and produce source code”. Therefore, all parameters must be entities that are known at compile time. • Type: most frequent and most important. All languages which support genericity allow type parameter. • Numerical Constant: e.g., the integer constant 3. • Useful for building generic arrays. • Variables: • Constant: Address of a variable in C++ • Routine: • Constant: Address of function in C++
A Template Taking a Constant template<int N> struct FixedArray { double values[N]; int size() { return N; } // Could be static }; int main(int, char**) { FixedArray<10> arr; arr.values[0] = 3.14; cout << "first element=" << arr.values[0] << endl; cout << "size=" << arr.size() << endl; return 0; }
A Template Taking a Function Pointer typedef void (*ErrorFunction)(const char *); template <class Type, ErrorFunction error> class Array { size_t n; Type *buff; public: Array(size_t n_): n(n_), buff(new Type[n]){ if ((buff == (Type *)0) error("Memory failure"); } Type & operator [] (size_t i) { if (i >= n){ error("Array overflow"); return buff[i]; } } ... };
Using Function Arguments to Templates #include <stdlib.h> #include <iostream.h> void err_abort(const char *msg) { cerr << "Error: " << msg <<". Aborting!\n"; exit(1); } typedef Array<int, err_abort> SafeIntArray; ... SafeIntArray a(20);
String Arguments to Templates? template <class Type, const char *Name> class NamedType: public Type { public: const char *name() { return Name; } }; #define NAMED(T) NamedType<T, #T> typedef NAMED(MyClass) MyNamedClass; Does not work! Consider constchar *const Hello1 = "Hello"; constchar *const Hello2 = "Hello"; Then, Hello1 and Hello2 are not (usually) the same!
A Template Taking a Type template<typename T> struct Pair { void set(const T& x, const T& y) { a = x; b = y; } void print() { cout << a << "," << b << endl; } private: T a, b; }; typedef Pair<char*> StrPair; // Instantiate Pair, // pass char* as an argument typedef Pair<int> IntPair; int main(int, char**) { StrPair sp; IntPair ip; sp.set("ab", "cd"); sp.print(); ip.set(10, 20); ip.print(); return 0; }
Specialization template<typename T> struct Pair { void set(const T& x, const T& y) { a = x; b = y; } void print() { cout << a << "," << b << endl; } private: T a, b; }; template<> struct Pair<bool> { void set(bool x, bool y) { v = (x?1:0) + (y?2:0); } void print() { cout << (v&1) << "," << (v&2) << endl; } private: int v; }; int main(int, char**) { Pair<bool> pb; pb.set(true, false); pb.print(); Pair<char> pc; pc.set('x', 'y'); pc.print(); return 0; }
A Non Conforming Specialization is Legal template<typename T> struct Pair { void set(const T& x, const T& y) { a = x; b = y; } void print() { cout << a << "," << b << endl; } private: T a, b; }; template<> struct Pair<bool> { void set(bool x, bool y) { v = (x?1:0) + (y?2:0); } public: int v; }; void doSomething() { Pair<bool> pb; pb.set(true, false); cout << pb.v << endl; Pair<char> pc; pc.set('x', 'y'); pc.print(); pb.print(); // Error. Pair<bool>::print() is undefined }
C++ Templates: Interim Summary • A template is a “function” • Arguments: types, constants • Return value : A new class or a new function • Recognized by the compiler • The template is recompiled with each Instantiation • Specialization == “if” • Different result based on the actual arguments
Compile Time Computations • template <int N> • struct Factorial • { • enum { value = N * Factorial<N - 1>::value }; • }; • template <> • struct Factorial<0> • { • enum { value = 1 }; • }; • void foo() • { • int x = Factorial<4>::value; // == 24 • int y = Factorial<0>::value; // == 1 • }
A Non-Terminating Compilation template<typename T> struct Loop { typedef typename Loop<Loop<T> >::Temp Temp; }; int main(int, char**) { Loop<int> n; return 0; }
Constraints on Parameters? • Constant parameters: type of argument. In C++, the usual type conversions are allowed, e.g., • if formal parameter to class template is of type T*, then actual parameter can be of type T1*,provided that T1 is derived from T. • Type of parameters: what is the type of a type? • Main approaches: • No restrictions • Dynamic checking • Explicit list of constraints • By derivation
No Restriction on Arguments • Any type is allowed. Generic limited to what can be done on all types. • Example: • Early versions of Eiffel. • Advantages: • Code sharing. • Disadvantages: • Restrictions on generics. • Only the most primitive operations are allowed on arguments.
Dynamic Checking of Arguments (The generic equivalent of dynamic typing.) The compiler tries to instantiate a template. If an invalid operation is attempted, then an error message is issued. • Example: • C++ • Advantages: • Compiler writing is easy • Flexibility to programmer • Disadvantages: • Extra burden on the library designer • Surprises to the user • Constraints are dependent on actual usage • Difficult to understand • Some parts of static type checking are deferred to link time
Explicit List of Constraints The programmer specifies the set of operations that might be used on a type argument. • Example: Ada • Advantages: • Readability • No surprises • Disadvantages: • Lists could be very long • Less flexibility in the design of classes • List of operations instead of abstraction of functionality
Constraints on Type Parameters (cont.) • By derivation. The actual parameter must be the same or inherited from the declared type. • Example: • Current Eiffel • Advantages: • Code sharing • OOP Like • Abstraction at the right level • Disadvantages: • Could lead to over use of inheritance • Inheritance is used for abstraction, not only for subtyping • Too specific parameter types • Too general parameter types • Not appropriate for built-in types
Quiz: What are the 6 Constraints? template <class T> T avg(const T a[], int size) { T sum = a[0]; for (int i = 1; i < size; i++) sum += a[i]; return sum/size; }
Quiz: The 5 Constraints template<class Type> class Stack { Type buff[50]; int sp; public: Stack(void): sp(0) {} void push(const Type &e) { buff[sp++] = e; } Type pop(void) { return buff[--sp]; } int empty(void) const { return sp == 0; } int full(void) const { return sp == 50; } };
Instantiation • Implicit: Instantiation occurs whenever an instance is required. • Examples: ML, Function Templates in C++. • Advantages: simple syntax. • Disadvantages: Surprises. Requires a complex type inference engine: can be in some conditions an undecidable problem. • Implicit Usage: Instantiation occurs whenever the programmer uses an instance of a generic for the first time. • Examples: Class templates in C++. Optional in function templates in C++ (since November 1993). • Advantages: Balance between convenience and accuracy. • Disadvantages: Confusing error messages. Relatively complex syntax. Obscure environment of instantiation. • Explicit: Programmer must instantiate a generic priorly to using it. • Examples: Ada. C++ new features: explicit instantiation. • Advantages: Explicit. No surprises. • Disadvantages: What if the same generic is instantiated twice?
Example: Function Template Instantiation template <class T> T max(T a,T b) { return a > b ? a : b; } int main() { extern int f(int); int i(10), j = f(i); int k = max(i, j); extern double cos(double); double r(0.5), s(cos(r)); double t = max(r, s); return max(k, int(1/t)); }
Function Return Type? The function return type is not used for resolving ambiguities template <class T> inline long double sum(const T a[], int size){ return (long double) avg(a, size) * size; } template <class T> inline T sum(const T a[], int size) { return avg(a, size) * size; } The compiler will complain if the sum template is used
Class Template Instantiation • Whenever a new particular version of the class template Stackis used (such as Stack<int>or Stack<char*>), the compiler will arrange for a version of that template to be instantiated. • Each instantiation of template class is a class by itself. • The template classes Stack<int>, Stack<Complex>, and Stack<void*>are different classes. • There may be compile time errors when a template is instantiated. • Only the members that are used should be instantiated. • In a single compilation unit, instantiation of a template occurs only once for each particular set of values of the arguments.
Explicit Instantiation in C++ New feature: adopted in San Jose, November 1993. • Ability to specify environment for instantiation. • Pre-create libraries of instantiations. Independent of changes in the environment of the using programs. Some implementations: multiple explicit instantiation may produce an error message or warning. • Explicit instantiation of a class template: forces instantiation of all its members, whereby forcing constraints. template class Stack<int>; • Explicit instantiation of a function member of a class template: template void Stack<int>::push(int &); • Explicit instantiation of a function template: ...
Explicit Instantiation of Function Template Given the function template:template <class T> T max(T a, T b) { return a > b ? a : b;} Explicit Instantiation is: template int max<int>(int, int); // naming the argument template int max(int, int); // the argument is deduced here