Department of Engineering

IT Services

C++ templates

Introduction

Templates (which were introduced into C++ in 1989) are used internally by the standard library. They can be created by programmers too. Whole books (e.g. "C++ Templates: The Complete Guide" by Vandevoorde and Josuttis) are devoted to them. This document is a brief introduction.

Rather than offer a description of templates let's build up to an example. The following code swaps 2 integers in the usual C++ way

void swap (int& a, int& b) {
   int tmp = a;
   a = b;
   b = tmp;
}

If you wanted to swap other types of variables (including those you've defined) you could copy this code, replacing int by (say) float. But in situations like this, where the code is potentially generic, templates can be used -

template <class T>
void swap (T& a, T& b) {
   T tmp = a;
   a = b;
   b = tmp;
}

Here the "T" name is arbitrary (like a formal parameter in a function definition). Note that the function is written much as before. When the compiler is now given the following code

   int i, j;
   float f, g;
   swap (i, j);
   swap (f, g);

it will create a version ("instantiation") of swap for each type required, replacing T by int and float respectively. Note we still have "strong typing": swap (i, f); (trying to swap a float and an integer) will be caught by the compiler because the template requires the 2 arguments to be the same type.

Template Specialization

Simple templates like the one above can save a lot of work, but sometimes (perhaps for optimisation reasons) you need to treat one type as a special case. Suppose that for some reason you wanted to do something different when swapping doubles. C++ lets you specialize a template.

#include <iostream>
template <class T> 
void swap (T& a, T& b) 
{ 
 T tmp = a; 
 a = b; 
 b = tmp; 
 std::cout <<"Unspecialized\n";
}

template <>
void swap (double& a, double& b) 
{ 
 double tmp = a; 
 a = b;
 b = tmp; 
 std::cout <<"Specialized\n";
}

int main() {
  int i,j;
  float f,g;
  double d,e;

  swap(i,j);
  swap(f,g);
  swap(d,e);
}

Class templates and non-type template parameters

The examples above were function templates. In much the same way it's also possible to have class templates. Values as well as types can be used as template parameters. The following code makes MyArray a class whose data array is controlled by parameters.

#include <iostream>
template <class T, int size> 
class MyArray {
public:
  T data[size];
  void how_big() { std::cout << "Size " << size << std::endl;}
};

int main() {
  MyArray<int,7> seven_ints;
  MyArray<float,10> ten_floats;
  seven_ints.how_big();
}

This lets the template create arrays of a fixed size (good for performance reasons, maybe) but it means that a new piece of code will be created for each different type/size combination used.

Code Bloat

Templates can make source code smaller and tidier, but with so much code being created behind the scenes, it's possible for the resulting executable to become unexpectedly large, especially if non-type parameters are used. The following code (from Guotao Luan's C++ Template Review) creates a Factorial class. When the code is compiled, code is generated to deal with the N=5 situation, and (by recursion) code is generated to deal with N=4, etc. The N=1 case is dealt with as a specialization.

#include <iostream>
using namespace std;

template<int N>
class Factorial {
public:
    static const int value = N * Factorial<N-1>::value;
};

class Factorial<1> {
public:
    static const int value = 1;
};

int main() {
  Factorial<5L> f;
  cout << "5! = " << f.value << endl;
}

This is an example of a situation where speed is gained at the cost of code-bloat. In computationally-dense code this can be a valuable feature (the work being done at compile-time rather than run-time) but recursion can get out of hand. GCC compilers have a -ftemplate-depth option to limit compiler-recursion depth. ANSI/ISO C++ conforming programs must not rely on a maximum depth greater than 17.

Even with simple function templates like swap there can be trouble when you have many source files. It would be nice if in swap.h you could have

void swap (T& a, T& b);

which could be #included by each source file that wanted to use the routine, then have the body of code in a separate file (called swap.cc, say). This is how source code is usually organised. The C++ specification allows this for templates, but you need to use the export keyword in swap.cc to make this work, and hardly any compilers support this use of export. So for now you need to #include the swap.cc code in each source file that uses swap.