Cool C++0X features III: Variadic templates, a fix for varargs

Post by Nico Brailovsky @ 2011-04-26 | Permalink | Leave a comment

Last time we saw why a function with varargs may bring lots of problems. Then we saw how to solve it, but never explained why that last solution doesn't have the problems the varargs solution had, nor how does it work. Let's start by copying the solution here:

// Stop condition
void println() {}
// General case
template 
void println(H p, T... t)
{
   std::cout << p;
   println(t...);
}
int main() {
   println("Hola", " mundo ", 42, 'n';);
   return 0;
}

It certainly looks much better than the varargs function, even though some new strange syntax has been introduced. Keep in mind some template-foo is required, not only because of the syntax but because we'll be talking about functional programming too.

With all that intro (the last 2 articles were just an intro!) now we are in a good shape to ask what a variadic template really is. In its easiest form, it's just a list of template arguments, like this:

template  void foo(T... t) {}

That simple template can accept as many parameters as you need, of any type. This is much safer than a vararg because:

Pretty neat, huh? But how does it work? Variadic templates are actually very similar to how Haskell handles lists, you get all the arguments as a list of types in which you can either get the head or the tail. To do something useful, get the head and continue processing the tail recursively.

template 
void do_something(H h, T... t)
{
    // Do something useful with h
    really_do_something(h);
    // Continue processing the tail
    do_something(t...);
}

Of course, you'll eventually need a condition to stop processing: (we'll explain the new syntax later)

void do_something()
{
    // Do nothing :)
}

When the list is completely processed the empty do_something function will be called. Easy, right? But it does have a lot of weird syntax. Let's see what each of those ellipses mean:

With all that in mind, let's put together our typesafe printf:

// Condition to stop processing
void println() {}
// Println receives a list of arguments. We don't know it's type nor
// how many there are, so we just get the head and expand the rest
template 
void println(H p, T... t)
{
    // Do something useful with the head
    std::cout << p;
    // Expand the rest (pass it recursively to println)
    println(t...);
}
int main() {
    // See how it works even better than varargs?
   println("Hola", " mundo ", 42, 'n');
   return 0;
}

Next time, we'll see a more complex (and fun) example of variadic templates.