C++ std::move and std::forward
C++ std::move does not move and std::forward does not forward. This article dives deep into a long list of rules on lvalues, rvalues, references, overloads and templates to be able to explain a few deceivingly simple lines of code using std::move and std::forward.
Motivation
N4543 suggests a solution for when the content of a std::function is
not copyable. But it first starts with the code below (and I’m going to ignore
the rest of N4543 here). It has a commands variable that maps strings to
functions, and an utility function to insert a new command.
1
2
3
4
5
6
7
8
9
10
std::map<std::string, std::function<void()>> commands;
template<typename ftor>
void install_command(std::string name, ftor && handler)
{
commands.insert({
std::move(name),
std::forward<ftor>(handler)
});
}
The code above is easy to read, but has subtleties. The goal of the article is
to provide enough background information to be able to understand in detail
each line, in particular why does it use std::move in one place (at line 7)
and std::forward in another (at line 8).
There is a lot of background information required. We’ll start with the almost mathematical theory, but then dive quickly into C++ specifics and historical artefacts.
Value categories (lvalue, prvalue and xvalue)
A C++ expression has, in addition to a type, a value category. Traditionally
the main value categories were lvalue and rvalue with a rough meaning that
if the expression could stand on the left side of an assignment it’s an
lvalue, otherwise it’s an rvalue.
With the advent of C++ 11, additional value categories have been identified and organized in a systematic way based on the observation that there are two important properties of an expresion: has identity (i.e. ‘can we get its address’) and can be moved from.
The naming of the main value categories is illustrated using Venn diagrams below.
- If it has identity, but cannot be moved it’s an
lvalue; otherwise it’s anrvalue. A typicallvalueis a variable namea. - If it can be moved, but has no identity is a
prvalue(pure right value); otherwise it’s aglvalue(generalized left value). A typicalprvalueis a temporary resulting from a function call/operator (with a non-reference return type) likes.substr(1, 2)ora + bor integral constant like42. - If it has an identity and can be moved it’s an
xvalue(because that was considered strange, andxis a good prefix for weird things). A typicalxvalueisstd::move(a).
The above categories are the main ones. There are additional
ones (e.g. void has a category with no identity and that
can’t be moved from), but I’m going to skip over them in this article.
References as function parameters (are lvalues)
References as function parameters are relevant here because they allow us to bind to arguments depending on their value category.
1
2
3
4
5
6
7
8
// i is a function parameter
void fn(int i) { }
int main() {
int j = 42;
// j is a function argument
fn(j);
}
There are two types of reference declarations in C++. The pre-C++ 11 is called
now lvalue reference (and uses one &), and the new C++ 11 called rvalue
reference (that looks like &&).
If a function has lvalue reference parameter, then it can be called with an
lvalue argument, but not an rvalue argument.
1
2
3
4
5
6
7
8
9
10
// parameter is lvalue reference
void fn(X &) { std::cout<< "X &\n"; }
int main()
{
X a;
fn(a); // works, argument is an lvalue
fn(X()); // compiler error, argument is an rvalue
}
Similarly if a function has a rvalue reference parameter, then it can be
called with an rvalue argument, but not an lvalue argument.
1
2
3
4
5
6
7
8
9
10
// parameter is rvalue reference
void fn(X &&) { std::cout<< "X &&\n"; }
int main()
{
X a;
fn(a); // compiler error, argument is an lvalue
fn(X()); // works, argument is an rvalue
}
But when used inside the function body, a parameter, whether lvalue
reference or rvalue reference, is an lvalue itself: it has a name like any
other variable.
1
2
3
4
5
6
// parameter is rvalue reference
void fn(X && x)
{
// but here expression x has an lvalue value category
// can use std::move to convert it to an xvalue
}
From here, things get even more complicated when one notices that const
matters. In the example below, fn can be called with both an lvalue and an
rvalue argument. This is pre-C++ 11 behaviour that is unchanged.
1
2
3
4
5
6
7
8
9
10
// parameter is const lvalue reference
void fn(const X &) { std::cout<< "const X &\n"; }
int main()
{
X a;
fn(a); // works, argument is an lvalue
fn(X()); // also works, argument is an rvalue
}
References and function overloads
We could provide overloads for fn, and we end up with three main
overload options. If for an expression the preferred overload is not available,
there is a fallback mechanism until all options are exhausted, and then we get
a compiler error.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct X {};
// overloads
void fn(X &) { std::cout<< "X &\n"; }
void fn(const X &) { std::cout<< "const X &\n"; }
void fn(X &&) { std::cout<< "X &&\n"; }
int main()
{
X a;
fn(a);
// lvalue selects fn(X &)
// fallbacks on fn(const X &)
const X b;
fn(b);
// const lvalue requires fn(const X &)
fn(X());
// rvalue selects fn(X &&)
// and then on fn(const X &)
}
In addition to the three overloads above there is of course the option of the
overload with a const X && argument, but I’m going to skip over it in this
article.
A typical usage of this rules is for the typical copy/move
constructor/assignment quadruple. For a user defined class X we expect two
overloads requiring:
const X &for copy constructor or assignmentX &&for the move constructor or assignment
Template argument deduction and reference collapsing rules
If a templated function declares an argument as an rvalue reference to one of
its template parameters, special template argument deduction rules kick
in. Despite the syntactic similarities with the rvalue reference
rules above, the rules for this case were specifically designed to support
argument forwarding and are called forwarding references.
1
2
3
4
5
6
7
8
template<typename T>
void foo(T &&); // forwarding reference here
// T is a template parameter for foo
template<typename T>
void bar(std::vector<T> &&); // but not here
// std::vector<T> is not a template parameter,
// only T is a template parameter for bar
The rules allow the function foo above to be called with either an lvalue
or an rvalue:
- When called with an
lvalueof typeX, thenTresolves toX & - When called with and
rvalueof typeX, thenTresolves toX
When applying these rules we end up with an argument being X & &&. So there
are even more rules to collapse the outcome:
X & &collapses toX &X & &&collapses toX &X && &collapses toX &X && &&collapses toX &&
Combining the two rules we can have:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
template<typename T>
void fn(T &&) { std::cout<< "template\n"; }
int main()
{
X a;
fn(a);
// argument expression is lvalue of type X
// resolves to T being X &
// X & && collapses to X &
fn(X());
// argument expression is rvalue of type X
// resolves to T being X
// X && stays X &&
}
static_cast<X &&>
Once we have an expression of a value category, we can convert it to an expression
of a different value category. If we have a rvalue we can assign it to a
variable, or take a reference, hence becoming a lvalue. If we have a lvalue
we can return it from a function, so we get a rvalue.
But one important rule is that: one can covert from a lvalue to a rvalue
(to an xvalue more precisely) by using static_cast<X &&> without creating
temporaries. And this is the last piece of the puzzle to understand
std::move and std::forward.
std::move
The idiomatic use of std::move is to ensure the argument passed to a function
is an rvalue so that you can move from it (choose move semantics). By
function I mean an actual function or a constructor or an operator (e.g.
assignment operator).
Here is an example where std::move is used twice in an idiomatic way:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct X
{
std::string s_;
X(){}
X(const X & other) : s_{ other.s_ } {}
X(X && other) noexcept : s_{ std::move(other.s_) } {}
// other is an lvalue, and other.s_ is an lvalue too
// use std::move to force using the move constructor for s_
// don't use other.s_ after std::move (other than to destruct)
};
int main()
{
X a;
X b = std::move(a);
// a is an lvalue
// use std::move to convert to a rvalue,
// xvalue to be precise,
// so that the move constructor for X is used
// don't use a after std::move (other than to destruct)
}
Here is a possible implementations for std::move.
1
2
3
4
5
6
7
8
9
template<typename T> struct remove_reference { typedef T type; };
template<typename T> struct remove_reference<T&> { typedef T type; };
template<typename T> struct remove_reference<T&&> { typedef T type; };
template<typename T>
constexpr typename remove_reference<T>::type && move(T && arg) noexcept
{
return static_cast<typename remove_reference<T>::type &&>(arg);
}
First of all std::move is a template with a forwarding reference argument
which means that it can be called with either a lvalue or an rvalue, and
the reference collapsing rules apply.
Because the type T is deduced, we did not have to specify when using
std::move.
Then all it does is a static_cast.
The remove_reference template specializations are used to get the underlying
type for T without any references, and that type is decorated with && for
the static_cast and return type.
In conclusion std::move does not move, all it does is to return a rvalue so
that the function that actually moves, eventually receiving a rvalue
reference, is selected by the compiler.
std::forward
The idiomatic use of std::forward is inside a templated function with an
argument declared as a forwarding reference, where the argument is now
lvalue, used to retrieve the original value category, that it was called
with, and pass it on further down the call chain (perfect forwarding).
Here is an example where std::forward is used twice in an idiomatic way:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
struct Y
{
Y(){}
Y(const Y &){ std::cout << "Copy constructor\n"; }
Y(Y &&) noexcept { std::cout << "Move constructor\n"; }
};
struct X
{
Y a_;
Y b_;
template<typename A, typename B>
X(A && a, B && b) :
// retrieve the original value category from constructor call
// and pass on to member variables
a_{ std::forward<A>(a) },
b_{ std::forward<B>(b) }
{
}
};
template<typename A, typename B>
X factory(A && a, B && b)
{
// retrieve the original value category from the factory call
// and pass on to X constructor
return X(std::forward<A>(a), std::forward<B>(b));
}
int main()
{
Y y;
X two = factory(y, Y());
// the first argument is a lvalue, eventually a_ will have the
// copy constructor called
// the second argument is an rvalue, eventually b_ will have the
// move constructor called
}
// prints:
// Copy constructor
// Move constructor
Here is a possible implementations for std::forward.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template<typename T> struct is_lvalue_reference { static constexpr bool value = false; };
template<typename T> struct is_lvalue_reference<T&> { static constexpr bool value = true; };
template<typename T>
constexpr T&& forward(typename remove_reference<T>::type & arg) noexcept
{
return static_cast<T&&>(arg);
}
template<typename T>
constexpr T&& forward(typename remove_reference<T>::type && arg) noexcept
{
static_assert(!is_lvalue_reference<T>::value, "invalid rvalue to lvalue conversion");
return static_cast<T&&>(arg);
}
First of all std::forward is more complex than std::move. This version is
the result of several iterations.
The type T is not deduced, therefore we had to specify it when using
std::forward.
Then all it does is a static_cast.
The static_assert is there to stop at compile time attempts to convert from
an rvalue to an lvalue (that would have the dangling reference problem: a
reference pointing to a temporary long gone). This is explained in more details
in N2835, but the gist is:
1
2
3
forward<const Y&>(Y()); // does not compile
// static assert in forward triggers compilation failure for line above
// with "invalid rvalue to lvalue conversion"
Some non-obvious properties of std::forward are that the return value can be
more cv-qualified (i.e. can add a const). Also it allows for the case where
the argument and return are different e.g. to forward expressions from derived
type to it’s base type (even some scenarios where the base is derived from as
private).
Conclusion
Going back to the code we started with:
1
2
3
4
5
6
7
8
9
10
std::map<std::string, std::function<void()>> commands;
template<typename ftor>
void install_command(std::string name, ftor && handler)
{
commands.insert({
std::move(name),
std::forward<ftor>(handler)
});
}
The first parameter, name, for the function install_command is passed by
value. That is really a temporary, but has a name, hence it’s
an lvalue expression inside install_command. The second parameter handler
is a forwarding reference. Because it has a name, it’s an lvalue expression
as well inside install_command.
The std::map has an insert overload that accepts an templated rvalue
reference for the key/value pair to insert. For the key we can provide an
rvalue using std::move because really we don’t need name any more. If we
did not use std::move we would do a silly copy. For the value we provide
whatever we the install_command was called with for the handler. We use
std::forward to retrieve the original value category. If for the handler we
provided an rvalue then insert will move from it. If for the handler we
provided an lvalue then insert will copy it.
There are a lot of rules that come into play for the initial deceivingly simple code. They are the result of maintaining backward compatibility and plumbing move semantics and perfect forwarding support on top of that, while making it so that most common scenarios are easy to write and read.
References
- Stroustrup on C++ 11 value category classification: http://www.stroustrup.com/terminology.pdf
- Cppreference with details on value category: http://en.cppreference.com/w/cpp/language/value_category
- Thomas Becker on rvalue references: http://thbecker.net/articles/rvalue_references/section_08.html
- MSDN on rvalue references: https://msdn.microsoft.com/en-us/library/dd293668.aspx
- Final C++ 11 versions of std::forward and std::move: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3143.html
- Use cases for std::forward: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2951.html
- Explaining the static_assert in std::forward: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2835.html
- On reference binding rules: http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2008/n2812.html
- Howard Hinnant on Stack Overflow: http://stackoverflow.com/a/9672202/5495780