 |
Department of Engineering |
 |
 |
C++ templates
Contents
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.
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);
}
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.
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.