Demystifying Sean Parent's for_each_argument [duplicate] - c++

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.

The short answer is "it does it not very well".
It invokes f on each of the args..., 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 given Arg has an overloaded operator, it can have unfortunate side effects.
With some white space:
[](...){}(
(
f(std::forward<Args>(args)), 0
)...
);
We will start from the inside.
f(std::forward<Args>(args)) is an incomplete statement that can be expanded with a .... It will invoke f on one of args when expanded. Call this statement INVOKE_F.
(INVOKE_F, 0) takes the return value of f(args), applies operator, then 0. If the return value has no overrides, this discards the return value of f(args) and returns a 0. Call this INVOKE_F_0. If f returns a type with an overriden operator,(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 this HELPER
HELPER(INVOKE_F_0...) is a parameter pack expansion. in the context of invoking HELPER's operator(), which is a legal context. The evaluation of arguments is unspecified, and due to the signature of HELPER INVOKE_F_0... probably should only contain plain old data (in C++03 parlance), or more specifically [expr.call]/p7 says: (via #T.C)
Passing a potentially-evaluated argument of class type (Clause 9) having a nontrivial copy constructor, a non-trivial move constructor, or a non-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics.
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:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
[](...){}((void(f(std::forward<Args>(args))), 0)...);
}
then we can guarantee order by expanding in an initializer:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
int unused[] = {(void(f(std::forward<Args>(args))), 0)...};
void(unused); // suppresses warnings
}
but the above fails when Args... is empty, so add another 0:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
int unused[] = {0, (void(f(std::forward<Args>(args))), 0)...};
void(unused); // suppresses warnings
}
and there is no good reason for the compiler to NOT eliminate unused[] from existance, while still evaluated f on args... in order.
My preferred variant is:
template <class...F>
void do_in_order(F&&... f) {
int unused[] = {0, (void(std::forward<F>(f)()), 0)...};
void(unused); // suppresses warnings
}
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:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
do_in_order( [&]{ f(std::forward<Args>(args)); }... );
}
which puts the "strange expansion" in an isolated function (do_in_order), and we can use it elsewhere. We can also write do_in_any_order that works similarly, but makes the any_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.

Actually it calls function f for each argument in args in unspecified order.
[](...){}
create lambda function, that does nothing and receives arbitrary number of arguments (va args).
((f(std::forward<Args>(args)), 0)...)
argument of lambda.
(f(std::forward<Args>(args)), 0)
call f with forwarded argument, send 0 to lambda.
If you want specified order you can use following thing:
using swallow = int[];
(void)swallow{0, (f(std::forward<Args>(args)), 0)...};

Related

Best way to apply a void function to a parameter pack

I would like to write a function that applies a function to each element of a parameter pack. The functions returns a std::tuple with the results of each invocation.
However, if the applied function returns void, I have to do something else, so I have a different overload for this case. But, almost all the ways I've found to expand the parameter pack do not work with void expressions, so I had to resort to what you see below, which seems a weird trick.
template<typename F, typename ...Args>
requires requires { std::tuple{std::declval<F>(Args)...}; }
auto for_each(F f, Args ...args) {
return std::tuple{f(args)...};
}
template<typename F, typename ...Args>
auto for_each(F f, Args ...args)
[[maybe_unused]] int a[] = {(f(Members), 0)...};
}
Note that I have to declare a unused variable and mark it with the attribute.
Which is the best way to obtain the expected result here?
This trick is the way to go pre-C++17, except that you need an extra , 0 in the array to support zero-length packs.
In C++17 and newer, use a fold expression: (f(args), ...);.
Note that you forgot perfect forwarding. You should be doing F &&f, Args &&... args, and then (f(std::forward<Args>(args)), ...);, and similarly for the first function.
I also question the value of having such a function. I'd understand doing this to a tuple, but if you already have a pack, you can do this manually at the call site.

How to understand this C++ syntax? [duplicate]

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.
The short answer is "it does it not very well".
It invokes f on each of the args..., 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 given Arg has an overloaded operator, it can have unfortunate side effects.
With some white space:
[](...){}(
(
f(std::forward<Args>(args)), 0
)...
);
We will start from the inside.
f(std::forward<Args>(args)) is an incomplete statement that can be expanded with a .... It will invoke f on one of args when expanded. Call this statement INVOKE_F.
(INVOKE_F, 0) takes the return value of f(args), applies operator, then 0. If the return value has no overrides, this discards the return value of f(args) and returns a 0. Call this INVOKE_F_0. If f returns a type with an overriden operator,(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 this HELPER
HELPER(INVOKE_F_0...) is a parameter pack expansion. in the context of invoking HELPER's operator(), which is a legal context. The evaluation of arguments is unspecified, and due to the signature of HELPER INVOKE_F_0... probably should only contain plain old data (in C++03 parlance), or more specifically [expr.call]/p7 says: (via #T.C)
Passing a potentially-evaluated argument of class type (Clause 9) having a nontrivial copy constructor, a non-trivial move constructor, or a non-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics.
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:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
[](...){}((void(f(std::forward<Args>(args))), 0)...);
}
then we can guarantee order by expanding in an initializer:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
int unused[] = {(void(f(std::forward<Args>(args))), 0)...};
void(unused); // suppresses warnings
}
but the above fails when Args... is empty, so add another 0:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
int unused[] = {0, (void(f(std::forward<Args>(args))), 0)...};
void(unused); // suppresses warnings
}
and there is no good reason for the compiler to NOT eliminate unused[] from existance, while still evaluated f on args... in order.
My preferred variant is:
template <class...F>
void do_in_order(F&&... f) {
int unused[] = {0, (void(std::forward<F>(f)()), 0)...};
void(unused); // suppresses warnings
}
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:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
do_in_order( [&]{ f(std::forward<Args>(args)); }... );
}
which puts the "strange expansion" in an isolated function (do_in_order), and we can use it elsewhere. We can also write do_in_any_order that works similarly, but makes the any_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.
Actually it calls function f for each argument in args in unspecified order.
[](...){}
create lambda function, that does nothing and receives arbitrary number of arguments (va args).
((f(std::forward<Args>(args)), 0)...)
argument of lambda.
(f(std::forward<Args>(args)), 0)
call f with forwarded argument, send 0 to lambda.
If you want specified order you can use following thing:
using swallow = int[];
(void)swallow{0, (f(std::forward<Args>(args)), 0)...};

How to map a parameter pack using a member function? (non-recursive, pre-fold expressions)

In C++17, fold expressions allow you to map parameter packs, calling a member function on each, before passing them on into another function:
template <typename... Ts>
double func(const Ts&... ts) { ... }
template <typename... Us>
double func2(const Us&... us)
{
// assume all member_func return types are non-void
return func((us.member_func(1), ...));
}
Is it possible to achieve a similar, non-recursive mapping in C++14?
Your original code only passes one argument to the function. That is because this
(us.member_func(1), ...)
Is a fold expression that applies the comma operator. It calls all those members in sequence, but the value of the expression is just what the last invocation returns.
Assuming you want to call the member on each object, and pass all the results onto the next function, the fix also turns it into valid C++14:
template <typename... Us>
double func2(const Us&... us)
{
// assume all member_func return types are non-void
return func(us.member_func(1) ...);
}
Just a regular pack expansion in a functions argument list. Though there better not be any sequencing requirements between those calls. The comma operator has sequencing guarantees, the evaluation of function arguments far less so.

What does this variadic template code do?

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.
The short answer is "it does it not very well".
It invokes f on each of the args..., 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 given Arg has an overloaded operator, it can have unfortunate side effects.
With some white space:
[](...){}(
(
f(std::forward<Args>(args)), 0
)...
);
We will start from the inside.
f(std::forward<Args>(args)) is an incomplete statement that can be expanded with a .... It will invoke f on one of args when expanded. Call this statement INVOKE_F.
(INVOKE_F, 0) takes the return value of f(args), applies operator, then 0. If the return value has no overrides, this discards the return value of f(args) and returns a 0. Call this INVOKE_F_0. If f returns a type with an overriden operator,(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 this HELPER
HELPER(INVOKE_F_0...) is a parameter pack expansion. in the context of invoking HELPER's operator(), which is a legal context. The evaluation of arguments is unspecified, and due to the signature of HELPER INVOKE_F_0... probably should only contain plain old data (in C++03 parlance), or more specifically [expr.call]/p7 says: (via #T.C)
Passing a potentially-evaluated argument of class type (Clause 9) having a nontrivial copy constructor, a non-trivial move constructor, or a non-trivial destructor, with no corresponding parameter, is conditionally-supported with implementation-defined semantics.
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:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
[](...){}((void(f(std::forward<Args>(args))), 0)...);
}
then we can guarantee order by expanding in an initializer:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
int unused[] = {(void(f(std::forward<Args>(args))), 0)...};
void(unused); // suppresses warnings
}
but the above fails when Args... is empty, so add another 0:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
int unused[] = {0, (void(f(std::forward<Args>(args))), 0)...};
void(unused); // suppresses warnings
}
and there is no good reason for the compiler to NOT eliminate unused[] from existance, while still evaluated f on args... in order.
My preferred variant is:
template <class...F>
void do_in_order(F&&... f) {
int unused[] = {0, (void(std::forward<F>(f)()), 0)...};
void(unused); // suppresses warnings
}
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:
template <class F, class... Args>
void for_each_argument(F f, Args&&... args) {
do_in_order( [&]{ f(std::forward<Args>(args)); }... );
}
which puts the "strange expansion" in an isolated function (do_in_order), and we can use it elsewhere. We can also write do_in_any_order that works similarly, but makes the any_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.
Actually it calls function f for each argument in args in unspecified order.
[](...){}
create lambda function, that does nothing and receives arbitrary number of arguments (va args).
((f(std::forward<Args>(args)), 0)...)
argument of lambda.
(f(std::forward<Args>(args)), 0)
call f with forwarded argument, send 0 to lambda.
If you want specified order you can use following thing:
using swallow = int[];
(void)swallow{0, (f(std::forward<Args>(args)), 0)...};

variadic template arguments unpacking

For each argument I need apply two nested function:
obj.apply(someFilter(arg)); // arg is one argument, but here
// should be an unpacking of args
I don't know how to write unpacking for such case.
I saw this:
pass{([&]{ std::cout << args << std::endl; }(), 1)...};
on wiki, but again don't know how to apply this for my case.
It's actually quite simple:
You can put arbitrary expression inside the unpack of an variadic templates argument pack:
obj.apply(someFilter(arg))...
This will give you the result of obj.apply as a coma seperated list. You can then pass it to a dummy function:
template<typename... Args> swallow (Args&&...) {}
swallow(obj.apply(someFilter(arg))...);
To swallow the comma seperated list.
Of course, this assumes that obj.apply returns some kind of object. If not you can use
swallow((obj.apply(someFilter(arg)), 0)...);
to make actual (non void) arguments
If you don't know what obj.apply` returns (result might have overloaded the comma operator), you can disable the use of custom comma operators by using
swallow((obj.apply(someFilter(arg)), void(), 0)...);
Should you actually need to evaluate the items in order (which doesn't seem very likely from the question), you can abuse array initialization syntax instead of using a function call:
using Alias=char[];
Alias{ (apply(someFilter(args)), void(), '\0')... };
Here is a robust way to do an arbitrary set of actions on a parameter pack. It follows the principle of least surprise, and does the operations in order:
template<typename Lambda, typename Lambdas>
void do_in_order( Lambda&& lambda, Lambdas&& lambdas )
{
std::forward<Lambda>(lambda)();
do_in_order( std::forward<Lambdas>(lambdas)... );
}
void do_in_order() {}
template<typename Args>
void test( Args&& args ) {
do_in_order( [&](){obj.apply(someFilter(std::forward<Args>(args)));}... );
}
Basically, you send a pile of lambdas at do_in_order, which evaluates them front to back.
I assume that the code has multiple args as a parameter pack? Try:
obj.apply( someFilter( arg )... );
as parameter unpacking applies to the expression, so each element of the parameter pack get's expanded into someFilter( arg ).