63

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)
}
Joseph Mansfield
  • 100,738
  • 18
  • 225
  • 303
  • move(lvalue) converts the parameter to an rvalue. forward(lvalue) leaves it as an lvalue. – Andrew Tomazos Nov 04 '12 at 14:46
  • @AndrewTomazos-Fathomling `std::forward(lvalue_expression)` will give an lvalue if the type of `lvalue_expression` is an lvalue and it will give an rvalue if the type of `lvalue_expression` is an rvalue (in the case of a named rvalue, for example). Here, I'm using `std::forward` on an expression that I know has rvalue type. – Joseph Mansfield Nov 04 '12 at 14:48
  • 1
    @Andrew Tomazos - Fathomling - that is false, the whole point of `std::forward` is that it can sometimes change lvalues to rvalues. – Mankarse Nov 04 '12 at 14:48
  • The terminology is a little ambiguous. when I say forward(lvalue), I mean a parameter that has a deduced type T& through reference collapsing. – Andrew Tomazos Nov 04 '12 at 14:55
  • possible duplicate of [Why parameters of universal reference needs to be casted, before used?](http://stackoverflow.com/questions/12837327/why-parameters-of-universal-reference-needs-to-be-casted-before-used) – BЈовић Nov 04 '12 at 18:43
  • @BЈовић I don't think it's a duplicate. We're referring to the same talk and the same topic but asking different questions. – Joseph Mansfield Nov 04 '12 at 21:05
  • 8
    [Response from Scott Meyers](http://scottmeyers.blogspot.co.uk/2012/11/on-superfluousness-of-stdmove.html). – Joseph Mansfield Nov 12 '12 at 08:23

2 Answers2

70

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.

Kerrek SB
  • 428,875
  • 83
  • 813
  • 1,025
  • As much as I don't want to, I have to give +1 from this, as I'd have basically answered the same. Damn you, if this goes on, you'll really get 4th C++11 gold badge. :P – Xeo Nov 04 '12 at 16:25
  • Hey, you don't have it yet. :) You need a few more upvotes in the tag, and I still hope to get my "few more answers" before you get those votes. :P – Xeo Nov 04 '12 at 21:00
  • why would any one move a unique_ptr ? Do I miss something? Isnt the above example above trying to move the storage of a unique_ptr? Why does this make sense? – Gabriel Dec 11 '13 at 19:09
  • 3
    @Gabriel: Without the move, it wouldn't work. `std::unique_ptr` can't be copied. – Ben Voigt Oct 09 '14 at 03:18
  • 3
    @Gabriel: You misunderstand: You always move the resource *handle*, never the resource itself. Resources by their definition are unmovable entities; what's movable is the resource *ownership*. – Kerrek SB Oct 09 '14 at 09:26
  • Thanks for the clarification, that was years ago when I was not yet so sure about rvalue references and so on! – Gabriel Oct 10 '14 at 07:22
  • It's worth noting that std::forward makes sense in the case when rvalue reference is actually _forwarding_ reference. Otherwise, forward will not do the forwarding of supplied argument, but will move it, even if T is deduced template parameter. It's not safe to use forward for parameters not declared as T&&. – Григорий Шуренков Jan 10 '15 at 13:39
  • @KerrekSB "... and magically creates an lvalue reference or an rvalue expression depending on what the type was...". This should be corrected because it is wrong. An rvalue expression is not a type, it is a value category. You probably meant to say rvalue reference (not expression). – KeyC0de Oct 01 '18 at 17:24
  • @Nik-Lz: I think it should be corrected to "creates an lvalue or rvalue expression" -- the result of a function call is an expression, not a type, right? – Kerrek SB Oct 03 '18 at 00:53
  • @KerrekSB Yeah, sure. That's correct. Well an expresion has: 1. a type, 2. a value category. But yeah, what you say is correct. – KeyC0de Oct 03 '18 at 00:55
14

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
}
Mankarse
  • 37,343
  • 9
  • 88
  • 138