C++ - Templates FAQ's

Templates is an important addition in C++ when compared with C. Lot of readers have been sending queries about templates. I would try to explain a majority of them here in a question-answer form. The questions have been suitably modified to address a general audience.

What are templates?
Templates are a mechanism for generating functions and classes based on type parameters (templates are sometimes called "parameterised types"). By using templates, you can design a single class that operates on data of many types, instead of having to create a separate class for each type. For example, to create a type-safe function that returns the minimum of two parameters without using templates, you would have to write a set of overloaded functions like this:

// min for ints
int min( int a, int b )
{
return ( a < b ) ? a : b ;
}

// min for longs
long min( long a, long b )
{
return ( a < b ) ? a : b;
}

// min for chars
char min( char a, char b )
{
return ( a < b ) ? a : b;
}
// etc...

By using templates you can reduce this duplication to a single templated function as shown below:

template T min( T a,T b )
{
return ( a < b ) ? a : b;
}

Templates can significantly reduce source code size and increase code flexibility without reducing type safety.

What are function templates?
With function templates, you can specify a set of functions that are based on the same code, but act on different types or classes. For example:

template void MySwap( T& a, T& b)
{
T c( a );
a = b;
b = c;
}

This code defines a family of functions that swap their parameters. From this template you can generate functions that will swap not only int and long types, but also user-defined types. MySwap will even swap classes if the class's copy constructor and assignment operator are properly defined. In addition, the function template will prevent you from swapping objects of different types, since the compiler knows the types of the a and b parameters at compile time. Note that all of the template parameters inside the angle brackets must be used as parameters for the templated function. You call a templated functions as you would a non-templated function; no special syntax is needed. For example:

int i, j;
char k;
MySwap( i, j ); //OK
MySwap( i, k ); //Error, different types.

What do you mean by Function Template Instantiation?

When a templated function is first called for each type, the compiler creates an "instantiation", a specialized version of the templated function will be called every time the function is used for the type. If you have several identical instantiations, even in different modules, only one copy of the instantiation will end up in the executable. Standard type conversions are not applied to templated functions. Instead, the compiler first looks for an "exact match" for the parameters supplied. If this fails, it tries to create a new instantiation to create an "exact match". Finally, the compiler attempts to apply overloading resolution to find a match for the parameters. if this fails, the compiler generates an error. What is a function template override? With a templated function, you can define special behavior for a specific type by providing a non-templated function for that type. For example:

void MySwap( double a, double b );

This declaration enables you to define a different function for double variables. Like other non-templated functions, Standard type conversions (such as promoting a variable of type float to double) are applied.

What are Class Templates?

You can use class templates to create a family of classes that operate on a type. For example:

template class ex
{
public:
ex ( void ) ;
~ex( void ) ;
int setdata( T a, int b ) ;
private:
T Tarray[i] ;
int arraysize ;
} ;

In this example, the templated class uses two parameters, a type T and an int i. The T parameter can be passed any type, including structures and classes. The i parameter has to be passed an integer constant. Since i is a constant defined at compile time, you can define a member array of size i using a standard automatic array declaration. Unlike function templates, you do not use all template parameters in the definition of a templated class.

How do you define member functions of a template class?

Members of template class are defined slightly differently than those of non-templated class. Continuing the preceding example:

template
int ex<>::setdata( T a, int b )
{
if( ( b >= 0 ) && (b < i ) )
{
Tarray[b++] = a ;
return sizeof( a ) ;
}
else
return -1 ;
}

Constructors and Destructors

Although constructors and destructors reference the name of the templated class twice, the template parameters should be referenced only once in the fully specified name.

template
TempClass<>::TempClass( void )
{
TRACE( "TempClass created. \n" ) ;
}

template
TempClass<>::~TempClass( void )
{
TRACE( "TempClass destroyed. \n" ) ;
}

Class Template Instantiation

Unlike function templates, when instantiating a class template, you must explicitly instantiate the class by giving the parameters for the templated class. To create an instance of TempClass:

TempClass<> test1 ; // OK
TempClass<> test2 ; // Error, second parameter
// must be constant.

No code is generated for a templated class ( or function ) until it is instantiated. Moreover, member functions are instantiated only if they are called. This can cause problems if you are building a library with templates for other users.

Angle Bracket Placement

Bad placement of angle brackets ( <> ) causes many template syntax errors. Make sure that you use proper spacing and parenthesis to distinguish angle brackets from operators such as >> and ->. For example:

TempClass<> b ? a : b > test1 ;

should be rewritten as

TempClass<> b ? a : b ) > test1 ;

Similarly, pay extra attention when using macros that use angle brackets as template arguments.

When Should You Use Templates ?

Templates are often used to :
* Create a type-safe collection class ( for example, a stack ) that can operate on data of any type.
* Add extra type checking for functions that would otherwise take void pointers.
* Encapsulate groups of operator overrides to modify type behavior ( such as smart pointers ) .
Most of these uses can be implemented without templates offer several advantages:
* Templates are easier to write. You create only one generic version of your class or function instead of manually creating specializations.
* Templates can be easier to understand, since they can provide a straightforward way of abstracting type information.
* Templates are type safe. Since the types that templates act upon are known at compile-time, the compiler can perform type checking before errors occur.

Templates vs. Macros

In many ways, templates work like preprocessor macros, replacing the templated variable with the given type. However, there are many differences between a macro like this:

# define min(i,j) ((i) < (j) ? (i) : (j))

and template:

template T min (T i, T j) { return (i
Here are some problems with the macro :
* There is no way for the compiler to verify that the macro parameter are of compatible types. The macro is expanded without any special; type checking.
* The i and j parameter are evaluated twice . For example, if either parameter has a post incremented variable, the incremented is performed two times.
* Since macros are expanded by the preprocessor, compiler error messages will refer to the expanded macro, rather than the macro definition itself. Also the macro will show up in expanded form during debugging.

Templates vs. void Pointers

Many functions that are now implemented with void pointers can be implemented with templates. Void pointers are often used to allow functions to operate on data of an unknown type. When using void pointers, the compiler cannot distinguish types, so it cannot perform type checking or type-specific behavior such as using type-specific operator overloading, or constructors and destructors . With templates, you can create functions and classes that operate on typed data. The type looks abstracted in the template definition. However, at compile-time the compiler creates a separate version of the function for each specified type. This enables the compiler to treat templated classes and function as if they acted on specific types. Templates can also improve coding clarity, since you don't need to create special cases for complex types such as structures.

Collection Classes

Templates are a good way of implementing collection classes. Version 3.0 of the Microsoft Foundation Classes uses templates to implement six new collection classes: CArray, CMap, CTypedPtrArray, CTypedPtrList, and CTypedPtrMap. For information on using and customizing these classes, see the "collections" articles in Programming with the Microsoft Foundation Class Library. The My stack collection is a simple implementation of a stack. The two template parameter ,T and i, specify the type of elements in the stack and the maximum number of that item in the stack. The push and pop member functions add and remove items in the stack, with the stack growing from the bottoms of the stack.

templates class My stack
{
T stackBuffer[i];
int cItems;
public:
void Mystack(void):cItems(i) {};
void push ( const T items ) ;
T pop ( void );
};
template void My stack<>::push (const T items)
{
if (cItems >0)
stackbuffer[--cItems] = items;
else
throw " stack overflow error .";
return;
}

template T My stack::pop (void)
{
if(cItems return stackBuffer[cItems++]
else
throw "stack underflow error .";
}

Smart Pointers

C++ allows you to create "smart pointer" classes that encapsulate pointer and override pointer operators to add new functionally to pointer of almost any type. The following code outlines a simple references count garbage collector. The template class Ptr implemented a garbage collecting pointer to any class derived from Refcount.

class Refcount
{
int crefs;
public:
Refcount (void){crefs =0;}
void upcount(void){ ++crefs;}
void downcount(void){if(--crefs ==0)delete this ;}
};
class Sample : public Refcount
{
public :
void dosomething(void){ TRACE(did somethiing\n");}
};
template class Ptr
{
T* p;
public :
Ptr(T* P_): p(p_){ p->upcount();}
~Ptr(void){ p->downcount();}
operator T*(void){ return p;}
T& oprator* (void){return *p}
T* operator->(void){ return p;}
Ptr& operator =(t*p_)
{
p->upcount(); p=p_ ; p->downcount();return *this;
}
};

int main()
{
Ptr p =new Samples; // samples #1
Ptr p2 = new Samples;// samples #2
p=p2; // #1 has 0 refs, so it is destroyed ; #2 has two refs
p->doSomething();
return 0;
// As p2 and p go out of scope, their destructors call
// downcount. The cref varaible of #2 goes to 0,so #2 is
//destroyed
}

Classes Refcount and Ptr> together provide a simple garbage collection solution for any class that can afford the int per instance overhead to inherit from Refcount. Note that primary benefit of using a parametric class like Ptr instead of a more generic class like Ptr is the former is complete type-safe. The preceding code ensures that a Ptr can be used almost anywhere a T* is used; in contrast, a generic Ptr would only provide implicit conversions to void*. For example, if this class is used to create and manipulate garbage collected files, symbols, strings, and so forth. From the class template Ptr, the compiler will create template classes Ptr, Ptr, Ptr, and so on, and their member functions: Ptr::~Ptr( ), Ptr:: operator File*( ), Ptr::~Ptr( ), Ptr::operator String*( ), and so on.

Download

No comments: