3

I'm really happy to have discovered for_each_arg(...), which makes dealing with argument packs much easier.

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

I'm, however, confused on its correct usage. There are many arguments that need to be perfectly forwarded, but am I performing any unnecessary forwarding?

Reading the code becomes harder with excessive fowarding.

struct UselessContainer
{
    // Expects a perfectly-forwarded item to emplace
    template<typename T> void add(T&&) { }   
};

// Creates an `UselessContainer` already filled with `mArgs...`
auto makeUselessContainer(TArgs&&... mArgs)
{
    using namespace std;
    UselessContainer result;

    for_each_arg
    (
        [&result, &mArgs...] // Am I capturing the `mArgs...` pack correctly here?
        (auto&& mX) // Am I passing the arguments to the lambda correctly here?
        { 
            // Is this `forward` necessary?
            result.add(forward<decltype(mX)>(mX)); 

            // Could it be replaced with
            // `result.add(forward(mX));` 
            // ?             
        }, 
        forward<TArgs>(mArgs)... // I assume this `forward` is necessary.
    );

    return result;
}

All my questions/doubts are expressed in the comments in the above code example.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
Vittorio Romeo
  • 90,666
  • 33
  • 258
  • 416
  • I don't see why you need to capture the pack. The rest looks OK. You forward the arguments to `for_each_arg`, which forwards them to the lambda, which needs to forward them to `add()`. – T.C. Feb 01 '15 at 13:12

2 Answers2

3

Every forward in your code is indeed necessary to perfectly forward all arguments until the end. Names of rvalue references are lvalues, so unless you're forwarding everytime you pass arguments on, the value category information is lost.
Also it is impossible to call forward without an explicit template argument list as the template parameter is only used in one, non-deduced context. In fact, a function template called without an explicit argument list cannot do the job.

You can try a macro to somewhat shorten the code:

#define FORWARD(...) std::forward<decltype(__VA_ARGS__)>(__VA_ARGS__)

It then becomes

for_each_arg
(
    // Removed superfluous capture
    [&result] (auto&& mX) { 
        result.add(FORWARD(mX));       
    }, 
    FORWARD(mArgs)...
);

It's also possible to use a macro instead of for_each_arg in the first place:

#define FOR_EACH_ARG(...) (void)std::initializer_list<int>{((__VA_ARGS__),0)...}

FOR_EACH_ARG( result.add(forward<TArgs>(mArgs)) );
Columbo
  • 60,038
  • 8
  • 155
  • 203
  • 1
    Thank god in C++17 we only have to do `((void)result.add(forward(mArgs)) , ...);` – T.C. Feb 01 '15 at 13:27
  • @T.C.: Is that part of the fold expressions proposal? How can I learn more about it? – Vittorio Romeo Feb 01 '15 at 13:35
  • 1
    @Deduplicator Not needed, fold expression works with empty packs too (for certain operators, including `,`). – T.C. Feb 01 '15 at 13:35
  • 1
    @VittorioRomeo Original proposal is [N4191](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4191.html); the final wording is [N4295](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4295.html). – T.C. Feb 01 '15 at 13:37
  • @T.C. Why are you casting to `void`? – Columbo Feb 01 '15 at 13:40
  • @Columbo Not necessary in this case; guarding against overloaded comma for the general case. – T.C. Feb 01 '15 at 13:41
  • 1
    @columbo What if `add` returns `struct evil {void operator,(evil){delete this;}};`? ;) – Yakk - Adam Nevraumont Feb 01 '15 at 13:42
  • @Yakk ... but it doesn't – Columbo Feb 01 '15 at 13:42
  • @T.C. Good point. For normal pack expansions the implicit comma is never invoking the comma operator though, I was mixing things up. – Columbo Feb 01 '15 at 13:43
  • @columbo there could be a specialization, introduced by author of `evil`. Bwahahaha. ahum. (or some other type) – Yakk - Adam Nevraumont Feb 01 '15 at 13:45
  • @Columbo: Unrelated, but I really like the use of the forward macro. Makes the code much more readable. Does it apply to every situation? - Consider having a `TArgs&&... mArgs` parameter pack. Would writing `FORWARD(mArgs)...` be equivalent to writing `std::forward(mArgs)...`? – Vittorio Romeo Feb 01 '15 at 14:17
1
for_each_arg (
  [&](auto&& mX){
    result.add(std::forward<decltype(mX)>(mX));
  },
  std::forward<TArgs>(mArgs)...
);

Just capture & when making this kind of lambda. If you must list, only &result need be captured.

forward<?> is always used with a type parameter.

Note Eric's for_each_arg is imperfect, and mostly about doing it in 140 characters or less. ;) Its imperfections are mild, and harmless here.

Here is an alternative:

First, write this:

template<class...Fs>
void do_in_order(Fs&&...fs){
  int _[]={0,
    (((void)(std::forward<Fs>(fs)())),0)...
  };
  (void)_; // kills warnings
}

it takes zero arg lambdas, and runs them left to right.

Then replace the call to for_each_arg with:

do_in_order(
  [&]{
    result.add(std::forward<TArgs>(mArgs));
  }...
);

the downside is that more compilers won't like the above.

Ordering of the expressions in the do_in_order is guaranteed by [dcl.init] and [dcl.init.list] sections in n4296 8.5.4/4 8.5.4/1 8.5/15 8.5/1. The initialization is a copy-list-initialization (8.5/15 and 8.5.4/1), is a "initializer-list of a braced-init-list" (8.5/1) and as such is sequenced left to right (8.5.4/4).

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • Didn't know about possible imperfections in `for_each_arg`. I do not actually care about the length of its implementation, so if it's possible to fix those imperfections I'd really like to know how! `for_each_arg` has become one of my favorite functions :) – Vittorio Romeo Feb 01 '15 at 13:34
  • @vitt is should take `F` by `F&&` to do away with a possibly useless copy. Returning `F` is questionable, and if it happens should be forwarded into (well moved in existing code). `std::ref(f)` is pointless -- just `f`. Overall it often requires extra forwarding (compare the two above) and boilerplate. – Yakk - Adam Nevraumont Feb 01 '15 at 13:38
  • 1
    Well, `std::ref` is not quite pointless, only mostly. What if you want to call the same member-function-pointer on a whole list of objects? Remember that `ref(f)(...)` has the semantics of `INVOKE(f, ...)`. – Deduplicator Feb 01 '15 at 13:42
  • @deduplicator heh, true: but the caller can `std::ref` if it wasn't there. I guess it is harmless -- does `ref` support rvalue `()`? – Yakk - Adam Nevraumont Feb 01 '15 at 14:32
  • Not for its argument. But the returned `reference_wrapper` does for the rest. – Deduplicator Feb 01 '15 at 14:36
  • @Yakk For the member function pointer case the caller can `std::mem_fn` it. – T.C. Feb 01 '15 at 18:15
  • I'm pretty sure the initializers in `int _[] = { stuff... };` aren't guaranteed to be evaluated in order left-to-right. The new "always left-to-right" behavior (in C++11 at least) applies only to Uniform Initialization Syntax, which needs to omit the `=` sign. – Quuxplusone Feb 02 '15 at 19:00
  • 1
    @Quuxplusone I don't see that argument supported in the standard. I edited my post to contain citations to the various sections I believe apply. – Yakk - Adam Nevraumont Feb 02 '15 at 19:11