We already know what is a rvalue reference and a lvalue.
Rvalue references enable the move semantics. We have rvalue reference of type T when we see in the code T&&. The references of this type can bind to rvalues and are used to identify objects that can be moved.We can use the std::move function to cast its argument to an rvalue.
But rvalue references also can behave as if they were lvalue references (i.e., “T&”).When they behave like that we call them universal references.
class X {...}
X x;
X& b=x;
void teste(X&& a){}
template <typename T>
void test_type_deduction(T&& a) {}
if we try this code:
//Okay rvalue reference bind to rvalue
X&& a = X() ;
teste(X());
//error: cannot bind rvalue reference of type ‘X&&’ to lvalue
//X&& d= b;
//teste(x);
// okay bind rvalue reference of type ‘X&&’to rvalue a
X&& a = X();
//void teste(X&& a) parameter is a rvalue reference. Ok
teste(x());
teste(std::move(x));
// x3 bind to lvalue?!... why? hint: Presence of type deduction
// existing type deduction we call the above references :
// Universal references; they can bind to lvalues or rvalues
auto&& x3 = b;
auto&& x1 = b; //x1 bind to rvalue?!... why?
auto&& x2 = X(); // x2 iq a rvalue
// Function test_type_deduction(T&& x) can receive lvalue or
// rvalue; The function is a function template and we have
// type deduduction when passing parameters.
// we say that (T&& x) is a Universal reference
test_type_deduction(b);
test_type_deduction(X());
Has we can see, when the type of the parameter is deduced call X&& a universal reference.
This is more usual with function templates and enable a function to accept any kind of parameters , rvalues, lvalues, const or non-const objects and volatile or non-volatile objects, even to objects that are const and volatile at same time. The functions with universal references can call others functions and pass the correct type using the perfect_forwarding semantic.
with rvalue references we use std::move to move objects; and with universal references we use std::forward when we want to call other functions and pass the parameter received, with correct type_casting if is necessary.
See the examples using std::forward:
//Function template using a universal reference
// call the process function forwarding the parameter
template<typename T>
void logAndProcess(T&& param)
{
process(std::forward<T>(param));
}
void process(const X& lval) // process lvalues
{
std::cout<<"process lvalue\n";
}
void process(X&& rvalArg) // process rvalues
{
std::cout<<"process rvalue\n";
}
Let X =x; X& a=x;
logAndProcess(a)
the output: "process lvalue";
logAndProcess(X()) or logAndProcess(std::move(a))
the output :"process rvalue";
The function logAndProcess(T&& x) forward the parameter to the the rigth version of function, ( process(X&&) or process(x&) .
Another example:
template <typename T>
void f1(T&& x)
{
f2(std::forward(x));
}
template <typename T>
void f2(T&& x)
{
g(std::forward<T>(x));
}
template <typename T>
void g(T x)
{
std::cout<<"g(T x)"<<std::endl;
}
//f1 receive lvalue parameter and call f2, then f2 call g(Tx)
f1(b);
//f1 receive rvalue parameter and call f2, then f2 call g(Tx)\n";
f1(X());
You can run and play with the code http://tpcg.io/zdIe0wPr
Next post :Type deduction and reference collapsing