Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do I need to use std::move in the initialization list of a move-constructor?

Let's say I have a (trivial) class, which is move-constructible and move-assignable but not copy-constructable or copy-assignable:

class movable
{
  public:
    explicit movable(int) {}
    movable(movable&&) {}
    movable& operator=(movable&&) { return *this; }
    movable(const movable&) = delete;
    movable& operator=(const movable&) = delete;
};

This works fine:

movable m1(movable(17));

This, of course, does not work, because m1 is not an rvalue:

movable m2(m1);

But, I can wrap m1 in std::move, which casts it to an rvalue-reference, to make it work:

movable m2(std::move(m1));

So far, so good. Now, let's say I have a (equally trivial) container class, which holds a single value:

template <typename T>
class container
{
  public:
    explicit container(T&& value) : value_(value) {}
  private:
    T value_;
};

This, however, does not work:

container<movable> c(movable(17));

The compiler (I've tried clang 4.0 and g++ 4.7.2) complains that I'm trying to use movable's deleted copy-constructor in container's initialization list. Again, wrapping value in std::move makes it work:

    explicit container(T&& value) : value_(std::move(value)) {}

But why is std::move needed in this case? Isn't value already of type movable&&? How is value_(value) different from movable m1(movable(42))?

like image 689
sarnesjo Avatar asked Oct 30 '12 12:10

sarnesjo


2 Answers

That's because value is a named variable, and thus an lvalue. The std::move is required to cast it back into an rvalue, so that it will cause move-constructor overload of T to match.

To say it another way: An rvalue reference can bind to an rvalue, but it is not itself an rvalue. It's just a reference, and in an expression it is an lvalue. The only way to create from it an expression that is an rvalue is by casting.

like image 131
Kerrek SB Avatar answered Nov 19 '22 07:11

Kerrek SB


How is value_(value) different from movable m1(movable(42))?

A named rvalue reference is an lvalue (and will thus bind to the deleted copy ctor), while a temporary is, well, an rvalue (a prvalue to be specific).

§5 [expr] p6

[...] In general, the effect of this rule is that named rvalue references are treated as lvalues and unnamed rvalue references to objects are treated as xvalues [...]

Aswell as from the example:

A&& ar = static_cast<A&&>(a);

The expression ar is an lvalue.

The above quotes are from non-normative notes, but are an adequate explanation, since the rest of clause 5 goes and explains which expressions only create xvalues (aka, only the specified expressions and none else will create xvalues). See also here for an exhaustive list.

† xvalues are one subgroup of rvalues, with prvalues being the other subgroup. See this question for an explanation.

like image 21
Xeo Avatar answered Nov 19 '22 08:11

Xeo