for_each_arg—Eric Niebler

Save to:
Instapaper Pocket Readability

Eric Niebler picked up the Sean Parent's challenge regarding his for_each_argument tweet.

for_each_arg

by Eric Niebler

And after several iterations between Sean and Eric this is the beautiful result:

template<class F, class...Ts>
F for_each_arg(F f, Ts&&...a) {
  return (void)initializer_list<int>{(ref(f)((Ts&&)a),0)...}, f;
}

 

Add a Comment

Comments are closed.

Comments (8)

0 0

Eric Niebler said on Jan 26, 2015 04:49 PM:

I'm not sure I'd go so far as to call it "beautiful". grin
0 0

Felix Petriconi said on Jan 27, 2015 02:54 PM:

I know what you probably mean.
The number of braces does not contribute to the readability. But I trace this back to the limitations of a tweet length.
So let me better call it elegant. Your idea of returning the used predicate back to the caller is, what makes the "salt in the soup."
0 0

Yakk said on Jan 28, 2015 01:23 PM:

template<class F, class...Ts>
auto for_each_arg(F&& f, Ts&&...ts) {
return std::initializer_list<int>{0,(f((Ts&&)ts),0)...}, (F&&)f;
}

First, take the operation by reference. Forward it out. Second, support empty set of args.
0 0

Danny Havenith said on Feb 3, 2015 04:40 AM:

Question: I understand that the initialization of the elements of the initializer list is in a defined order, but does that mean that also the order of evaluation of the initializer expressions is guaranteed? I can imagine a that a compiler could choose to first execute all function invocations in some order, and then initialize the initializer list.

In other words: are there sequence points inside that initializer list initialization?

Or in yet other words, and this is what I'm really getting at: could the initializer list also contain expressions with side-effects like this:

size_t i = 0;
initializer_list<int>{(ref(f)((Ts&&)a, ++i),0)...}

...and would the order of evaluation of those expressions also be guaranteed?
0 0

Yakk said on Feb 3, 2015 01:56 PM:

Danny Havenith, yes, the expressions are sequenced left to right relative to each other.
0 0

Loic Urien said on Jan 5, 2016 08:19 AM:

Fun thing, using the upcoming fold expressions (already implemented by clang), this works :

template<class Fn, class ... Args>
constexpr Fn for_each_args(Fn f, Args&& ... args)
{
// return ((..., f(std::forward<Args>(args))), f);
// EDIT : to correct the issue pointed by Yakk
return ((..., (void)f(std::forward<Args>(args))), f);
}


Should work as expected with the -std=c++1z flag. Not really useful, but I found it amusing, so I might as well share it.
1 0

Yakk said on Jan 7, 2016 07:32 AM:

You are missing a void cast there (if `f(blah)` returns a non-void with an overloaded operator, bad things happen)
0 0

Loic Urien said on Jan 7, 2016 08:44 AM:

Yakk, frankly, after looking back at the code, I don't see how can bad things happen. It may be because my understanding is biaised, so here is how I understand the fold expressions : based on the cppreference definition http://en.cppreference.com/w/cpp/language/fold)

In my code

((..., f(std::forward<Args>(args))), f)

Is the same as (supposing argN = N-th argument, and TArgN = N-th argument type)

((((f(std::forward<TArg1>(arg1)), f(std::forward<TArg2>(arg2))), f(std::forward<TArg3>(arg3))), ... remaining), f)

Coma operator will evaluate and discard the result for every call, and return the last operand, the "f" function.
I have the feeling I may miss something here, so I'll be grateful if you could give me hints smile

EDIT : Oh nevermind, I was recently explaining to a friend how this trick worked, and this struck me. Sometimes, it's the simple thing that we don't see wink