Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ why does passing an lvalue to a move constructor work for templates?

I have this code, which doesn't compile, which is expected.

This is the error: an rvalue reference cannot be bound to an lvalue

class SomeData
{
public:
    vector<int> data;

    SomeData()
    {
        cout << "SomeData ctor" << endl;
        data.push_back(1);
        data.push_back(2);
        data.push_back(3);
    }

    SomeData(const SomeData &other)
    {
        cout << "SomeData copy ctor" << endl;
        data = other.data;
    }

    SomeData(SomeData &&other)
    {
        cout << "SomeData move ctor" << endl;
        data = move(other.data);
    }

    ~SomeData()
    {
        cout << "SomeData dtor" << endl;
    }

    void Print() const
    {
        for(int i : data)
            cout << i;

        cout << endl;
    }
};

void Function(SomeData &&someData)
{
    SomeData localData(someData);
    localData.Print();
}

int main(int argc, char *argv[])
{
    SomeData data;
    Function(data);                       // ERROR

    data.Print();

    return 0;
}

But, when I turn Function() into a template, it works fine, and uses the copy constructor of SomeData instead.

template<class T>
void Function(T &&someData)
{
    T localData(someData);                  // no more error
    localData.Print();
}


Is this standard C++ behaviour?

I've noticed that visual studio tends to be more forgiving when it comes to templates, so I am wondering if I can expect this same behaviour from all compliant C++11 compilers.

like image 956
CuriousGeorge Avatar asked Jul 17 '13 03:07

CuriousGeorge


2 Answers

Yes. In the case of a template function, the compiler deduces the template argument T such that it matches the argument given.

Since someData is in fact an lvalue, T is deduced as SomeData &. The declaration of Function, after type deduction, then becomes

void Function(SomeData & &&)

and SomeData & &&, following the rules for reference collapsing, becomes SomeData &.

Hence, the function argument someData becomes an lvalue-reference and is passed as such to the initialization of localData. Note that (as @aschepler pointed out correctly) localData is declared as T, so it is itself a reference of type SomeData &. Hence, no copy construction happens here – just the initialization of a reference.


If you want localData to be an actual copy, you would have to turn the type from SomeData & into SomeData, i.e. you would have to remove the & from the type. You could do this using std::remove_reference:

template<class T>
void Function(T &&someData)
{
   /* Create a copy, rather than initialize a reference: */
    typename std::remove_reference<T>::type localData(someData);
    localData.Print();
}

(For this, you'd have to #include <type_traits>.)

like image 61
jogojapan Avatar answered Oct 22 '22 08:10

jogojapan


It is indeed intended behavior. Template functions of the form:

template< class T >
Ret Function( T&& param )
{
  ...
}

follows special rules (Ret can be or not a template, it doesn't matter). T&& is called a universal reference and it can basically bind to anything. This is because when template deduction kicks in and the param is in that form (beware, vector<T>&& is not a universal reference, neither is C<T> for any other template parameter), the reference collapsing rule is applied:

T = int& => T&& = int& && and that collapse to a single int&

the complete table of corrispondence is:

& + && = &
&& + & = &
&& + && = &&

So when you have the above function

int a = 5;
int b& = a;
Function( a ); // (1)
Function( b ); // (2)
Function( 3 ); // (3)

In case 1, T = int& and the deduced type is int& (since a is an lvalue) so Function has the following signature:

Ret Function( int& param ) // int& && collapses to int&

In case 2, T = int&

Ret Function( int& param ) // int& && collapses to int&

In case 3, T = int

Ret Function( int&& param ) 

This collapsing rule is what the committee found out to be reasonable to make perfect forwarding works. You can find the long story in this Scott Meyers's video

like image 22
Fulvio Esposito Avatar answered Oct 22 '22 09:10

Fulvio Esposito