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 double
s.
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 #include
d 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
.