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

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.


Yes, you almost certainly do want to use std::forward here, this is assuming that the arguments in list are not used after the called to call_fn. This is a typical use case of std::forward, in that you want to exercise the semantics of perfect forwarding.

std::forward preserves the value category of its arguments (i.e. lvalues as lvalues, rvalues as rvalues). std::forward_as_tuple in turn will do the same, as if std::tuple<List&&...>(std::forward<List>(list)...) had been called.

A note on the "stored as rvalue references". It is not that the arguments List in the parameter pack are all rvalues references (they could be), but the List is being deduced in this context, hence reference collapsing will apply and the deduced type(s) could be rvalue references or lvalue references. During the creation of the std::tuple, it is this distinction that you would want to maintain/preserve.