Cool C++0X features II, Variadic templates: What’s wrong with varargs

Last time we explained what variadic templates are. We’ll see what they can do now. We mentioned that solving the problem of having a type-safe varargs is one of the best ways of applying variadic templates, but what’s varargs?

cpp

Varargs functions (from C world, not even from C++!) are functions which have a variable number of arguments, just like printf. These are usually very dangerous functions, since they are not typesafe. Let’s see how they are implemented with an example:

#include <stdarg.h>
#include <iostream>

// My god, it's full of bugs
void va_println(int args_left, ...) {
   va_list arg_lst;
   va_start(arg_lst, args_left);

   while(args_left--) {
      const char *p = va_arg(arg_lst, const char*);
      std::cout << p;
   }

   va_end(arg_lst);
}

int main() {
   va_println(3, "Hola ", "mundo", "n");
   return 0;
}

This implementation of a function with variable arguments is, more or less, the best C can give us, yet it riddled with bugs and hidden problems. Let’s go one by one:

  • Arg num will get out of sync: You need to specify the list of args as well as how many you have. That WILL get out of sync. Trust me, it’s just a mater of time. And when it does, you’ll have a coredump.
  • Type-unsafe: You just tell varargs “Hey, get me an int”. And it will give you an int, no warranties included. If it was supposed to be a short instead, though luck, you end up with a coredump.
  • No, really, coredump: Where are so many coredumps coming from, you may ask. Easy, varargs it’s just a way of handling the stack. Calling va_arg just moves the stack pointer by the sizeof the datatype you requested. That means no compile-time checks are included.
  • No pod types: Remember POD types? Try running this code:
    #include <stdarg.h>
    
    struct X { virtual ~X(){} };
    
    void va_println(int args_left, ...) {
       va_list arg_lst;
       va_start(arg_lst, args_left);
    
       while(args_left--) {
          X *p = va_arg(arg_lst, X*);
       }
    
       va_end(arg_lst);
    }
    
    int main() {
       X x, y, z;
       va_println(3, x, y, z);
       return 0;
    }
    

And how do we fix it?

The fix is easy. Too easy. You just need C++0X. We will discuss why this is better next time, but just as a sneak peak:

void println() {}
template <typename H, typename... T> void println(H p, T... t) {
   std::cout << p;
   println(t...);
}

int main() {
   println("Hola", " mundo ", 42, 'n');
   return 0;
}

Remember to compile using -std=c++0x in gcc.

(Thanks Hugo Arregui for correcting the POD example)

Witchcraft

Advertisements

One Comment on “Cool C++0X features II, Variadic templates: What’s wrong with varargs”

  1. Matthew Fioravante says:

    “Type-unsafe: You just tell varargs “Hey, get me an int”. And it will give you an int, no warranties included. If it was supposed to be a short instead, though luck, you end up with a coredump.”

    While the concept is true in general, your exact example is not exactly right. Variadic functions perform integral promotion on all of the arguments. So if you pass a short you have to read it back out using va_arg(vl, int).

    If however you pass a short and then do va_arg(vl, short), you could be triggering undefined behavior.. Similarly with char and the unsigned variants which all get promoted to int.

    More reasons not to use variadic functions…


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s