Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Understanding the dots in variadic template function

Suppose I have the following piece of code. Which I understand to a large extent.

template <class ...Args>               //---> Statement A
void foo_imp(const Args&... args)      //---> Statement B
{
    std::vector<int> vec = {args...};  //---> Statement C
}

Now I am a bit confused as to where the ... should appear before or after the name of the variable. Here is why I am confused.

The statement A, given below

template <class ...Args>  

suggests that ... is the variable type and Args is the variable name (which is fine)

The statement B, given below

void foo_imp(const Args&... args) 

This seems to me like the type is Args which was the template type however the addition of ... after Args confuses me?

and then assigning the value to vector is further confusing

 std::vector<int> vec = { args... };

why are there ... after args ?

Any ideas on how I could remember the above code so that it would make sense to me ?

like image 537
Rajeshwar Avatar asked May 06 '14 03:05

Rajeshwar


1 Answers

<class ...Args> declares Args to be a variadic parameter pack where the (meta-)type of each parameter is class. For the sake of this explanation, let's suppose this can be thought of as similar to:

<class Args1, class Args2, class Args3, etc.>

Variadic type parameter packs can appear wherever lists of types may appear, but must be expanded with .... So for example void foo_imp(Args) is not legal, but

void foo_imp(Args...)

would be equivalent to

void foo_imp(Args1, Args2, Args3, etc.) 

The ... expands the thing to its left, using the values of the parameter pack. So in your actual example,

void foo_imp(const Args&...)    

would be equivalent to

void foo_imp(const Args1&, const Args2&, const Args3&, etc.)  

Now it is time to abandon the idea of the template parameter pack as a fixed sized list of types; in actuality, its length is determined by the number of arguments supplied at call time.

"BUT WE DON'T SUPPLY ANY TEMPLATE ARGUMENTS AT CALL TIME!?!" I hear you say. This is because foo_imp is a function whose template arguments are deduced on the basis of its normal arguments. This means that the length of the (const Args1&, const Args2&, const Args3&) list is determined by the function call, and each ArgsN type is determined by the normal rules for function template type deduction.


Now on to args. Similarly to Args, args is a variadic parameter pack, and so must be expanded whenever it is used.

std::vector<int> vec = {args...};

is equivalent to (using our explanatory naming system from earlier)

std::vector<int> vec = {args1, args2, args3, etc.};

Variadic parameter packs do not automatically expand themselves, because they can be expanded as part of larger expressions, and so automatic expansion could be ambiguous or incorrect. For example:

std::forward<Args>(args)...
//expands as:
std::forward<Args1>(args1), std::forward<Args2>(args2), std::forward<Args3>(args3), etc.

is able to work because it individually forwards each argument. If it were expanded as

std::forward<Args...>(args...)
//expands as:
std::forward<Args1, Args2, Args3, etc.>(args1, args2, args3, etc)    

it wouldn't even compile, because std::forward only takes a single parameter.


In conclusion. If ... appears to the right of an expression containing any variadic parameter packs, it expands that expression as a list, with each element of the list having the nth value from every contained parameter pack in place of that parameter pack.

//Foo and bar are variadic parameter packs
Foo...                     => Foo1, Foo2, Foo3, etc
std::vector<Foo>...        => std::vector<Foo1>, std::vector<Foo2>, std::vector<Foo3>, etc.
std::tuple<Foo...>(bar...) => std::tuple<Foo1, Foo2, Foo3, etc>(bar1, bar2, bar3, etc)
&bar...                    => &bar1, &bar2, &bar3, etc

If such an expanded expression appears in a parameter list, it can be captured by a variadic parameter pack variable. In this case, if the expression does not contain any variadic parameter packs, the parameter list is freed to take an arbitrary number of parameters.

template<typename ...Baz> => Baz is a variable length variadic parameter pack of `typename`s
void bar(Baz ...baz)      => baz is a parameter pack of `Baz`s
void bar(Baz &&...baz)    => baz is a parameter pack of `Baz&&`s
like image 177
Mankarse Avatar answered Oct 05 '22 22:10

Mankarse