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

2

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?

Dimitri answered 15/7, 2013 at 10:46 Comment(1)
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.Davy
C
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.

Complaint answered 15/7, 2013 at 11:0 Comment(9)
Regarding the expression that is matched; It should be the largest possible expression, should it not? For instance x+args... should be expanded to x+arg0,x+arg1,x+arg2, not x+arg0,arg1,arg2.Pes
So the ... applies to every expandable entity in the pattern.Zygapophysis
@bitmask: Yes. x+args... should expand to x+arg0,x+arg1,x+arg2, not x+arg0,arg1,arg2.Complaint
C++1z introduces (x + ... + args) for x + arg0 + arg1 + arg2.Elemental
@Jarod42: I'll update this answer once C++17 is released.Complaint
Additionally, you can even call a function on each element of a pack: int a[sizeof...(T)] = {(my_foreach_f(args), 0)...};Clie
@synther: sizeof...(T) is not needed there. You can simply write : int a[] = { ___ };Complaint
@Nawaz Need update for Folding expressions in C++17 ? as per Jarod42 exampleReservation
@P0W: Will update it once C++17 is officially released.Complaint
C
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.

Ctesiphon answered 15/7, 2013 at 11:46 Comment(3)
Nice. Function arguments are left out of the loci, though :PMinatory
@Minatory Thanks. The expansion in the function argument list was not mentioned.Ctesiphon
@Ctesiphon Sorry for the edit, but I felt that your original post was too close to plagiarism. Btw, I also watched that talk some time ago -- awesome.Imperceptible

© 2022 - 2024 — McMap. All rights reserved.