Correct usage of `for_each_arg` - too much forward

2019-04-11 20:19发布

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)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.

2条回答
女痞
2楼-- · 2019-04-11 20:34

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)) );
查看更多
够拽才男人
3楼-- · 2019-04-11 20:35
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).

查看更多
登录 后发表回答