I am trying to learn variadic templates and functions. I can't understand why this code doesn't compile:
template<typename T>
static void bar(T t) {}
template<typename... Args>
static void foo2(Args... args)
{
(bar(args)...);
}
int main()
{
foo2(1, 2, 3, "3");
return 0;
}
When I compile it fails with the error:
Error C3520: 'args': parameter pack must be expanded in this context
(in function foo2
).
You can use
make_tuple
for pack expansion as it introduces a context where the,
sequence produced by an expansion is validNow, I suspect the unused/unnamed/temporary tuple of zeroes that's produced is detactable by the compiler and optimized away
Demo
This is a full example, based on the answers here.
Example to reproduce
console.log
as seen in JavaScript:Filename e.g.
js_console.h
:SHAMELESS COPY [approved by its source]
Parameter packs can only be expanded in a strictly-defined list of contexts, and operator
,
is not one of them. In other words, it's not possible to use pack expansion to generate an expression consisting of a series of subexpressions delimited by operator,
.The rule of thumb is "Expansion can generate a list of
,
-separated patterns where,
is a list delimiter." Operator,
does not construct a list in the grammar sense.To call a function for each argument, you can use recursion (which is the primary tool in the variadic template programmer's box):
USEFUL NON-COPIED INFO
Another thing you probably haven't seen in this answer is use of the
&&
specifier andstd::forward
. In C++, the&&
specifier can mean one of 2 things: rvalue-references, or universal references.I won't go into rvalue-references, but to somebody working with variadic templates; universal references are a god-send.
Perfect Forwarding
One of the uses of
std::forward
and universal references are perfect forwarding of types to other functions.In your example, if we pass an
int&
tofoo2
it will be automatically demoted toint
because of the signature of the generatedfoo2
function after template deduction and if you wanted to then forward thisarg
to another function that would modify it ny reference, you will get undesired results (the variable won't be changed) becausefoo2
will be passing a reference to the temporary created by passing anint
to it. To get around this, we specify a forwarding function to take any type of reference to a variable (rvalue or lvalue). Then, to be sure that we pass the exact type passed in the forwarding function we usestd::forward
, then and only then do we allow the demoting of types; because we are now at the point where it matters most.If you need to, read more on universal references and perfect forwarding; scott meyers is pretty great as a resource.
One of the places where a pack expansion can occur is inside a braced-init-list. You can take advantage of this by putting the expansion inside the initializer list of a dummy array:
To explain the content of the initializer in more detail:
Demo.
An important advantage of expanding in
{}
is that it guarantees left-to-right evaluation.With C++1z fold expressions, you can just write
The execution order is not guaranteed for this!
In this example parameters will be printed in reverse order at least with GCC.
Parameter packs can only be expanded in a strictly-defined list of contexts, and operator
,
is not one of them. In other words, it's not possible to use pack expansion to generate an expression consisting of a series of subexpressions delimited by operator,
.The rule of thumb is "Expansion can generate a list of
,
-separated patterns where,
is a list delimiter." Operator,
does not construct a list in the grammar sense.To call a function for each argument, you can use recursion (which is the primary tool in the variadic template programmer's box):
Live example