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?
Universal reference was a term Scott Meyers coined to describe the concept of taking an rvalue reference to a cv-unqualified template parameter, which can then be deduced as either a value or an lvalue reference.
std::forward helps to implement perfect forwarding. This mechanism implies that objects passed to the function as lvalue expressions should be copied, and objects passed to the function as rvalue expressions should be moved. If you assign an rvalue reference to some ref variable, then ref is a named entity.
Reference collapsing is the mechanism that leads to universal references (which are really just rvalue references in situations where reference-collapsing takes place) sometimes resolving to lvalue references and sometimes to rvalue references.
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.
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 described a
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.
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:
template <class T> void f(T&&); // rvalue or lvalue
void g(T&&); // rvalue only
void g(T&) // lvalue only
void h() {
C c;
f(c); // okay: calls f(T&)
f(std::move(c)); // okay: calls f(T&&)
g(c); // error: c is not an rvalue
g(std::move(c)); // okay: move turns c into an rvalue
}
Inside f
and g
, applying std::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.
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