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

Post by Nico Brailovsky @ 2011-04-19 | Permalink | 1 comments | Leave a comment

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?

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 
#include 
// 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:

#include 
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  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)


In reply to this post, Matthew Fioravante commented @ 2015-09-03T22:38:03.000+02:00:

"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...

Original published here.