2.3.3 Variadic Templates

What was the problem

Let’s design a some kind of message dispatcher using features available before C++11. The use case is presented by the code below.

// Some functions that will be called by our dispatcher 
void on_event(); 
void also_on_event(); 
void on_message(const char* message); 
void also_on_message(const char* message); 
void on_long_message(const char* message_1, const char* message_2); 
void also_on_long_message(const char* message_1, const char* message_2); 
 
int main() 
{ 
  // A dispatcher with no arguments and two scheduled functions. 
  dispatcher<> zero; 
  zero.queue(&on_event); 
  zero.queue(&also_on_event); 
 
  // A dispatcher with one argument and two scheduled functions. 
  dispatcher<const char*> one; 
  one.queue(&on_message); 
  one.queue(&also_on_message); 
 
  // A dispatcher with two argument and two scheduled functions. 
  dispatcher<const char*, const char*> two; 
  two.queue(&on_long_message); 
  two.queue(&also_on_long_message); 
 
  // Now we trigger the calls with the given arguments. 
  zero.dispatch(); 
  one.dispatch("hello"); 
  two.dispatch("hello", "world"); 
 
  return 0; 
}

The idea is to have a dispatcher whose role is to store functions to be called with arguments provided later. It is parameterized by the type of the arguments. If we want to accept any kind of function, we must accept from zero to n arguments.

A typical solution for that would have been to declare the type with multiple template parameters and to default these parameters to a type representing the state of not being used. Then the actual implementation would have been selected by template specialization of a super type. In order to keep this example short we are going to limit our implementation to functions with up to two arguments.

Here is the dispatcher with the defaulted parameters.

template<typename Arg1 = void, typename Arg2 = void> 
struct dispatcher: dispatcher_impl<Arg1, Arg2> 
{};

Let’s have a look at the dispatcher implementation with two arguments.

template<typename Arg1, typename Arg2> 
struct dispatcher_impl 
{ 
  typedef void (*function_type)(Arg1, Arg2); 
 
  void queue(function_type f) 
  { 
    m_scheduled.push_back(f); 
  } 
 
  template<typename A1, typename A2> 
  void dispatch(A1 a1, A2 a2) 
  { 
    const std::size_t count = m_scheduled.size(); 
 
    for (std::size_t i(0); i != count; ++i) 
      m_scheduled[i](a1, a2); 
  } 
 
private: 
  // The functions that will be called on the next dispatch. 
  std::vector<function_type> m_scheduled; 
};

Nothing special here. Let’s look at the implementation with one argument.

template<typename Arg> 
struct dispatcher_impl<Arg, void> 
{ 
  typedef void (*function_type)(Arg); 
 
  void queue(function_type f) 
  { 
    m_scheduled.push_back(f); 
  } 
 
  template<typename A> 
  void dispatch(A a) 
  { 
    const std::size_t count = m_scheduled.size(); 
 
    for (std::size_t i(0); i != count; ++i) 
      m_scheduled[i](a); 
  } 
 
private: 
  // The functions that will be called on the next dispatch. 
  std::vector<function_type> m_scheduled; 
};

This is very similar to the previous implementation. We could factorize some parts (queue() and m_scheduled) by moving them in a parent class parameterized with function_type, but for now let’s just keep it as is.

The dispatch() function cannot be factorized though, as its signature and the m_scheduled[i](/*args*/) line is specific to each version.

The implementation with no arguments is equivalently similar, so I won’t include it here. I think you got the point: this is a lot of code, redundant code, for a feature limited to only three use cases amongst many. Wouldn’t it be better if we could put all of that in a single implementation that would handle any number of arguments?

How the Problem is Solved

The variadic template syntax introduced in C++11 provides a solution to this problem.

// The template accepts any number of arguments.
template<typename... Args>
struct dispatcher 
{ 
  // We can list the template parameters by appending ’...’. 
  typedef void (*function_type)(Args...); 
 
  void queue(function_type f) 
  { 
    m_scheduled.push_back(f); 
  } 
 
  // args represent any sequence of arguments here. 
  template<typename... A> 
  void dispatch(A&&... args) 
  { 
    const std::size_t count = m_scheduled.size(); 
 
    // Then we can list the arguments by appending ’...’ 
    // to the function argument. 
    for (std::size_t i(0); i != count; ++i) 
      m_scheduled[i](args...); 
  } 
 
private: 
  std::vector<function_type> m_scheduled; 
};

Here we have a single implementation accepting any number of arguments, and thus covering all use cases from the previous implementation and more.

The syntax of typename... Args defines a parameter pack. This is like a template parameter but with an ellipsis.

A template with a parameter pack is called a variadic template. Note that it is allowed to mix a parameter pack with non-pack template arguments, as in template<typename Head, typename... Tail>.

When an expression containing a parameter pack ends with an ellipsis, it is replaced by a comma-separated repetition of the same expression applied to each type from the pack. For example:

template<typename F, typename... Args) 
void invoke(F&& f, Args&&... args) 
{ 
  // this is equivalent to 
  //   f(std::forward<Args0>(args_0), 
  //     std::forward<Args1>(args_1), 
  //     etc); 
  f(std::forward<Args>(args)...); 
}

Or, for another example, here is a function creating an array of the names of the types passed as template parameters:

template<typename... T> 
void collect_type_names() 
{ 
  const char* names[] = {typeid(T).name()...}; 
  // }