Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why forwarding return value is needed

In the doc of std::forward, it gave the following example:

template<class T>
void wrapper(T&& arg)
{
    foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
}

Why is forwarding of return value needed here? What's the cases where it is different to the following code:

template<class T>
void wrapper(T&& arg)
{
    foo(forward<T>(arg).get());
}
like image 776
Kan Li Avatar asked Jun 02 '19 16:06

Kan Li


People also ask

Why do we need perfect forwarding?

Perfect forwarding reduces excessive copying and simplifies code by reducing the need to write overloads to handle lvalues and rvalues separately.

What is forwarding reference in C++?

When t is a forwarding reference (a function argument that is declared as an rvalue reference to a cv-unqualified function template parameter), this overload forwards the argument to another function with the value category it had when passed to the calling function.

What does std :: forward do?

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.


2 Answers

Let's break down the possibilities. T::get could return an lvalue reference (which is an lvalue expression), an rvalue reference (which is an xvalue expression), or a prvalue.

The forward expression will convert the lvalue expression into... an lvalue expression. It will convert the xvalue into... an xvalue. And it will convert a prvalue into an xvalue.

C++'s rules about how arguments bind to parameters in overload resolution are the same for prvalue and xvalue expressions. So the last two will always call the same function.

Therefore, the outer forward accomplishes nothing. Indeed, it is worse than doing nothing at all. Why?

Because prvalues in C++17 and above have guaranteed elision; xvalues do not. If foo takes the parameter by value, the additional forward will manifest an unnecessary temporary, which will then be moved into the argument. If the type is something more complex than an int, then there's a decent chance that you're going to lose some performance.

So don't forward return values which you are going to pass directly as function arguments. If you need to store the value in an intermediary auto&& variable, then you'll need to forward that. But if you're doing it in-situ like this, don't.

like image 83
Nicol Bolas Avatar answered Sep 28 '22 03:09

Nicol Bolas


The edit it was added in claims it is an example for the second overload:

template< class T >
constexpr T&& forward( typename std::remove_reference<T>::type&& t ) noexcept;

The example is not very good, as is already an rvalue. Actually I don't think the second overload is all that useful, except for making:

std::forward<decltype(expression)>(expression);

work for all expressions (Including if expression is an rvalue), but most use cases for std::forward are limited to the lvalues of T&& and auto&&.

like image 30
Artyer Avatar answered Sep 28 '22 04:09

Artyer