Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 566
tower120 Avatar asked Aug 21 '14 13:08

tower120


People also ask

What is std :: forward in C++?

The std::forward function as the std::move function aims at implementing move semantics in C++. The function takes a forwarding reference. According to the T template parameter, std::forward identifies whether an lvalue or an rvalue reference has been passed to it and returns a corresponding kind of reference.

What is std :: tuple?

Class template std::tuple is a fixed-size collection of heterogeneous values. It is a generalization of std::pair. If std::is_trivially_destructible<Ti>::value is true for every Ti in Types , the destructor of tuple is trivial.


3 Answers

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.

like image 114
Praetorian Avatar answered Nov 08 '22 11:11

Praetorian


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.

like image 21
Niall Avatar answered Nov 08 '22 10:11

Niall


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.

like image 1
Piotr Skotnicki Avatar answered Nov 08 '22 11:11

Piotr Skotnicki