<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