Uses of Variadic Class Templates
Function templates can really benefit from the use of variadic template parameters because of the tie-in with variadic function parameters, but class templates can benefit too. Just as with function templates, the benefit comes in when you need a list of types but class templates support default template parameters. You've been able to fake it in the past by specifying a reasonably-sized maximum number of parameters and defaulting all the parameters to a dummy type. The key change with variadic templates is therefore the lifting of the arbitrary limit. It also makes the templates easier to write. For example, boost::variant
allows you to declare a variable that can be an instance of one of several types. For instance, boost::variant<int,std::string>
can either hold an int or a
In the current release of boost, you can specify up to 20 types in the list through the use of the boost preprocessor and metaprogramming libraries. With variadic templates, this could be unbounded, allowing you to declare a variable that can hold an instance of one of 100 types. It also simplifies the code as you no longer need to rely on preprocessor and metaprogramming tricks.
A similar case is std::tuple, except it holds an instance for each entry in the list rather than the either/or choice of boost::variant. You can write a simple tuple class quite easily. First, declare that simple_tuple is a variadic template without specifying any of the details.
template<typename ... Types>
Then you specialize it for an empty list: if there aren't any types in the list, then you haven't got any values. So it's just an empty class.
Now you can specialize for a list of at least one element by recursion. A list of N elements is a single element plus a list of N-1 elements.
template<typename First,typename ... Rest>
simple_tuple(First const& f,Rest const& ... rest):
First const& head() const
simple_tuple<Rest...> const& rest() const
The simple accessor functions allow you to get at the data:
- To access the first element of a tuple, you can call t.head().
- To access the second, you call t.rest().head().
- To access the third, you call t.rest().rest().head()
- And so forth
This works, but it's a bit unwieldy, which is why std::tuple has a helpful get() function to retrieve a numbered element. You can write a get_tuple_entry() function for your tuple too (see Listing 1).
In order to obtain the type and value of the N-th element of your simple tuple, you need to use a helper class (simple_tuple_entry) because you cannot partially specialize a function template. The get_tuple_entry function itself just passes everything on to simple_tuple_entry, either to retrieve the type of the entry or to retrieve the value itself.
The simple_tuple_entry class again has two specializations. The first is for the 0-th element, which is therefore first in the list and corresponds to the head() function for the tuple. If your index is not zero, you still need a list of at least one element. In this case, you discard the first element and find the (index-1)-th element of the rest() of your original tuple.
This get_tuple_entry function makes element accesses much easier: you can just say get_tuple_entry<5>(t) to get the sixth element of your tuple. The following simple code will thus output "42,a,3.141":
Of course std::tuple has a lot more features and is correspondingly more complex, but the basis is still something similar to this approach.
Power to the Templates
Templates are one of the most powerful features in C++, and variadic templates make them even more powerful. They are used in many places in the C++0x standard library to enable passing of arbitrary numbers of function arguments in places such as std::thread
, and to allow building tuples of arbitrary size with
. Variadic templates can greatly simplify the writing of type-safe code with variable numbers of arguments.