Search code examples
c++templatesc++11tuplesperfect-forwarding

C++11 std::forward_as_tuple and std::forward


Should I std::forward my function parameters when I use them as arguments to std::forward_as_tuple?

template<class ... List>
void fn(List&& ... list){
   // do I need this forward?
   call_fn( forward_as_tuple( forward<List>(list)... ) );  
}

I know that they will be stored as rvalue references, but is there anything else I should consider?


Solution

  • You must use std::forward in order to preserve the value category of the argument(s) to fn(). Since the arguments have a name within fn, they are lvalues, and without std::forward they will always be passed as such to std::forward_as_tuple.

    The difference can be demonstrated using the following example:

    template<typename T>
    void bar2(T&& t)
    {
        std::cout << __PRETTY_FUNCTION__ << ' '
                   << std::is_rvalue_reference<decltype(t)>::value << '\n';
    }
    
    template<typename T>
    void bar1(T&& t)
    {
        std::cout << __PRETTY_FUNCTION__ << ' '
                  << std::is_rvalue_reference<decltype(t)>::value << '\n';
        bar2(std::forward<T>(t));
        bar2(t);
    }
    

    bar1 always passes it arguments on to bar2, once with std::forward and once without. Now let's call them with an lvalue and an rvalue argument.

    foo f;
    bar1(f);
    std::cout << "--------\n";
    bar1(foo{});
    

    Output:

    void bar1(T&&) [with T = foo&] 0
    void bar2(T&&) [with T = foo&] 0
    void bar2(T&&) [with T = foo&] 0
    --------
    void bar1(T&&) [with T = foo] 1
    void bar2(T&&) [with T = foo] 1
    void bar2(T&&) [with T = foo&] 0
    

    As you can see from the output, in both cases, without the use of std::forward, the argument is being passed as an lvalue to bar2.