In the lecture about universal references, Scott Meyers (at approximately 40th minute) said that objects that are universal references should be converted into real type, before used. In other words, whenever there is a template function with universal reference type, std::forward
should be used before operators and expressions are used, otherwise a copy of the object might be made.
My understanding of this is in the following example :
#include <iostream>
struct A
{
A() { std::cout<<"constr"<<std::endl; }
A(const A&) { std::cout<<"copy constr"<<std::endl; }
A(A&&) { std::cout<<"move constr"<<std::endl; }
A& operator=(const A&) { std::cout<<"copy assign"<<std::endl; return *this; }
A& operator=(A&&) { std::cout<<"move assign"<<std::endl; return *this; }
~A() { std::cout<<"destr"<<std::endl; }
void bar()
{
std::cout<<"bar"<<std::endl;
}
};
A getA()
{
A a;
return a;
}
template< typename T >
void callBar( T && a )
{
std::forward< T >( a ).bar();
}
int main()
{
{
std::cout<<"\n1"<<std::endl;
A a;
callBar( a );
}
{
std::cout<<"\n2"<<std::endl;
callBar( getA() );
}
}
As expected, the output is :
1
constr
bar
destr
2
constr
move constr
destr
bar
destr
The question really is why is this needed?
std::forward< T >( a ).bar();
I tried without std::forward, and it seems to work fine (the output is the same).
Similarly, why he recommends to use move inside the function with rvalue? (the answer is the same as for std::forward)
void callBar( A && a )
{
std::move(a).bar();
}
I understand that both std::move
and std::forward
are just casts to appropriate types, but are these casts really needed in the above example?
Bonus : how can the example be modified to produce the copy of the object that is passed to that function?
What is said in the lecture is this :
After some thought, I realized that it is correct (as expected). Changing the
callBar
function in the original example to this demonstrate the point :If the
std::forward
wasn't used incallBar
, then thereallyCallBar( A& )
would be used. Becausea
incallBar
is a lvalue reference.std::forward
makes it a rvalue, when the universal reference is the rvalue reference.Next modification proves the point even further :
Since
std::move
is not used in thereallyCallBar( A&& ra )
function, it doesn't enter the endless loop. Instead it calls the version taking lvalue reference.Therefore (as explained in the lecture) :
std::forward
must be used on universal referencesstd::move
must be used on rvalue referencesIn the case where you have an rvalue reference as a parameter, which can only bind to an rvalue you normally want to use move semantics to move from this this rvalue and take it's guts out.
The parameter itself is an lvalue, because it is a named thing. You can take it's address.
So in order to make it an rvalue again and be able to move from it, you apply
std::move
to it. If you were literally just calling a function on a passed parameter, I don't see why you'd have a parameter that is an rvalue reference.You only want to pass an rvalue reference if you are going to move from this inside your function, which is why you then have to use
std::move
.Your example here doesn't actually make much sense in that respect.
There are two different uses for
&&
on a parameter to a function. For an ordinary function it means that the argument is an rvalue reference; for a template function it means that it can be either an rvalue reference or an lvalue reference:Inside
f
andg
, applyingstd::forward
to such an argument preserves the lvalue- or rvalue-ness of the argument, so in general that's the safest way to forward an argument to another function.It's needed because
bar()
might be overloaded separately for rvalues and lvalues. That means that it might do something differently, or flat out not be allowed, depending on if you correctly describeda
as an lvalue or an rvalue, or just blindly treated it like an lvalue. Right now, most users don't use this functionality and don't have exposure to it because the most popular compilers don't support it - even GCC 4.8 doesn't support rvalue*this
. But it is Standard.