Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I typically/always use std::forward instead of std::move?

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) } 
like image 703
Joseph Mansfield Avatar asked Nov 04 '12 14:11

Joseph Mansfield


People also ask

What is the difference between std :: forward and std :: move?

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.

Do I need std :: move?

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.

What is std :: forward for?

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.

What happens when you to std :: move?

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.


2 Answers

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.

like image 175
Kerrek SB Avatar answered Oct 05 '22 15:10

Kerrek SB


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 } 
like image 31
Mankarse Avatar answered Oct 05 '22 15:10

Mankarse