The implementation of std::move
basically looks like this:
template<typename T>
typename std::remove_reference<T>::type&&
move(T&& t)
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
Note that the parameter of std::move
is a universal reference (also known as a forwarding reference, but we're not forwarding here). That is, you can std::move
both lvalues and rvalues:
std::string a, b, c;
// ...
foo(std::move(a)); // fine, a is an lvalue
foo(std::move(b + c)); // nonsense, b + c is already an rvalue
But since the whole point of std::move
is to cast to an rvalue, why are we even allowed to std::move
rvalues? Wouldn't it make more sense if std::move
would only accept lvalues?
template<typename T>
T&&
move(T& t)
{
return static_cast<T&&>(t);
}
Then the nonsensical expression std::move(b + c)
would cause a compile-time error.
The above implementation of std::move
would also be much easier to understand for beginners, because the code does exactly what it appears to do: It takes an lvalue and returns an rvalue. You don't have to understand universal references, reference collapsing and meta functions.
So why was std::move
designed to take both lvalues and rvalues?
Best Answer
Here is some example simplified to the extreme:
Cases like the one above may appear in generic code, for example when using containers which have specializations that return proxy objects (rvalues), and you may not know whether the client will be using the specialization or not, so you want unconditional support for move semantics.