Let T
be an arbitrary type. Consider a function that takes a const
[lvalue] reference:
void f(const T &obj);
Suppose that this function internally makes a call to another function, which has an rvalue reference overload:
void g(T &&obj);
If we pass an rvalue to f
, will the rvalue reference overload of g
be called, or will it fail to do so since it has been "converted"/bound to a const
lvalue reference?
Similarly, if f
called a function that takes an instance of T
by value,
void h(T obj);
and T
has a move constructor, (i.e. T(T &&);
), will the move constructor be called, or will the copy constructor be called?
In conclusion, if we wanted to ensure that, when calling f
on an rvalue, the rvalue reference is passed around keeping its rvalue "status", would we necessarily have to provide an rvalue reference overload for f
?
When you use the name of a reference as an expression, that expression is always an lvalue. No matter if it's a lvalue reference or an rvalue reference. It also doesn't matter if the reference was initialized with an lvalue or an rvalue.
int &&x = 1;
f(x); // Here `x` is lvalue.
So in void f(const T &obj) {...}
, obj
is always an lvalue, regardless of what you pass as an argument.
Also note that value category is determined at compile time. Since f
is not a template, value category of every expression in it can't depend on arguments you pass.
Thus:
If we pass an rvalue to
f
, will the rvalue reference overload ofg
be called
No.
if
f
called a function that takes an instance ofT
by value,void h(T obj);
andT
has a move constructor, (i.e.T(T &&);
), will the move constructor be called
No.
In conclusion, if we wanted to ensure that, when calling
f
on an rvalue, the rvalue reference is passed around keeping its rvalue "status", would we necessarily have to provide an rvalue reference overload forf
?
Providing an overload is one option. Note that in this case you have to explicitly call std::move
in the rvalue overload.
Another option is using forwarding references, as Nicol Bolas suggests:
template <typename T> void f(T &&t)
{
g(std::forward<T>(t));
}
Here, std::forward
essentially acts as a 'conditional move
'. It moves t
if an rvalue was passed to it, and does nothing otherwise.