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))
?
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.
How is
value_(value)
different frommovable 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.
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