C++ Templates David Camp
C Marcos #define <identifier>(<parameter list>) <replacement token list> #define min(i, j) (((i) < (j))? (i) : (j)) #define max(i, j) (((i) > (j))? (i) : (j)) #define RADTODEG(x) (x * 57.29578) // What is the problem with using this macro RADTODEG(a + b) // = (a + b * 57.29578)
C Marcos #define PRETTY_PRINT(msg) \ printf ("Message: '%s'\n", msg); if (n < 10) PRETTY_PRINT("n is less than 10"); else PRETTY_PRINT("n is at least 10"); error: expected expression before else gcc 4.1.1 Why do we get this error message?
C Macro Problems There are many problems with C macros: There is no way for the compiler to verify that the macro parameters are of compatible types. The macro is expanded without any special type checking. Debugging problems. May only show the macro or may step in but the variables are wrong Cause wrong code to be generated. See more about C Macros at http://en.wikipedia.org/wiki/c_macro
Function Templates template<class T> const T& max( const T& i, const T& j ) return ((i < j)? i : j) ; // use in code cout << max( 3, 7 ); // outputs 7 // Compiler create the following function const int& max( const int& x, const int& y ) return((i < j)? i : j);
Function Templates int main() // calls max<int> (by argument deduction) std::cout << max( 3, 7 ) << std::endl; // calls max<double> (by argument deduction) std::cout << max( 3.0, 7.0 ) << std::endl; // The type is ambiguous, explicitly instantiate max<double> std::cout << max<double>( 3, 7.0 ) << std::endl; return 0;
Function Templates Perform identical operations for each type of data compactly and conveniently Based on the argument types provided in calls to the function, the compiler automatically instantiates separate object code functions to handle each type of call appropriately. STL algorithms are implemented as function templates
Class templates Class Templates Allow type-specific versions of generic classes Format: template <class T> class ClassName // Class Definition Need not use "T", any identifier will work To create an object of the class, type ClassName< type > myobject; Example: Stack< double > doublestack;
Class Templates Functions Template class functions Declared normally, but preceded by template<class T> Generic data in class listed as type T Template class function definition for class Constructor: template<class T> ClassName< T >::ClassName( int size ) // Creates an array of type T myarray = new T[size];
#define DEFAULT_STACK_SIZE 10 template < class T > class Stack public: Stack( int size = DEFAULT_STACK_SIZE ); ~Stack() delete [] stackptr; bool push( const T& pushvalue ); bool pop( T& popvalue ); private: int _size; int _top; T * _stackptr; // default constructor // destructor // push an element onto the stack // pop an element off the stack // # of elements in the stack // location of the top element // pointer to the stack ; bool isempty() const return( top == -1 ); // utility bool isfull() const return( top == size -1 ); // functions template < class T > Stack< T >::Stack( int size ) _size = size > 0? size : DEFAULT_STACK_SIZE; _top = -1; // Stack is initially empty _stackptr = new T[_ size ]; // allocate space for elements
// Push an element T onto the stack // return true if successful, false otherwise template< class T > bool Stack< T >::push( const T &pushvalue ) if(!isfull() ) stackptr[ ++top ] = pushvalue; // place item in Stack return( true ); // push successful return( false );// push unsuccessful // Pop an element off the stack template< class T > bool Stack< T >::pop( T &popvalue ) if(!isempty() ) popvalue = stackptr[ top-- ]; return( true ); return( false ); // remove item from Stack // pop successful // pop unsuccessful
int main() Stack< double > doublestack( 5 ); cout << "Pushing elements onto doublestack" << endl; for(double f = 1.1; doublestack.push( f ) ; f += 1.1 ) // if success true is returned cout << f << ' '; cout << "\nstack is full. Cannot push " << f << "\n\npopping elements from doublestack\n"; while( doublestack.pop( f ) ) // if success true is returned cout << f << ' '; cout << "\nstack is empty. Cannot pop\n"; Stack< int > intstack; cout << "\npushing elements onto intstack\n"; for(int i = 1; intstack.push( i ) ; ++i) // if success true is returned cout << i << ' '; cout << "\nstack is full. Cannot push " << i << "\n\npopping elements from intstack\n"; while( intstack.pop( i ) ) // if success true is returned cout << i << ' '; cout << "\nstack is empty. Cannot pop\n"; return( 0 );
Stack Sample Output Pushing elements onto doublestack 1.1 2.2 3.3 4.4 5.5 Stack is full. Cannot push 6.6 Popping elements from doublestack 5.5 4.4 3.3 2.2 1.1 Stack is empty. Cannot pop Pushing elements onto intstack 1 2 3 4 5 6 7 8 9 10 Stack is full. Cannot push 11 Popping elements from intstack 10 9 8 7 6 5 4 3 2 1 Stack is empty. Cannot pop
Class Templates and Non-type Parameters Can use non-type parameters in templates Default argument Treated as const Example: template< class T, int elements > Stack< double, 100 > mostrecentsalesfigures; Declares object of type Stack< double, 100> This may appear in the class definition: T stackholder[ elements ]; //array to hold stack Creates array at compile time, rather than dynamic allocation at execution time
Templates and Inheritance A class template can be derived from a template class A class template can be derived from a nontemplate class A template class can be derived from a class template A non-template class can be derived from a class template
Templates and friends Friendships allowed between a class template and Global function Member function of another class Entire class friend functions Inside definition of class template X: friend void f1(); f1() a friend function of all template classes friend void f2( X< T > & ); f2( X< int > & ) is a friend function of X< int > only. The same applies for float, double, etc. friend void A::f3(); Member function f3 of class A is a friend of all template classes
Templates and friends friend void C< T >::f4( X< T > & ); C<float>::f4( X< float > & ) is a friend function of class X< float > only friend classes friend class Y; Every member function of class Y is a friend with every template class made from X friend class Z< T >; Class Z< float > a friend of class X< float >, etc.
Template Parameters C++ templates allow one to implement a generic Stack<T> template that has a type parameter T. T can be replaced with actual types. Templates can have any number of parameters. C++ allows you to specify a default template parameter, so the definition could now look like: template Stack <class T = float, int elements = 100>
Template Specialization Template class specialization overrides the template-generated code by providing special definitions for specific types Template class partial specialization generate a specialization of the class for just one parameter
Template Specialization template<> const string& max<string>( const string& s1, const string& s2 ) if( s1.size() > s2.size() ) return( s1 ); return( s2 );
Partial Template Specialization template< class L, class R > L add( L left, R right ) return( left + right ); template< class R > string add( string left, R right ) ostringstream s; s << left << right; return( s.str() );
Template Metaprogramming template < int N > struct Factorial const int value = N * Factorial<N - 1>::value; ; template <> struct Factorial< 0 > const int value = 1; ; template <> struct Factorial< 1 > const int value = 1; ;
Benefits of Templates C++ promotes code reusability. Templates provide a way to re-use source code Templates are type-safe. Because the types that templates act upon are known at compile time, the compiler can perform type checking before errors occur Create a type-safe collection class (for example, a stack) that can operate on data of any type Templates avoid some of the common errors found in code that use macros like function
Drawbacks to Templates Compiler support Poor error messages Code bloat Hard to find errors in Templates Hard to read error messages during compiling