First Next Previous Last Glossary About

Introducing Templates

Introduction

#include <iostream>
#include <iomanip>

int add_values(int a, int b) 
{ return (a + b); }
 
float add_values(double a, double b)
{ return a + b; }

You might remember, in the lesson on function overloading, that we can declare functions which have the same name but different signatures.

The example shows two functions each called add_values but the functions have different argument lists.

This difference means that the compiler can distinguish between calls to the functions on the basis of the arguments. Somehow the compiler generates a reference to the correct function.

There is an alternative to function overloading - we use the template. The template lets us create functions and classes whose differences are determined by the data types we use with them.

The template is exactly what the name suggests - it is a template or a pattern for a family of functions or classes.

Function templates

What they are.

If you look at our two add_value functions you can see that they follow a general pattern:

datatype add_values(datatype arg1, datatype arg2)
{ return arg1 + arg2; }

The only difference between the two is the type of data, in one case integer, in the other case float.

#include <iostream>

template <class DataType>
DataType add_values( DataType a, DataType b );

int main()
{ cout << add_values(11,11) << endl; 
  cout << add_values(11.123, 11.567) << endl;
  return 1;
}

template <class DataType>
DataType add_values( DataType a, DataType b )
{ return ( a + b ); }

Here is how we do it in C++ code.

We have a prototype which declares that there will be a template of the class DataType.

The name DataType can be any name we wish, it simply stands for a type of data. We use the declaration <class DataType> which means DataType might be a class but it can be any data type.

The template class is defined elsewhere in the usual fashion and we use it in the same way that we use a function.

The template mechanism can greatly enhance the reuse of code. We write one template and use it as a pattern for many different functions. When the compiler builds the program it will instantiate one particular "pattern" when it is first called. This means that we don't have inline expansions as for macros but genuine functions. If we make many calls to an integer add_value function then only one function is used, just as if we had written the actual function ourselves. It is a brilliant idea which has been extended to whole classes of algorithms and data structures. We meet many of these in the next lesson (in the Standard Template Library) but first it will help if learn how to use templates before we dealing with the standard templates.

Implicit and Explicit Parameters

Since a function template is a pattern for functions then it follows that whatever we can do for "normal" functions we can do for functions derived from templates.

The examples so far have used a single data type, for example:


template <class DataType>
DataType add_values( DataType a, DataType b );

Whenever our program instantiates a function based on this template it expects that each of the parameters will be the same datatype, for instance:


int add_values( int a, int b );
float add_values( float a, float b );

The data type of the parameters is impliedby the arguments/variables that are used to instantiate the function.

You might think this implicit data typing is a bit unrealistic, after all a function template that limits itself to parts all of the same data type is a bit limited. You'd be right too, the fathers of C++ thought likewise and to make the function template more universal they gave us the option of being able to explicitly determine data types. The next example shows how we make explicit specifications for template arguments.

#include <iostream>
#include <iomanip>

template <class T, class U, class Z>
 T func(U,Z);

The function template has three data types T, U and Z. The return type is T and the function arguments are U and Z.

int main()
{ double x = 19.256, y;
  int z = 12, w;

  cout.setf(ios::showpoint);

  y = func (11,x);
  y = func <double, int, float> (11,x);
  cout << y << endl;

  y = func <double, int, int> (11,11);
  cout << y << endl;

  w = func <double, int, int> (11,11);
  cout << w << endl;

  w = func <double, float, float>
   (11.0,11.0);
  cout << w << endl;

  y = func <int, int> (12,9);
  cout << setprecision(10)
       << y << endl;

  return 1;
}

We first set the default output to show decimal points for floating point data types.

The second statement y = func (11,x); causes the compiler to stumble with the error message: no matching function for call to `func (int, double &)'. The reason for this is that the template has been declared with three different datatypes but we have only supplied two viz, double (return type), int and double (the arguments).

We get around this by explicitly stating what data types the function should expect. The next few statements demonstrate that.

Note well: you will get a couple of warnings:

warning: assignment to `int' from `double'

See if you can tell where they will occur before you build the program.

template <class T, class U, class Z>
T func(U x, Z y)
{ cout << sizeof(T) << setw(2)   
       << sizeof(U) << setw(2)
       << sizeof(Z); 
  return x * y;
}

Here is the template defined. The sizeof statements are just there to help you see the difference between the function instantiations.

8 4 4   211.816
8 4 4   121.000
8 4 4   121
8 4 4   121
4 4 4   108.0000000

Here is the output


Return to top of page

Tutorial 1


Return to top of page

Class templates


Return to top of page

Tutorial 2


Return to top of page

First Next Previous Last Glossary About


Copyright © 1999 - 2001 David Beech