template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
[](...){}((f(std::forward<Args>(args)), 0)...);
}
It was recently featured on isocpp.org without explanation.
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
[](...){}((f(std::forward<Args>(args)), 0)...);
}
It was recently featured on isocpp.org without explanation.
Actually it calls function
f
for each argument inargs
in unspecified order.create lambda function, that does nothing and receives arbitrary number of arguments (va args).
argument of lambda.
call
f
with forwarded argument, send0
to lambda.If you want specified order you can use following thing:
The short answer is "it does it not very well".
It invokes
f
on each of theargs...
, and discards the return value. But it does so in a way that leads to unexpected behavior in a number of cases, needlessly.The code has no ordering guarantees, and if the return value of
f
for a givenArg
has an overloadedoperator,
it can have unfortunate side effects.With some white space:
We will start from the inside.
f(std::forward<Args>(args))
is an incomplete statement that can be expanded with a...
. It will invokef
on one ofargs
when expanded. Call this statementINVOKE_F
.(INVOKE_F, 0)
takes the return value off(args)
, appliesoperator,
then0
. If the return value has no overrides, this discards the return value off(args)
and returns a0
. Call thisINVOKE_F_0
. Iff
returns a type with an overridenoperator,(int)
, bad things happen here, and if that operator returns a non-POD-esque type, you can get "conditionally supported" behavior later on.[](...){}
creates a lambda that takes C-style variadics as its only argument. This isn't the same as C++11 parameter packs, or C++14 variadic lambdas. It is possibly illegal to pass non-POD-esque types to a...
function. Call thisHELPER
HELPER(INVOKE_F_0...)
is a parameter pack expansion. in the context of invokingHELPER
'soperator()
, which is a legal context. The evaluation of arguments is unspecified, and due to the signature ofHELPER
INVOKE_F_0...
probably should only contain plain old data (in C++03 parlance), or more specifically [expr.call]/p7 says: (via @T.C)So the problems of this code is that the order is unspecified and it relies on well behaved types or specific compiler implementation choices.
We can fix the
operator,
problem as follows:then we can guarantee order by expanding in an initializer:
but the above fails when
Args...
is empty, so add another0
:and there is no good reason for the compiler to NOT eliminate
unused[]
from existance, while still evaluatedf
onargs...
in order.My preferred variant is:
which takes nullary lambdas and runs them one at a time, left to right. (If the compiler can prove that order does not matter, it is free to run them out of order however).
We can then implement the above with:
which puts the "strange expansion" in an isolated function (
do_in_order
), and we can use it elsewhere. We can also writedo_in_any_order
that works similarly, but makes theany_order
clear: however, barring extreme reasons, having code run in a predictable order in a parameter pack expansion reduces surprise and keeps headaches to a minimum.A downside to the
do_in_order
technique is that not all compilers like it -- expanding a parameter pack containing statement that contains entire sub-statements is not something they expect to have to do.