105

In C++11 there are variadic templates like this one:

template< class T, class... Args >
unique_ptr<T> make_unique( Args&&... args )
{
    return unique_ptr<T>(new T(std::forward<Args>(args)...));
}

There are some curiosities about this: The expression std::forward<Args>(args)... uses both Args and args but only one ... token. Furthermore std::forward is a non-variadic template function taking only one template parameter and one argument. What are the syntax rules for that (roughly)? How can it be generalized?

Also: In the function implementation the ellipsis (...) is at the end of the expression of interest. Is there a reason that in the template argument list and the parameter list the ellipsis is in the middle?

Ralph Tandetzky
  • 22,780
  • 11
  • 73
  • 120
  • 2
    Briefly on the second part: When "declaring" a template parameter pack or function parameter pack, the `...` comes before the identifier being introduced. When using either or both types of packs, the `...` comes after the expression pattern to expand. – aschepler Jul 15 '13 at 13:15

2 Answers2

108

In the context of variadic template, the ellipsis ... is used to either pack or unpack parameters/arguments in a template definition, depending on the relative position where it appears:

  • it unpacks the template parameter pack if it appears on the right side of an expression (call this expression pattern for a moment)
  • or it's a pack argument if it appears on left side of the name:
...thing  // pack   : appears as template arguments
thing...  // unpack : appears when consuming the arguments

The rule is that whatever pattern is on the left side of ... is repeated — the unpacked patterns (call them expressions now) are separated by comma ,.

It can be best understood by some examples. Suppose you have this function template:

template<typename ...T> //pack
void f(T ... args)      //pack
{
   // here are unpack patterns

   g( args... );        //pattern = args
   h( x(args)... );     //pattern = x(args)
   m( y(args...) );     //pattern = args (as argument to y())
   n( z<T>(args)... );  //pattern = z<T>(args)
}

Now if I call this function passing T as {int, char, short}, then each of the function call is expanded as:

g( arg0, arg1, arg2 );           
h( x(arg0), x(arg1), x(arg2) );
m( y(arg0, arg1, arg2) );
n( z<int>(arg0), z<char>(arg1), z<short>(arg2) );

In the code you posted, std::forward follows the fourth pattern illustrated by n() function call.

Note the difference between x(args)... and y(args...) above!


You can use ... to initialize an array also as:

struct data_info
{
     boost::any  data;
     std::size_t type_size;
};

std::vector<data_info> v{{args, sizeof(T)}...}; //pattern = {args, sizeof(T)}

which is expanded to this:

std::vector<data_info> v 
{ 
   {arg0, sizeof(int)},
   {arg1, sizeof(char)},
   {arg2, sizeof(short)}
};

I just realized a pattern could even include access specifier such as public, as shown in the following example:

template<typename ... Mixins>
struct mixture : public Mixins ...  //pattern = public Mixins
{
    //code
};

In this example, the pattern is expanded as:

struct mixture__instantiated : public Mixin0, public Mixin1, .. public MixinN  

That is, mixture derives publicly from all the base classes.

starball
  • 20,030
  • 7
  • 43
  • 238
Nawaz
  • 353,942
  • 115
  • 666
  • 851
51

The following is taken from the talk "Variadic Templates are Funadic" by Andrei Alexandrescu at GoingNative 2012. I can recommend it for a good introduction on variadic templates.


There are two things one can do with a variadic pack. It's possible to apply sizeof...(vs) to get the number of elements and expand it.

Expansion rules

Use            Expansion

Ts...          T1, ..., Tn
Ts&&...        T1&&, ..., Tn&&
x<Ts,Y>::z...  x<T1,Y>::z, ..., x<Tn,Y>::z
x<Ts&,Us>...   x<T1&,U1>, ..., x<Tn&,Un>
func(5,vs)...  func(5,v1), ..., func(5,vn)

Expansion proceeds inwards outwards. When expanding two lists in lock-step, they have to have the same size.

More examples:

gun(A<Ts...>::hun(vs)...);

Expands all Ts in the template argument list of A and then the function hun gets expanded with all vs.

gun(A<Ts...>::hun(vs...));

Expands all Ts in the template argument list of A and all vs as the function arguments for hun.

gun(A<Ts>::hun(vs)...);

Expands the function hun with Ts and vs in lock-step.

Note:

Ts is not a type and vs is not a value! They are aliases for a list of types/values. Either list may be potentially empty. Both obey only specific actions. So the following is not possible:

typedef Ts MyList;  // error!
Ts var;             // error!
auto copy = vs;     // error!

Expansion loci

Function arguments

template <typename... Ts>
void fun(Ts... vs)

Initializer lists

any a[] = { vs... };

Base specifiers

template <typename... Ts>
struct C : Ts... {};
template <typename... Ts>
struct D : Box<Ts>... { /**/ };

Member initializer lists

// Inside struct D
template <typename... Us>
D(Us... vs) : Box<Ts>(vs)... {}

Tempate argument lists

std::map<Ts...> m;

Will only compile if there is a possible match for the arguments.

Capture lists

template <class... Ts> void fun(Ts... vs) {
    auto g = [&vs...] { return gun(vs...); }
    g();
}

Attribute lists

struct [[ Ts... ]] IAmFromTheFuture {};

It is in the specification, but there is no attribute that can be expressed as a type, yet.

Toby Speight
  • 27,591
  • 48
  • 66
  • 103
typ1232
  • 5,535
  • 6
  • 35
  • 51