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.
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>
.)
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With