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?
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.
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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With