March 9, 2011, 11:16 a.m.

posted by technodev

## Metaprogramming in C++In C++, it was discovered almost by accident [Unruh94], [Veld95b] that the template mechanism provides a rich facility for native language metaprogramming. In this section we'll explore the basic mechanisms and some common idioms used for metaprogramming in C++. ## Numeric ComputationsThe earliest C++ metaprograms performed integer computations at compile time. One of the very first metaprograms was shown at a C++ committee meeting by Erwin Unruh; it was actually an illegal code fragment whose error messages contained a sequence of computed prime numbers! Since illegal code is hard to use effectively in a larger system, let's examine a slightly more practical application. The following metaprogram (which lies at the heart of our little compiler test above) transliterates unsigned decimal numerals into their binary equivalents, allowing us to express binary constants in a recognizable form. template <unsigned long N> struct binary { static unsigned const value = binary<N/10>::value << 1 // prepend higher bits | N%10; // to lowest bit }; template <> // specialization struct binary<0> // terminates recursion { static unsigned const value = 0; }; unsigned const one = binary<1>::value; unsigned const three = binary<11>::value; unsigned const five = binary<101>::value; unsigned const seven = binary<111>::value; unsigned const nine = binary<1001>::value; If you're wondering "Where's the program?" we ask you to consider what happens when we access the nested
Because the C++ language imposes a distinction between the expression of compile-time and runtime computation, metaprograms look different from their runtime counterparts. As in Scheme, the C++ metaprogrammer writes her code in the same language as the ordinary program, but in C++ only the compile-time subset of the full language is available to her. Compare the previous example with this straightforward runtime version of unsigned binary(unsigned long N) { return N == 0 ? 0 : N%10 + 2 * binary(N/10); } A key difference between the runtime and compile time versions is the way termination conditions are handled: our meta-binary uses template specialization to describe what happens when Another important difference between runtime and compile time C++ is highlighted by this version of unsigned binary(unsigned long N) { unsigned result = 0; for (unsigned bit = 0x1; N; N /= 10, bit <<= 1) { if (N%10) result += bit; } return result; } Though more verbose than the recursive version, many C++ programmers will be more comfortable with this one, not least because at runtime iteration is sometimes more efficient than recursion. The compile-time part of C++ is often referred to as a "pure functional language" because of a property it shares with languages such as Haskell: (meta)data is immutable and (meta)functions can have no side effects. As a result, compile-time C++ has nothing corresponding to the non-const variables used in runtime C++. Since you can't write a (non-infinite) loop without examining some mutable state in its termination condition, iteration is simply beyond reach at compile time. Therefore, recursion is idiomatic for C++ metaprograms. ## Type ComputationsMuch more important than its ability to do compile time numeric computations is C++'s ability to compute with types. As a matter of fact, type computation will dominate the rest of this book, and we'll cover examples of it in the very first section of the next chapter. By the time we're through, you'll probably think of template metaprogramming as "computing with types." Although you may have to read Chapter 2 to understand the specifics of type computation, we'd like to give you a sense of its power. Remember our YACC expression evaluator? It turns out we don't need to use a translator to get that kind of power and convenience. With appropriate surrounding code from the Boost Spirit library, the following legal C++ code has equivalent functionality. expr = ( term[expr.val = _1] >> '+' >> expr[expr.val += _1] ) | ( term[expr.val = _1] >> '-' >> expr[expr.val -= _1] ) | term[expr.val = _1] ; term = ( factor[term.val = _1] >> '*' >> term[term.val *= _1] ) | ( factor[term.val = _1] >> '/' >> term[term.val /= _1] ) | factor[term.val = _1] ; factor = integer[factor.val = _1] | ( '(' >> expr[factor.val = _1] >> ')' ) ; Each assignment stores a function object that parses and evaluates the bit of grammar on its right hand side. The behavior of each stored function object, when invoked, is determined entirely by the type of the expression used to construct it, and the type of each expression is computed by a metaprogram associated with the various operators used. Just like YACC, the Spirit library is a metaprogram that generates parsers from grammar specifications. Unlike YACC, Spirit defines its domain language as a subset of C++ itself. If you don't see how that's possible at this point, don't worry. By the time you finish this book, you will. |

- Comment