I've been watching Scott Meyers' talk on Universal References from the C++ and Beyond 2012 conference, and everything makes sense so far. However, an audience member asks a question at around 50 minutes in that I was also wondering about. Meyers says that he does not care about the answer because it is non-idiomatic and would silly his mind, but I'm still interested.
The code presented is as follows:
// Typical function bodies with overloading: void doWork(const Widget& param) // copy { // ops and exprs using param } void doWork(Widget&& param) // move { // ops and exprs using std::move(param) } // Typical function implementations with universal reference: template <typename T> void doWork(T&& param) // forward => copy and move { // ops and exprs using std::forward<T>(param) }
The point being that when we take an rvalue reference, we know we have an rvalue, so we should std::move
it to preserve the fact that it's an rvalue. When we take a universal reference (T&&
, where T
is a deduced type), we want std::forward
to preserve the fact that it may have been an lvalue or an rvalue.
So the question is: since std::forward
preserves whether the value passed into the function was either an lvalue or an rvalue, and std::move
simply casts its argument to an rvalue, could we just use std::forward
everywhere? Would std::forward
behave like std::move
in all cases where we would use std::move
, or are there some important differences in behaviour that are missed out by Meyers' generalisation?
I'm not suggesting that anybody should do it because, as Meyers correctly says, it's completely non-idiomatic, but is the following also a valid use of std::move
:
void doWork(Widget&& param) // move { // ops and exprs using std::forward<Widget>(param) }
std::move takes an object and casts it as an rvalue reference, which indicates that resources can be "stolen" from this object. std::forward has a single use-case: to cast a templated function parameter of type forwarding reference ( T&& ) to the value category ( lvalue or rvalue ) the caller used to pass it.
std::move itself does "nothing" - it has zero side effects. It just signals to the compiler that the programmer doesn't care what happens to that object any more. i.e. it gives permission to other parts of the software to move from the object, but it doesn't require that it be moved.
std::forward If arg is an lvalue reference, the function returns arg without modifying its type. This is a helper function to allow perfect forwarding of arguments taken as rvalue references to deduced types, preserving any potential move semantics involved.
std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.
The two are very different and complementary tools.
std::move
deduces the argument and unconditionally creates an rvalue expression. This makes sense to apply to an actual object or variable.
std::forward
takes a mandatory template argument (you must specify this!) and magically creates an lvalue or an rvalue expression depending on what the type was (by virtue of adding &&
and the collapsing rules). This only makes sense to apply to a deduced, templated function argument.
Maybe the following examples illustrate this a bit better:
#include <utility> #include <memory> #include <vector> #include "foo.hpp" std::vector<std::unique_ptr<Foo>> v; template <typename T, typename ...Args> std::unique_ptr<T> make_unique(Args &&... args) { return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); // #1 } int main() { { std::unique_ptr<Foo> p(new Foo('a', true, Bar(1,2,3))); v.push_back(std::move(p)); // #2 } { v.push_back(make_unique<Foo>('b', false, Bar(5,6,7))); // #3 } { Bar b(4,5,6); char c = 'x'; v.push_back(make_unique<Foo>(c, b.ready(), b)); // #4 } }
In situation #2, we have an existing, concrete object p
, and we want to move from it, unconditionally. Only std::move
makes sense. There's nothing to "forward" here. We have a named variable and we want to move from it.
On the other hand, situation #1 accepts a list of any sort of arguments, and each argument needs to be forwarded as the same value category as it was in the original call. For example, in #3 the arguments are temporary expressions, and thus they will be forwarded as rvalues. But we could also have mixed in named objects in the constructor call, as in situation #4, and then we need forwarding as lvalues.
Yes, if param
is a Widget&&
, then the following three expressions are equivalent (assuming that Widget
is not a reference type):
std::move(param) std::forward<Widget>(param) static_cast<Widget&&>(param)
In general (when Widget
may be a reference), std::move(param)
is equivalent to both of the following expressions:
std::forward<std::remove_reference<Widget>::type>(param) static_cast<std::remove_reference<Widget>::type&&>(param)
Note how much nicer std::move
is for moving stuff. The point of std::forward
is that it mixes well with template type deduction rules:
template<typename T> void foo(T&& t) { std::forward<T>(t); std::move(t); } int main() { int a{}; int const b{}; //Deduced T Signature Result of `forward<T>` Result of `move` foo(a); //int& foo(int&) lvalue int xvalue int foo(b); //int const& foo(int const&) lvalue int const xvalue int const foo(int{});//int foo(int&&) xvalue int xvalue int }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With