std::move
The library move
function (§ 13.6.1, p. 533) is a good illustration of a template that uses rvalue references. Fortunately, we can use move
without understanding the template mechanisms that it uses. However, looking at how move
works can help cement our general understanding, and use, of templates.
In § 13.6.2 (p. 534) we noted that although we cannot directly bind an rvalue reference to an lvalue, we can use move
to obtain an rvalue reference bound to an lvalue. Because move
can take arguments of essentially any type, it should not be surprising that move
is a function template.
std::move
Is DefinedThe standard defines move
as follows:
// for the use of typename in the return type and the cast see § 16.1.3 (p. 670)
// remove_reference is covered in § 16.2.3 (p. 684)
template <typename T>
typename remove_reference<T>::type&& move(T&& t)
{
// static_cast covered in § 4.11.3 (p. 163)
return static_cast<typename remove_reference<T>::type&&>(t);
}
This code is short but subtle. First, move
’s function parameter, T&&
, is an rvalue reference to a template parameter type. Through reference collapsing, this parameter can match arguments of any type. In particular, we can pass either an lvalue or an rvalue to move
:
string s1("hi!"), s2;
s2 = std::move(string("bye!")); // ok: moving from an rvalue
s2 = std::move(s1); // ok: but after the assigment s1 has indeterminate value
std::move
WorksIn the first assignment, the argument to move
is the rvalue result of the string
constructor, string("bye")
. As we’ve seen, when we pass an rvalue to an rvalue reference function parameter, the type deduced from that argument is the referred-to type (§ 16.2.5, p. 687). Thus, in std::move(string("bye!"))
:
• The deduced type of T
is string
.
• Therefore, remove_reference
is instantiated with string
.
• The type
member of remove_reference<string>
is string
.
• The return type of move
is string&&
.
• move
’s function parameter, t
, has type string&&
.
Accordingly, this call instantiates move<string>
, which is the function
string&& move(string &&t)
The body of this function returns static_cast<string&&>(t)
. The type of t
is already string&&
, so the cast does nothing. Therefore, the result of this call is the rvalue reference it was given.
Now consider the second assignment, which calls std::move(s1)
. In this call, the argument to move
is an lvalue. This time:
• The deduced type of T
is string&
(reference to string
, not plain string
).
• Therefore, remove_reference
is instantiated with string&
.
• The type
member of remove_reference<string&>
is string
,
• The return type of move
is still string&&
.
• move
’s function parameter, t
, instantiates as string& &&
, which collapses to string&
.
Thus, this call instantiates move<string&>
, which is
string&& move(string &t)
and which is exactly what we’re after—we want to bind an rvalue reference to an lvalue. The body of this instantiation returns static_cast<string&&>(t)
. In this case, the type of t
is string&
, which the cast converts to string&&
.
static_cast
from an Lvalue to an Rvalue Reference Is PermittedOrdinarily, a static_cast
can perform only otherwise legitimate conversions (§ 4.11.3, p. 163). However, there is again a special dispensation for rvalue references: Even though we cannot implicitly convert an lvalue to an rvalue reference, we can explicitly cast an lvalue to an rvalue reference using static_cast
.
Binding an rvalue reference to an lvalue gives code that operates on the rvalue reference permission to clobber the lvalue. There are times, such as in our StrVec reallocate
function in § 13.6.1 (p. 533), when we know it is safe to clobber an lvalue. By letting us do the cast, the language allows this usage. By forcing us to use a cast, the language tries to prevent us from doing so accidentally.
Finally, although we can write such casts directly, it is much easier to use the library move
function. Moreover, using std::move
consistently makes it easy to find the places in our code that might potentially clobber lvalues.