When is a reference a forwarding reference, and when is it an rvalue reference?

11.7k views Asked by At

I understand that a forwarding reference is "an rvalue reference to a cv-unqualified template parameter", such as in

template <class T> void foo(T&& );

which means the above function can take both l-value and r-value reference.

There's something I don't understand, e.g.

template <class T>
class A
{
    template <class U>
    void foo(T&& t, U&& u)
    {
        T t2( std::forward(t) ); // or should it be std::move(t)? is T&& forwarding or r-value reference
        U u2( std::forward(u) ); // or should it be std::move(u)? I believe U&& is forwarding reference
    }
};

in the above code, are both T&& and U&& forwarding references?

I wrote some code to test (VS2015 compiler):

class A
{
public:
    A(){};
    A(const A& rhs)
    {
        std::cout << "calling 'const A&' l-value" << std::endl;
    }

    A(A&& rhs)
    {
        std::cout << "calling ' A&&' r-value" << std::endl;
    }

};

template <class T>
class Test
{
public:
    void test1(T&& t)
    {
        T t2(std::forward<T>(t));
    }

    template <typename X>
    void test2(X&& x)
    {
        T t2( std::forward<T>( x ) );
    }

};

void main()
{
    A a;
    Test<A> test;
    test.test1(A());
    test.test1(std::move(a));
    //test.test1(a); // this doesn't compile. error: cannot convert argument 1 from 'A' to 'A &&', You cannot bind an lvalue to an rvalue reference

    test.test2<A>(A());
    test.test2<A>( std::move( a ) );
    
    //test.test2<A>( a ); // this doesn't compile. error: cannot convert argument 1 from 'A' to 'A &&', You cannot bind an lvalue to an rvalue reference
}

I was expecting that test.test1(a) and test.test2<A>(a) should both compile if they are forwarding references, but neither does.

Could someone explain this to me?

3

There are 3 answers

1
Richard Hodges On BEST ANSWER

It's a great question which foxes almost everyone in the beginning.

template <class T>
class A
{
    template <class U>
    void foo(T&& t, U&& u);
};

In this example, T is not deduced (you explicitly define it when you instanciate the template).

U is deduced because it's deduced from the argument u.

Therefore, in almost all cases it would be:

std::move(t);
std::forward<U>(u);
0
Vittorio Romeo On

are both T&& and U&& forwarding references?

No, only U&& is a forwarding reference, because U is the only template argument that's being deduced. T was already "chosen" when instantiating A.

0
Johanna Ye On

In addition to what Richard and Artemy pointed out, when you specified test.test2<A>( a ), the type X is already explicitly defined to be A.

When you change it to test.test2( a ), then the type X should be deduced and it should compile.