Gonen I
Gonen I

Reputation: 6107

Perfect Forward using std::forward vs RefRefCast

I'd appreciate your help understanding std::forward implementation.

What's the difference between std::forward and a standard cast to T&& in a template function? In this example at least, they both seem to do the forwarding work correctly. The LValue gets forwarded to the LValue overload, and the RValue is forwarded to the RValue overload.

struct DemoPerfectForwardVsRefRefCast
{
    void overloaded(int const &arg) { std::cout << "receive by lvalue\n"; }
    void overloaded(int && arg) { std::cout << "receive by rvalue\n"; }

    template< typename t >
    void forwarding(t && arg) {
        std::cout << "via std::forward: ";
        overloaded(std::forward< t >(arg));  // forwards correctly
        std::cout << "via && cast: ";
        overloaded(static_cast<t&&>(arg)); // seems to also forward correctly
    }

    void demo()
    {
        std::cout << "initial caller passes rvalue:\n";
        forwarding(5);
        std::cout << "initial caller passes lvalue:\n";
        int x = 5;
        forwarding(x);
    }

};

Is universal reference + reference collapsing + static cast to T&& enough?

In Visual Studio 2017 the definition of std::forward is implemented differently, with two template functions, one with a T& param, and one with a T&& param. Is that necessary? Is it better than just the cast? I'm rather confused by their comments

// forward an lvalue as either an lvalue or an rvalue
// forward an rvalue as an rvalue

Upvotes: 5

Views: 208

Answers (2)

Caleth
Caleth

Reputation: 62531

If you mis-specify your type T, static_cast<T&&> will turn an rvalue into an lvalue, whereas the call to std::forward<T> will be ill-formed.

E.g.

int f() { return 42; }

template<typename T>
void forward() {
    std::forward<T>(f());
}

template<typename T>
void cast() {
    static_cast<T&&>(f());
}

int main() {
    forward<const int &>(); // error: static assertion failed: std::forward must not be used to convert an rvalue to an lvalue
    cast<const int &>(); // compiles
}

Upvotes: 2

Michael
Michael

Reputation: 950

In summary, in order just to forward a variable you could use a simple cast. The reason a function was introduced was primarily to make the code cleaner.

However, there is a slight advantage beyond that. In the vast majority of the times, the l-value variant of std::forward will be called, as every named variable is an l-value. But in some special cases, you might want to forward a function-call result (such as std::move). In this case, the casting static_cast<T&&>(std::move(t)) will not work if t is an lvalue, because casting from r-value to l-value is prohibited.

I believe that the second use-case is extremely rare, however it is a possibility that should be covered.

Upvotes: 2

Related Questions