devxlogo

An Introduction to Variadic Templates in C++0x

An Introduction to Variadic Templates in C++0x

f you’ve been programming in C++ for any time at all then you’re familiar with variadic functions, functions (such as printf) that can take a variable number of arguments. C99 introduced variadic macros, which also take a variable number of arguments. C++0x takes this concept a step further with the introduction of variadic templates, where the number of template arguments is not specified when you write the template.

You declare the variadic part of a variadic template with an ellipsis (…) just like with a variadic function, though in this case it goes in the template parameter list:

templateclass my_class{};

You can then specify the arguments when you use the template. This approach is the same one you would use for a normal template, except that you can specify as many or as few arguments as you like:

my_class mc1;my_class mc2;

Just like with variadic functions, you don’t even have to pass any arguments:

my_class mc3;

You can have other non-variadic template parameters too. After arguments have been allocated to the non-variadic parameters, the remainders constitute the parameter pack for the variadic parameter:

templateclass x{};x x1; // Args is x<:string> > x2; // Args is empty

Type-safe Variadic Functions

Variadic template parameters aren’t restricted to class templates; you can use them with function templates too. In fact, one of the most powerful uses of variadic template parameters is with function templates, where they combine with automatic template parameter type deduction to provide type-safe variadic functions. For example, you could use a variadic function template to print a comma-separated list of arbitrary values:

templatevoid print_comma_separated_list(T value){    std::coutvoid print_comma_separated_list(First first,Rest ... rest){    std::cout

You can then call this with a list of values of any type that can be written to std::cout:

print_comma_separated_list(42,"hello",2.3,'a');

How does this work? If there's only one element, you should just print that on it's own with a trailing newline. You take care of that with the first overload. If you've got more than one element to print, then you need to separate them with commas. That's where the variadic function template comes in.

The use of the ellipsis in the declaration of the function parameter "rest" is what's called a pack expansion, which means that "rest" is actually a pack of function parameters. It has one parameter for each element in the template parameter pack "Rest".

The ellipsis in the recursive call expands "rest" into a series of values, which are passed as normal arguments to the recursive call. This works both ways: if you pass more than one argument to print_comma_separated_list, the first argument is used to deduce "First" and "Rest" is deduced from the remaining arguments. Thus, in this code:

print_comma_separated_list(42,"hello",2.3,'a');

"First" is deduced to be int, and "Rest" is deduced to be .

So far, so good. Now let's move on to the body of the function.

The Body of the Function

The body of the function is actually relatively straightforward: you print out the first item in the list, followed by a comma, and then make a recursive call to print the rest of the list. The recursive call uses another pack expansion to pass the rest of the elements as individual arguments. Because the first argument had its own template parameter, it is not part of the variadic pack and it will not be included, which is exactly the behavior you're after. The sample call in the previous section will thus cause the following recursive calls:

print_comma_separated_list(42,"hello",2.3,'a');print_comma_separated_list("hello",2.3,'a');print_comma_separated_list(2.3,'a');print_comma_separated_list('a');

This final call will match the single-argument overload you defined at the beginning and will therefore terminate the recursion. Unfortunately, as written this call will copy all the parameters except the first with every recursive call. If you have twenty parameters then the last one will be copied twenty times!

Thankfully, you can avoid that by using rvalue references:

templatevoid print_comma_separated_list(T&& value){    std::coutvoid print_comma_separated_list(First&& first,Rest&& ... rest){    std::cout

In this case, the template type deduction rules mean that if lvalues (such as named variables) are passed to the function, then the template argument "First" or the corresponding element of "Rest" is deduced to be an lvalue reference. This reference is then passed down through the recursive calls rather than the values actually being copied. If an rvalue is passed (such as the constants in the previous examples), then the template parameters are deduced to be the plain type of the rvalue, and function arguments are bound by rvalue reference instead.

For the recursive calls, the parameters now refer to a named value (rest), and so are passed by lvalue reference. This is not a problem in this example, but if the actual operation being performed in place of the stream insertion depended on the lvalue/rvalue-ness of the parameters, then you could preserve this with std::forward:

print_comma_separated_list(std::forward(rest)...);

This and the rvalue-reference parameter declaration show an additional feature of parameter packs: you can expand an expression for each item in the pack by placing the ellipsis at the end of the sub-expression that should be expanded. The std::forward example above is equivalent to:

print_comma_separated_list(std::forward(rest1),    std::forward(rest2),    std::forward(rest3),    ...     std::forward(restN));

Where the X suffix indicates the Xth element of the parameter pack. You can extend this to arbitrarily complex expressions. The key point is that any expression that uses a template parameter pack or a function argument pack must also use the ellipsis to expand that pack.

In summary, you can use pack expansions in template instantiations:

  • As part of the template argument list
  • In function call expressions as part of the function argument list
  • As part of a brace-enclosed initializer list
  • In the base class list of a class definition or member initializer list for a constructor

How Many Elements Are There in a Parameter Pack?

There is one more special feature associated with variadic templates: the sizeof... operator. Whereas the normal sizeof operator gives you the size in bytes of a type or object, the sizeof... operator tells you how many values or types there are in a pack. This is the only time that a pack can appear in an expression without an ellipsis for the pack expansion. For example, the following function returns the number of supplied arguments:

templateunsigned how_many_args(Args ... args){    return sizeof...(args);}

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 can either hold an int or astd::string.

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.

templateclass simple_tuple;

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.

templateclass simple_tuple{};

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.

templateclass simple_tuple:    private simple_tuple{    First member;public:    simple_tuple(First const& f,Rest const& ... rest):        simple_tuple(rest...),        member(f)    {}    First const& head() const    {        return member;    }    simple_tuple const& rest() const    {        return *this;    }};

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(t) to get the sixth element of your tuple. The following simple code will thus output "42,a,3.141":

int main(){    simple_tuple st(42,'a',3.141);    std::cout(st)(st)(st)

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, std::bind and std::function, and to allow building tuples of arbitrary size withstd::tuple. Variadic templates can greatly simplify the writing of type-safe code with variable numbers of arguments.

devx-admin

Share the Post: