tower120
tower120

Reputation: 5255

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?

Upvotes: 15

Views: 10947

Answers (3)

Niall
Niall

Reputation: 30605

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.

Upvotes: 3

Praetorian
Praetorian

Reputation: 109159

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.

Upvotes: 11

Piotr Skotnicki
Piotr Skotnicki

Reputation: 48467

Yes, if you want to preserve the perfect forwarding semantics. In your example:

template<class ... List>
void fn(List&& ... list)

the type List&&, where List is actually a template parameter, is a Universal Reference rather than an r-value reference. As such, you should std::forward them to std::forward_as_tuple function, otherwise inside the std::forward_as_tuple the r-value references passed to fn will be visible as l-value references due to reference collapsing.

Upvotes: 2

Related Questions