Variadic template arguments in C++

We’ve had varargs forever in C/C++ but variadic templates allow us to use strongly types variadic arguments.

We’re going to create a simple container (which just wraps a vector) and then extend this with variadic template arguments

template<typename T>
class container
{
   std::vector<T> _values;
public:
};

Now we want to add a push_back method which allows us to pass in multiple values, so the code would look like this

container<int> c;
c.push_back(1, 2, 3);

If we declare a public method which looks like this

template<typename ...Args>
void push_back(Args... args)
{
}

we can see the …Args in the template, these are variadic template arguments and you can see from the push_back parameters how we might use this.

But how do we get the variables 1, 2 & 3 that were passed as arguments? Unfortunately these are not translated to an array or similar, but we can use recursion and a change in our method signature to solve this.

Here’s the code

template<typename ...Args>
void push_back(T v, Args... args)
{
   _values.push_back(v);
   push_back(args...);
}

With this the calling code c.push_back(1, 2, 3); is actually passing to the push_back method the equivalent of push_back(int, variable arguments), so the first item is assign to argument v and the 2 & 3 to to args. Now using recursion we can “unpack” the args with the call push_back(args…), however this will not compiler because at some point the variable args will be empty, i.e. there’s no push_back method which takes no arguments. So we need to add the followin

static void push_back() {}

as a private method and this acts as a recursive terminator method. i.e. called when no arguments exist and causes the recursion to complete and allows the stack to unwind.

Ofcourse an alternative to variable arguments is to use the std::initializer_list (as discussed in a previous post), so the calling code might now look like this

container<int> c;
c.push_back({ 1, 2, 3 });

From our example class, remove the push_back methods and replace with

void push_back(std::initializer_list<T> il)
{
   for(auto it = il.begin(); it != il.end(); ++it)
   {
      _values.push_back(*it);
   }
}

Obviously the variable template arguments looks a lot more natural but may not suit every use.

Note: The variadic argument templates are also used in C++ folding expressions, which I will discuss in a subsequent post.

Whilst the example container was handling a single generic type, we could (as you can see from the push_back methods) create methods that handle different types. Let’s assume we have a simple type_checker class, which outputs what each argument type is, we could write the following

class type_checker
{
   static void what() {}
public:
   template<typename ...Args>
   void what(int v, Args... args)
   {
      std::cout << "Int " << v << std::endl;
      what(args...);
   }

   template<typename ...Args>
   void what(std::string v, Args... args)
   {
      std::cout << "String " << v.c_str() << std::endl;
      what(args...);
   }
};

// calling code

type_checker c;
c.what(1, "Hello", 3);

Now we’re passing in heterogeneous data and depending upon the first argument type each time to choose the correct overload method. The variadic arguments are unpacked and again the correct overload will be called.

To understand this more clearly, the c.what(1, “Hello”, 3);
line of code in this example will call the what methods as follows.

The what(int, …) is called first followed by what(std::string, …) followed by what(int, …) and finally what().