Why is std::move named std::move?
c++, c++-faq, language-design, move-semantics, rvalue-reference
Solution
It is correct that `std::move(x)` is just a cast to rvalue - more specifically to an xvalue, as opposed to a prvalue. And it is also true that having a cast named `move` sometimes confuses people. However the intent of this naming is not to confuse, but rather to make your code more readable.
The history of `move` dates back to the original move proposal in 2002. This paper first introduces the rvalue reference, and then shows how to write a more efficient `std::swap`:
template <class T>
void
swap(T& a, T& b)
{
T tmp(static_cast<T&&>(a));
a = static_cast<T&&>(b);
b = static_cast<T&&>(tmp);
}
One has to recall that at this point in history, the only thing that "`&&`" could possibly mean was logical and. No one was familiar with rvalue references, nor of the implications of casting an lvalue to an rvalue (while not making a copy as `static_cast<T>(t)` would do). So readers of this code would naturally think:
I know how `swap` is supposed to work (copy to temporary and then exchange the values), but what is the purpose of those ugly casts?!
Note also that `swap` is really just a stand-in for all kinds of permutation-modifying algorithms. This discussion is much, much bigger than `swap`.
Then the proposal introduces syntax sugar which replaces the `static_cast<T&&>` with something more readable that conveys not the precise what, but rather the why:
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
I.e. `move` is just syntax sugar for `static_cast<T&&>`, and now the code is quite suggestive as to why those casts are there: to enable move semantics!
One must understand that in the context of history, few people at this point really understood the intimate connection between rvalues and move semantics (though the paper tries to explain that as well):
Move semantics will automatically come into play when given rvalue arguments. This is perfectly safe because moving resources from an rvalue can not be noticed by the rest of the program (nobody else has a reference to the rvalue in order to detect a difference).
If at the time `swap` was instead presented like this:
template <class T>
void
swap(T& a, T& b)
{
T tmp(cast_to_rvalue(a));
a = cast_to_rvalue(b);
b = cast_to_rvalue(tmp);
}
Then people would have looked at that and said:
But why are you casting to rvalue?
The main point:
As it was, using `move`, no one ever asked:
But why are you moving?
As the years went on and the proposal was refined, the notions of lvalue and rvalue were refined into the value categories we have today:
(image shamelessly stolen from dirkgently)
And so today, if we wanted `swap` to precisely say what it is doing, instead of why, it should look more like:
template <class T>
void
swap(T& a, T& b)
{
T tmp(set_value_category_to_xvalue(a));
a = set_value_category_to_xvalue(b);
b = set_value_category_to_xvalue(tmp);
}
And the question everyone should be asking themselves is if the above code is more or less readable than:
template <class T>
void
swap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
Or even the original:
template <class T>
void
swap(T& a, T& b)
{
T tmp(static_cast<T&&>(a));
a = static_cast<T&&>(b);
b = static_cast<T&&>(tmp);
}
In any event, the journeyman C++ programmer should know that under the hood of `move`, nothing more is going on than a cast. And the beginner C++ programmer, at least with `move`, will be informed that the intent is to move from the rhs, as opposed to copy from the rhs, even if they don't understand exactly how that is accomplished.
Additionally, if a programmer desires this functionality under another name, `std::move` possesses no monopoly on this functionality, and there is no non-portable language magic involved in its implementation. For example if one wanted to code `set_value_category_to_xvalue`, and use that instead, it is trivial to do so:
template <class T>
inline
constexpr
typename std::remove_reference<T>::type&&
set_value_category_to_xvalue(T&& t) noexcept
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
In C++14 it gets even more concise:
template <class T>
inline
constexpr
auto&&
set_value_category_to_xvalue(T&& t) noexcept
{
return static_cast<std::remove_reference_t<T>&&>(t);
}
So if you are so inclined, decorate your `static_cast<T&&>` however you think best, and perhaps you will end up developing a new best practice (C++ is constantly evolving).
So what does `move` do in terms of generated object code?
Consider this `test`:
void
test(int& i, int& j)
{
i = j;
}
Compiled with `clang++ -std=c++14 test.cpp -O3 -S`, this produces this object code:
__Z4testRiS_: ## @_Z4testRiS_
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
movl (%rsi), %eax
movl %eax, (%rdi)
popq %rbp
retq
.cfi_endproc
Now if the test is changed to:
void
test(int& i, int& j)
{
i = std::move(j);
}
There is absolutely no change at all in the object code. One can generalize this result to: For trivially movable objects, `std::move` has no impact.
Now lets look at this example:
struct X
{
X& operator=(const X&);
};
void
test(X& i, X& j)
{
i = j;
}
This generates:
__Z4testR1XS0_: ## @_Z4testR1XS0_
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
popq %rbp
jmp __ZN1XaSERKS_ ## TAILCALL
.cfi_endproc
If you run `__ZN1XaSERKS_` through `c++filt` it produces: `X::operator=(X const&)`. No surprise here. Now if the test is changed to:
void
test(X& i, X& j)
{
i = std::move(j);
}
Then there is still no change whatsoever in the generated object code. `std::move` has done nothing but cast `j` to an rvalue, and then that rvalue `X` binds to the copy assignment operator of `X`.
Now lets add a move assignment operator to `X`:
struct X
{
X& operator=(const X&);
X& operator=(X&&);
};
Now the object code does change:
__Z4testR1XS0_: ## @_Z4testR1XS0_
.cfi_startproc
## BB#0:
pushq %rbp
Ltmp0:
.cfi_def_cfa_offset 16
Ltmp1:
.cfi_offset %rbp, -16
movq %rsp, %rbp
Ltmp2:
.cfi_def_cfa_register %rbp
popq %rbp
jmp __ZN1XaSEOS_ ## TAILCALL
.cfi_endproc
Running `__ZN1XaSEOS_` through `c++filt` reveals that `X::operator=(X&&)` is being called instead of `X::operator=(X const&)`.
And that's all there is to `std::move`! It completely disappears at run time. Its only impact is at compile-time where it might alter what overload gets called.
Problem
The `std::move(x)` function doesn't really move anything at all. It is just a cast to an rvalue. Why was this done? Isn't this misleading?