The C++11 standard introduced so called Variadic Templates. These have many uses, one of which is the ability to write functions that take any number of arguments without having to mess around with C-style non-type safe “var-args” and printf like format specifiers.
It has always been possible to emulate variadic functions in C++, without using C style var-args, by using function objects and chained function operator calls. This; however, was never a satisfactory solution for a number of reasons:
- The calling syntax is unnatural (it doesn’t look like a standard function call)
- You have to write boiler-plate code in the guise of a function object (functor)
- You have to cache arguments if you can’t handle them discretely (one at a time)
- In which case arguments generally need converting to a single type (eg. string)
- It required multiple function calls, which are not cost free in terms of performance
- Overall, it was a clumsy solution to a problem
The standard istream and ostream are a good example of emulating variadic arguments. The istream is basically a functor object that does the same job as scanf and the ostream is a functor that does the same thing as printf. In each case, you use the stream operator to invoke the input of output functionality of the stream and you can chain these together to emulate variadic arguments.
Consider the following:
#include #include using namespace std; int main() { printf("%s%d%c%.2fn", "hello", 1, 'c', 56.23f); cout << "hello" << 1 << 'c' << 56.23f << std::endl; }
Notice that line 9 is actually a number of chained calls to the streaming operator on the cout stream object? This is semantically identical to the variadic arguments of the printf function call before it. Of course, this is a contrived example but it serves to demonstrate the point. There is, of course, nothing to stop the determined C++03 programmer from creating their own function object class that supports a similar chaining syntax, either by overloading the streaming operator or, as is often the case, the function operator.
#include #include using namespace std; struct mystream_t { template mystream_t & operator ()(T const & t) { // cout used here by way of demonstration cout << t; return *this; } }; mystream_t mystream; int main() { printf("%s%d%c%.2fn", "hello", 1, 'c', 56.23f); mystream("hello")(1)('c')(56.23f)('n'); }
As you can see this is a bit of a poor substitute for real variadic functions, so let’s see how we’d do this in C++11.
#include #include using namespace std; // This is the terminating template template void variadic_func(Arg const & arg) { std::cout << arg; } // This is the variadic args handling template template void variadic_func(Arg const & arg, Args const & ... args) { variadic_func(arg); variadic_func(args...); } int main() { printf("%s%d%c%.2fn", "hello", 1, 'c', 56.23f); variadic_func("hello", 1, 'c', 56.23f, 'n'); }
At this point you are probably looking at this and wondering how on earth this is any better than the previous example? Well, it is. It has many advantages over the chained function call:
- The calling syntax is natural (it is just a standard function call)
- No boiler-plate code, just your single arg function and the args wrapper version
- No need to cache arguments as the “parameter pack” handles that for you
- No need to convert arguments into a single type (such as a string)
- It’s properly type safe, since each argument is handled as the correct type
- It’s (mostly) simple, once you understand variadic templates is easy to follow
Ok, so how does it work?
Firstly, you’ll notice there are actaully two versions of the function, one that takes two arguments and one that takes only one argument. Actually, the one that seems to take two argument actually takes two or more arguments. You see the second argument is a so called “parameter pack“. When you call the function with just a single argument it will call the first form. When you call the function with multiple arguments it will call the second form. The second for receives one argument and a parameter pack, which is a special argument that contains all the other arguments as a single entity.
Now, you can’t access these other arguments directly so to resolve them we use a little bit of recursion trickery. Firstly, we handle the single argument by calling the single argument form of the function, we then call the multiple argument for of the function and only pass it the parameter pack. The function is called again and this time the next item in the argument pack is picked off as the single argument and the rest form the now smaller parameter pack. This repeats until there is only one argument left in the parameter pack, which means the recursion ends because the multiple argument form of the function is no longer called.
This is easier to understand if we walk through what is happened (and once you get it I promise it makes perfect sense and is very easy to understand). Let’s go through it step by step:
variadic_func<Arg, Args> is called with "hello", 1, 'c', 56.23f, 'n' arg = "hello" args = { 1, 'c', 56.23f, 'n' } variadic_func is called with "hello" variadic_func<Arg, Args> is called with 1, 'c', 56.23f, 'n' arg = 1 args = { 'c', 56.23f, 'n' } variadic_func is called with 1 variadic_func<Arg, Args> is called with 'c', 56.23f, 'n' arg = 'c' args = { 56.23f, 'n' } variadic_func is called with 'c' variadic_func<Arg, Args> is called with 56.23f, 'n' arg = 56.23f args = { 'n' } variadic_func is called with 'c' variadic_func is called with 'n'
So, as you can see, the variadic_func function just keeps calling itself until it runs out of arguments in the parameter pack, at which point the final call is to the overload that only takes a single argument and at that point the recursion ends. Of course, we still have the problem of multiple function calls but because this is all unravelled by the template compiler before the C++ code compiler kicks in the chances are the resultant code will be inlined.
Variadic templates are not just available to functions, they can also be used with classes, too. In fact, the C++11 std::tuple is a great example. In a future article I’ll explore how you can use variadic templates with classes.