As promised in Part I, I think it’s about time we start to actually have some fun with C++0x’s variadiac templates. There’s lots of basic tutorials out there and people talking about variadics here and there, but I haven’t seen anyone really delve into yet, so here we go. First up, the most trivial but of metaprogramming we can do — emulating the C++0x sizeof... operator (which yields the number of parameters in a parameter pack):
1 2 3 4 5 6 7 8 9 10 | template <typename... Args> struct count; template <typename T, typename... Args> struct count<T, Args...> { static const int num = count<Args...>::num + 1; }; template <> struct count<> { static const int num = 0; }; // let's use the struct const int numArgs = count<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>; // numArgs will be equal to 10 because there are 10 parameters given to the count struct |
Line 1, is pretty much the root of all template metaprogramming trickery. The fundamentals of it all relies on recursive partial specialization, and line 1 just sets up a forward declaration to the count struct, which never actually gets used. There’s some complicated rules to how partial specializations are chosen by a C++ compiler, but the basic rule of thumb is that the most specialized version of the template that applies is what’s chosen. So although our instantiation at line 10 could match our blank forward declaration, it doesn’t. The meat of our tiny template metaprogram gets called instead: the recursive partial specialization at line 3.
And this partial specialization brings us to the key behind using variadic templates in the real-world. Since it’s not possible to do random access on the pack elements, the only choice is to “unroll” the packs recursively. The trick is to define the partial specialization such that the first argument is just a single parameter, and the next is a parameter pack:
template <typename FirstArg, typename... RemainingArgs> struct count<T, Args...>;
When the above struct is instantiated with a parameter pack, the first element gets pulled out of the pack into FirstArg, and the remaining parameters are placed in RemainingArgs. So, if the count struct makes an instantiation of itself with RemainingArgs... as the only instantiation parameter, again the first parameter will get pulled out and become FirstArg, and the rest will get placed in RemainingArgs. Thus, we can now easily unroll any parameter pack!
Let’s go back to the first code listing at line 4. This is where the recursion happens, and we can see exactly what we were talking about above happening. The count struct calls itself with the remaining arguments, and simply keeps track of the number of times the recursion happens by incrementing a counter along the way. When the instantiation finishes count::num will be set to the number of parameters in the parameter pack as of the initial instantiation plus 1. We add one because technically the recursion only counts the number of items in the parameter pack which, due to our partial specialization, is going to be one less than the number of parameters we pass to count (remember the first element gets picked out of the parameter pack).
The next thing we need to do, like in all forms of recursion, is to define the termination condition of the recursion. There are a number of ways to do this, but in this first example, we employ the “empty partial specialization” trick:
template <> struct count<> { static const int num = 0; };
This terminates the recursion not because num is implicitly set to zero, but because this specialization does not call itself. Therefore when the recursion has completely unrolled the parameter pack we hit this empty specialization and since there is no recursive call, nothing else happens. We only bother to define num here so that if someone explicitly instantiates a count struct with no arguments, they can still call count::num and get a value of zero rather than a compilation error.
Alright, so that was fun, now let’s look at something a little more complex, and a lot more useful:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | template <int N, typename... Args> struct elementType; template <int N, typename T, typename... Args> struct elementType<N, T, Args...> { static_assert(N < sizeof...(Args) + 1, "overflow!"); typedef typename elementType<N - 1, Args...>::type type; }; template <typename T, typename... Args> struct elementType<0, T, Args...> { typedef T type; }; // now let's use our elementType struct elementType<0, int, char, std::string, double>::type var0 = 1; // var0 is an int elementType<1, int, char, std::string, double>::type var1 = '2'; // var1 is a char elementType<2, int, char, std::string, double>::type var2 = ";P"; // var2 is a string elementType<3, int, char, std::string, double>::type var3 = 3.14159; // var3 is a double |
The basic idea behind the elementType struct is that we give the struct an index and a variable number of types, and we can then use the struct’s type definition as a custom data type that maps back to whatever parameter the index value is associated with. The above listing at lines 15 through 18 do exactly that.
What’s so great about that, and why would such a thing be useful? While it may not seem like such an awesome thing to do at first, what we’ve done is found a way to pick elements out of a parameter pack by an index. Essentially we’ve given ourselves a way to allow random access to packs. The technique used is similar to the count struct, but not quite the same. We’re still using recursion, but this time we need to stop the recursion at the right element.
Take a look at line 6 in the above listing. This is where all the magic happens. The elementType struct calls itself using the same variadic unrolling trick that count used, except we decrement N at each step of the way, until N reaches zero, and we hit the termination condition (the partial specialization for when N is zero at lines 9 through 12). This means if we give an index of 5, we’ll get 5 recursions and the typedef that ends up getting defined is the one for the 6th parameter in the pack.
No problem right! So let’s put all this together for something actually useful — a variadic tuple class:
1 2 3 4 5 6 | template <typename... Args> struct tuple { tuple(const Args&... args) {} }; tuple<int, char> t1(1, '2'); |
The idea is we want to be able to use our tuple class like line 6 shows. This is the example from Part I, and as I previously mentioned, this isn’t so useful. Sure it lets us declare a tuple like we want, but it doesn’t actually store the tuple’s constructor arguments, nor does it give us any way to retrieve them. So how are we going to store the elements?
Like all things variadic, we’re going to use a recursive template definition! The tuple class will instantiate itself recursively, storing the arguments as it goes. Basically the tuple class will become a compile-time linked list of sorts. This means we’ll also have to use more metaprogramming to get access to the elements, but for now let’s take a look at the tuple class definition itself:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | template <typename... Args> struct tuple; // recursion termination condition template <> struct tuple<> {}; // recursive tuple template template <typename Head, typename... TailArgs> struct tuple<Head, TailArgs...> : private tuple<TailArgs...> { /** * \brief Recursive constructor **/ tuple(const Head& head, const TailArgs&... tailArgs) : tuple<TailArgs...>(tailArgs...), mHead(head) { } private: Head mHead; ///< Tuple head }; |
Although this might look a bit weird at first, there’s not actually that much that’s new here. We’re still just recursively defining a template class, and unrolling a parameter pack as we do it, but we’re doing it via inheritance this time. The tuple class’ base class is actually itself. We use the same trick we’ve used before to extract the first parameter from the parameter pack, but now we pass the template’s TailArgs to the base class so that the base class’ Head becomes the first element in its super class’ TailArgs.
When the instantiation of this class finishes it will be a tuple class that has an mHead member variable set to the value of the first argument in the parameter pack given to the tuple’s constructor as the head variable (which you can see being set at line 14). That tuple class will have a base class that itself has an mHead member variable which is set to be the second parameter in the initial instantiation’s parameter pack (or the first parameter given to the base class instantiation). This process repeats until all the parameters in the initial pack are exhausted and the termination condition is hit.
Okay, so that’s pretty cool, but now what? Basically we’ve got this weirdly constructed tuple class with a strange inheritance hierarchy that stores all the elements given to the constructor — we still need some method for extracting them! This is where all our previous metaprogramming fun comes into play. We’ve already built much of the framework we need to make this happen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | template <typename Head, typename... TailArgs> struct tuple<Head, TailArgs...> : private tuple<TailArgs...> { /** * \brief Recursive constructor **/ tuple(const Head& head, const TailArgs&... tailArgs) : tuple<TailArgs...>(tailArgs...), mHead(head) { } /** * \brief Get a tuple argument **/ template <int N> typename elementType<N, Head, TailArgs...>::type get() const { return getValueFromTuple<N>::template getValue<typename elementType<N, Head, TailArgs...>::type, Head, TailArgs...>(*this); } /** * \brief Get the head value **/ Head head() const { return mHead; } /** * \brief Get the next argument in the chain **/ const tuple<TailArgs...>& next() const { return *this; } private: Head mHead; ///< Tuple head }; // let's use our new get function! tuple<int, char, bool, double> t1(1, '2', true, 3.14159); char c = t1.get<1>(); // template get function where the index specified returns that parameter of the tuple class |
The first function we’ve added is the tuple’s get function, which can be seen being used at line 37. So how the %@*! does that work anyway?
Our new class member template function works pretty much the same as Boost’s tuple library (although much simpler). All we’ve done is defined a template function that takes an integer as a template parameter, and we’ve used our elementType struct, which we’ve already seen from earlier in the article, to set the return type of the function to whatever type is at that index in the tuple’s parameter pack!
We are missing some pretty important code though. The get function then calls the getValue function, which we’ve yet to define:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | template <int N> struct getValueFromTuple { template <typename ReturnType, typename Head, typename... TailArgs> static ReturnType getValue(tuple<Head, TailArgs...>& t) { return getValueFromTuple<N - 1>::template getValue<ReturnType, TailArgs...>(t.next()); } template <typename ReturnType, typename Head, typename... TailArgs> static const ReturnType getValue(const tuple<Head, TailArgs...>& t) { return getValueFromTuple<N - 1>::template getValue<ReturnType, TailArgs...>(t.next()); } }; // termination condition for when getValueFromTuple is instantiated with zero template <> struct getValueFromTuple<0> { template <typename ReturnType, typename... Args> static ReturnType getValue(tuple<Args...>& t) { return t.head(); } template <typename ReturnType, typename... Args> static const ReturnType getValue(const tuple<Args...>& t) { return t.head(); } }; |
This might look a bit complicated, but there’s absolutely nothing here we haven’t seen before. We define a template struct called getValueFromTuple that takes an integer as a template parameter. This is done so that we can partially specialize the struct so we have a termination condition for the function we actually need (getValue). C++ function templates are a bit more limited than class templates in how we can partially specialize them — specific specializations by constants are not allowed, so we use this struct wrapping trick to give us what we need.
The getValue function is also nothing new. It does the exact same recursive unrolling that the elementType struct uses to extract a specific parameter by index. The function recursively instantiates itself until N reaches zero and the termination condition is called. The trick here is that getValue calls the tuple class’ next() function during each instantiation, which passes the tuple’s base class to the next instantiation of the function. All we’re doing here is walking the “list” of parameters passed to the tuple’s constructor, we just happen to be doing it by walking the class hierarchy.
Let’s take a quick look at the tuple’s next() function to see how this works:
const tuple<TailArgs...>& next() const { return *this; }
This is how we trick the compiler into returning its base class — which has an mHead that contains the next parameter in the pack. The return type is the same type that the base class has, so when we return ourselves through the this pointer the compiler does the right thing and casts down to the base class.
So there we go: a fully functioning variadic tuple class that stores its constructor parameters and gives us a way to get at them by their index! We can now do this:
tuple<int, char, string, double> t1(1, '2', "variadics rule", 3.14159); int var0 = t1.get<0>(); // var0 is now 1 char var1 = t1.get<1>(); // var1 is '2' string var2 = t1.get<2>(); // var2 is now "variadics rule" double var3 = t1.get<3>(); // var3 is now 3.14159
Sweet! Of course there are many improvements that could be made such as allowing access to elements by reference (for efficiently storing and retrieving large structures), adding iterators, copy constructors, operator overrides, etc, but I’ll leave that stuff as reader exercises
.
For the next installment the plan is to go even deeper down the the metaprogramming hole, and see what kind of compile-time fun we can have. Template metaprogramming is a really useful skill to have; the more you can do at compile time, the fewer instructions need to get executed at runtime, plus, as we’ve just seen in this article, metaprogramming trickery can make other code more readable and, in turn, easier to maintain. Not only that but playing around with template metaprogramming is a great way to get to know your compiler, and the C++ language!
