Definition
Template metaprogramming (TMP) is the process of writing template-based C++ programs that execute during compilation. Think about that for a minute: a template metaprogram is a program written in C++ that executes inside the C++ compiler. When a TMP program finishes running, its output — pieces of C++ source code instantiated from templates — is then compiled as usual.
TMP Greatest Strength
- it makes some things easy that would otherwise be hard or impossible.
- because template metaprograms execute during C++ compilation, they can shift work from runtime to compile-time.
One consequence is that some kinds of errors that are usually detected at runtime can be found during compilation. Another is that C++ programs making use of TMP can be more efficient in just about every way: smaller executables, shorter runtimes, lesser memory requirements. (However, a consequence of shifting work from runtime to compile-time is that compilation takes longer. Programs using TMP may take much longer to compile than their non-TMP counterparts.)
Trait Example in Item 47
void advance(std::list<int>::iterator& iter, int d) {
if (typeid(std::iterator_traits<std::list<int>::iterator>::iterator_category) ==
typeid(std::random_access_iterator_tag)) {
iter += d; // compile error
} else {
if (d >= 0) { while (d--) ++iter; }
else { while (d++) --iter; }
}
}
std::list<int>::iterator iter;
...
advance(iter, 10); // move iter 10 elements forward;
// won’t compile with above impl.
In this case, we’re trying to use +=
on a list<int>::iterator
, but list<int>::iterator
is a bidirectional iterator (see Item 47, so it doesn’t support +=
. Even though, iter += d
will not execute, the compiler will still check if the grammar is correct because compilers are obliged to make sure that all source code is valid, even if it’s not executed. Using TMP(Trait method mentioned in Item 47 can solve this problem.
Template metaprogramming (TMP)
TMP has been shown to be Turing-complete, which means that it is powerful enough to compute anything. Using TMP, you can declare variables, perform loops, write and call functions, etc.
Loop in TMP
TMP has no real looping construct, so the effect of loops is accomplished via recursion. Even the recursion isn’t the nor- mal kind, however, because TMP loops don’t involve recursive function calls, they involve recursive template instantiations.
Factorial Example
template<unsigned n> // general case: the value of
struct Factorial { // Factorial<n> is n times the value
// of Factorial<n-1>
enum { value = n * Factorial<n-1>::value };
};
template<> // special case: the value of
struct Factorial<0> { // Factorial<0> is 1
enum{value=1};
};
Given this template metaprogram (really just the single template metafunction Factorial), you get the value of factorial(n) by referring to Factorial<n>::value
.
The looping part of the code occurs where the template instantiation Factorial<n>
references the template instantiation Factorial<n-1>
. Like all good recursion, there’s a special case that causes the recursion to terminate. Here, it’s the template specialization Factorial<0>.
Each instantiation of the Factorial template is a struct, and each struct uses the enum hack (see Item 2) to declare a TMP variable named value. Since TMP uses recursive template instantiation in place of loops, each instantiation gets its own copy of value, and each copy has the proper value for its place in the “loop.”
What TMP can accomplish?
- Ensuring dimensional unit correctness : In scientific and engineering applications, it’s essential that dimensional units (e.g., mass, distance, time, etc.) be combined correctly. Using TMP, it’s possible to ensure (during compilation) that all dimensional unit combinations in a program are correct, no matter how complex the calculations. (This is an example of how TMP can be used for early error detection.) One interesting aspect of this use of TMP is that fractional dimensional exponents can be supported. This requires that such fractions be reduced during compilation so that compilers can confirm, for example, that the unit t i m e 1 2 time^{\frac{1}{2}} time21 is the same as t i m e 4 8 time^{\frac{4}{8}} time84.
- Optimizing matrix operations: Consider following code:
Using an advanced template technology related to TMP called expression templates, it’s possible to eliminate the temporaries and merge the loops, all without changing the syntax of the client code above. The resulting software uses less memory and runs dramatically faster.typedef SquareMatrix<double, 10000> BigMatrix; BigMatrix m1, m2, m3, m4, m5; // create matrices and ... // give them values BigMatrix result = m1 * m2 * m3 * m4 * m5; // compute their product
- Generating custom design pattern implementations: Design patterns like Strategy (see Item 35), Observer, Visitor, etc. can be implemented in many ways. Using a TMP-based technology called policy-based design, it’s possible to create templates representing independent design choices (“policies”) that can be combined in arbitrary ways to yield pattern implementations with custom behavior.
- Generalized beyond the domain of programming artifacts like design patterns and smart pointers, this technology is a basis for what’s known as generative programming.
Conclusion
TMP is not for everybody. The syntax is unintuitive, and tool support is weak. However, TMP support is on the rise. It’s likely that the next version of C++ will provide explicit support for it, and TR1 already does (see Item 54). TMP will probably never be mainstream, but for some programmers — especially library developers — it’s almost certain to be a staple.
Things to Remember
- Template metaprogramming can shift work from runtime to compile-time, thus enabling earlier error detection and higher runtime performance.
- TMP can be used to generate custom code based on combinations of policy choices, and it can also be used to avoid generating code inappropriate for particular types.