I l@ve RuBoard Previous Section Next Section

2.7 Defining and Using Template Functions

Let's say that a colleague of ours has asked for three additional display_message() instances to handle a vector of integers, a vector of doubles, and a vector of strings:



void display_message( const string&, const vector<int>& ); 


void display_message( const string&, const vector<double>& ); 


void display_message( const string&, const vector<string>& ); 

On completing the implementation of these three instances, we notice that the function body for each instance is exactly the same. The only difference across these functions is the type of the second parameter: [3]

[3] A more flexible implementation adds a third parameter of type ostream that by default is set to cout:






void display_message( const string&, const vector<string>&, 


                      ostream& = cout ); 



void display_message( const string &msg, const vector<int> &vec ) 


{ 


     cout << msg; 


     for ( int ix = 0; ix < vec.size(); ++ix ) 


           cout << vec[ ix ] << ' '; 


} 





void display_message( const string &msg, const vector<string> &vec ) 


{ 


     cout << msg; 


     for ( int ix = 0; ix < vec.size(); ++ix ) 


           cout << vec[ ix ] << ' '; 


     cout << '\n'; 


} 





void display_message( const string&, const vector<string>&, 


                      ostream& = cout ); 

There's no reason to think that another colleague won't come along asking for yet another instance that supports a vector of some additional type. It would certainly save us a great deal of effort if we could define a single instance of the function body rather than duplicate the code multiple times and make the necessary minor changes to each instance. To do that, however, we need a facility to bind that single instance to each vector type we wish to display. The function template mechanism provides just this facility.

A function template factors out the type information of all or a subset of the types specified in its parameter list. In the case of display_message(), we wish to factor out the type of the element contained within the vector. This allows us to define a single instance of the unchanging part of the function template. It is incomplete, however, because the factored-out type information is missing. This type information is supplied by the user in using a particular instance of the function template.

A function template begins with the keyword template. It is followed by a list of one or more identifiers that represent the types we wish to defer. The list is set off by a less-than/greater-than bracket pair (<, >). The user supplies the actual type information each time he uses a particular instance of the function. These identifiers in effect serve as placeholders for actual data types within the parameter list and body of the function template. For example,



template <typename elemType> 


void display_message( const string &msg, 


                      const vector<elemType> &vec ) 


{ 


     cout << msg; 


     for ( int ix = 0; ix < vec.size(); ++ix ) 


     { 


           elemType t = vec[ ix ]; 


           cout << t << ' '; 


     } 


} 

The keyword typename specifies elemType as a type placeholder within the function template display_message(). elemType is an arbitrary name. I could have easily chosen foobar or T. We must defer the actual type of the vector to be displayed. We do that by placing elemType within the bracket pair following vector.

What about the first parameter? msg never varies its type with each invocation of display_message(). It is always a constant reference to a string class object, so there is no need to factor its type. A function template typically has a combination of explicit and deferred type specifiers in its parameter list.

How do we use a function template? It looks pretty much the same as the use of an ordinary function. For example, when we write



vector< int > ivec; 


string msg; 


// ... 


display_message( msg, ivec ); 

the compiler binds elemType to type int. An instance of display_message() is created in which the second parameter is of type vector<int>. Within the function body, the local object t also becomes an object of type int. Similarly, when we write



vector< string > svec; 


// ... 


display_message( msg, svec ); 

elemType becomes bound to type string. An instance of display_message() is created in which the second parameter is of type vector<string>, and so on.

The function template serves as a kind of prescription for the generation of an unlimited number of function instances in which elemType is bound to a built-in or user-defined class type.

In general, we overload a function when there are multiple implementations, but each instance provides the same general service. We make a function a template when the body of the code remains invariant across a variety of types.

A function template can also be an overloaded function. For example, let's provide two instances of display_message(): one with a second parameter of type vector, and the other with a second parameter of type list. (list is another container class defined in the C++ standard library. We look at the list container class in Chapter 3.)



// overloaded instances of a function template 


template <typename elemType> 


void display_message( const string &msg, const vector<elemType> &vec ); 





template <typename elemType> 


void display_message( const string &msg, const list<elemType> &lt ); 
    I l@ve RuBoard Previous Section Next Section