What are the rules for the "..." token in the context of variadic templates?

C++C++11Variadic Templates

C++ Problem Overview


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?

C++ Solutions


Solution 1 - C++

In the context of variadic template, the ellipsis ... is used to unpack 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.

Hope that helps.

Solution 2 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRalph TandetzkyView Question on Stackoverflow
Solution 1 - C++NawazView Answer on Stackoverflow
Solution 2 - C++typ1232View Answer on Stackoverflow