Search code examples
c++perfect-forwarding

Is forward needed to perfectly pass output of a call to another call?


According to the (cppreference) there is a need to use std::forward() in order to perfectly pass outcome of a function passed to another call without storing it explicitly elsewhere.

[...] For example, if a wrapper does not just forward its argument, but calls a member function on the argument, and forwards its result:

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

where the type of arg may be

struct Arg
{
    int i = 1;
    int  get() && { return i; } // call to this overload is rvalue
    int& get() &  { return i; } // call to this overload is lvalue
};

Attempting to forward an rvalue as an lvalue, such as by instantiating the form (2) with lvalue reference type T, is a compile-time error. [...]

Isn't is the case that calling a function with a parameter that is outcome of another call perfectly passes the outcome with original value type and category? Consider following snippet that is close to the original example:

struct Inner{};

struct Arg
{
    Inner inner{};
    Inner&& get() && { return move(inner); } // call to this overload is rvalue
    Inner& get() & { return inner; } // call to this overload is lvalue
    const Inner& get() const & { return inner; }; // call to this overload is also lvalue
};

void foo(Inner&&) { std::cout << "&&" << std::endl; }
void foo(Inner&) { std::cout << "&" << std::endl; }
void foo(const Inner&) { std::cout << "const&" << std::endl; }

template<class T>
void wrapper(T&& arg)
{
    // NOTE: choose either the former or the latter version!

    // with extra forward
    foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get()));
    
    // without extra forward
    foo(forward<T>(arg).get());
}

int main()
{
    // prvalue
    wrapper(Arg{});
    // xvalue
    Arg arg;
    wrapper(move(arg));
    // lvalue
    Arg arg2;
    wrapper(arg2);
    // const lvalue
    const Arg arg3;
    wrapper(arg3);
    
    return 0;
}

In both cases the output is exactly the same:

&&
&&
&
const&

Solution

  • You are right. The second forward does absolutely nothing, except force a return-by-value function that returns a prvalue to create a temporary (usually unwanted).

    If decltype(forward<T>(arg).get()) is an lvalue reference, forward<T>(arg).get() is an lvalue already and the outer forward does nothing.

    If decltype(forward<T>(arg).get()) is an rvalue reference, forward<T>(arg).get() is an xvalue already and the outer forward does nothing.

    If decltype(forward<T>(arg).get()) is not a reference, forward<T>(arg).get() is a prvalue and the outer forward forces a temporary to be materialized (resulting in an xvalue). *In C++11/14 this would turn a prvalue into an xvalue, inhibiting an optional RVO


    forward<decltype(expr)>(expr) should only be used if expr is the name of a variable. Thats the only time that decltype(expr) can have different ref-qualifiers than the value category of the expression. If expr is not a name, it is better to just write (expr).