Search code examples
c++lambdaparameter-passingc++14perfect-forwarding

Correct usage of `for_each_arg` - too much forwarding?


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.


Solution

  • 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)) );