Why Do I Have to Call Move on an Rvalue Reference

Reason to use std::move on rvalue reference parameter

isn't the std::move here unnecessary?

No. Types and value categories are different things.

(emphasis mine)

Each C++ expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category.

The following expressions are lvalue expressions:

the name of a variable, a function, a template parameter object (since
C++20), or a data member, regardless of type, such as std::cin or
std::endl. Even if the variable's type is rvalue reference, the
expression consisting of its name is an lvalue expression
;

std::move converts lvalue to rvalue (xvalue). As a named variable, x is an lvalue, std::move converts it to rvalue in objects[size++] = std::move(x); then the move assignment operator is supposed to be used. Otherwise, copy assignment operator will be used instead; lvalue can't be bound to rvalue reference.

we still need to use std::move on its member if we want to call move instead of copy right?

Yes, same reason as above.

What happens when std::move is called on a rvalue reference?

string constructed = s;

This does not cause a move because s is not an rvalue. It is an rvalue reference, but not an rvalue. If it has a name, it is not an rvalue. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression.

string constructed = std::move(s);

This causes a move because std::move(s) is an rvalue: it's a temporary and its type is not lvalue reference.

There are no other moves in the program (std::move is not a move, it's a cast).

Why do I have to call move on an rvalue reference?

As skypjack correctly comments, accessing an object through its name always results in an lvalue reference.

This is a safety feature and if you think it through you will realise that you are glad of it.

As you know, std::move simply casts an l-value reference to an r-value reference. If we use the returned r-value reference immediately (i.e. un-named) then it remains an r-value reference.

This means that the use of the r-value can only be at the point in the code where move(x) is mentioned. From a code-reader's perspective, it's now easy to see where x's state became undefined.

so:

 1: auto x = make_x();
2: auto&& r = std::move(x);
3: // lots of other stuff
35: // ...
54: // ...
55: take_my_x(r);

does not work. If it did, someone maintaining the code would have a hard time seeing (and remembering) that x (defined on line 1) enters an undefined state on line 55 through a reference taken on line 2.

This is a good deal more explicit:

 1: auto x = make_x();
2: //
3: // lots of other stuff
35: // ...
54: // ...
55: take_my_x(std::move(x));

Why to use std::move despite the parameter is an r-value reference

Every named object is a Lvalue:

the name of a variable, a function, a template parameter object (since
C++20), or a data member, regardless of type, such as std::cin or
std::endl. Even if the variable's type is rvalue reference, the
expression consisting of its name is an lvalue expression;

vector has two overloads of assignment operator, one for Lvalue reference and another for Rvalue reference.

vector::operator=(const vector&) // copy assignment operator
vector::operator=(vector&&) // move assignment operator

Overload which takes Lvalue reference is called when Lvalue is passed as argument for operator=.
Details here

when a function has both rvalue reference and lvalue reference
overloads, the rvalue reference overload binds to rvalues (including
both prvalues and xvalues), while the lvalue reference overload binds
to lvalues

By std::move(rv); you cast rv - Lvalue to Rvalue reference, and operator= which takes Rvalue reference is called. Otherwise, Lvalue binds to Lvalue reference and vector is copied instead of being moved.

Why to move rvalue reference in a move constructor?

In short: every named object is Lvalue, and even if v is reference to Rvalue you need to use move to force move ctor to be called.

From reference - value categories

Even if the variable's type is rvalue reference, the expression
consisting of its name is an lvalue expression;

now your data member m_v is vector which contains copy and move constructor.
Next sentences describe which one is called, reference:

If both copy and move constructors are provided and no other
constructors are viable, overload resolution selects the move
constructor if the argument is an rvalue of the same type (an xvalue
such as the result of std::move or a prvalue such as a nameless
temporary (until C++17)), and selects the copy constructor if the
argument is an lvalue (named object or a function/operator returning
lvalue reference).

So when you write:

m_v(std::move(v)) // move ctor is called because you are passing Xvalue

but in this:

m_v(v) // copy ctor is called because v as named object is passed

Why move return an rvalue reference parameter need to wrap it with std::move()?

In passThroughMove(Widget&& w), w's type is already rvalue reference, std::move(w) just cast it into rvalue reference again.

So std::move(w) returns an rvalue reference, just as w itself.

No, std::move(w) casts to rvalue, while rvalue references are lvalues.

Both functions passThroughMove and passThrough return by value.
They differ, however, in the way they create internally such return value.
Internally, passThroughMove creates its return value by move. A new Widget object (the return value) is created by moving into it, that's the effect of std::move on the return value. passThrough on the other hand creates its own return value by copy.

The fact that the assignment

Widget wt2 = passThrough(std::move(w2));

is done from an rvalue does not change the fact that passThrough is forced to create its return value by copy.

In the output of the code you see the effect of the above semantics plus RVO. Without RVO both assignments should result into two additional move-constructions, which are optimized away.

C++ Why does returning rvalue reference change caller's behavior when function signature does not return rvalue reference?

What's happening is you are seeing elision there. You are move-constructing on return std::move(x) with a simple type of Bar; then the compiler is eliding the copy.

You can see the non-optimized assembly of GetBarRValue here. The call to the move constructor is actually happening in the GetBarRValue function, not upon returning. Back in main, it's just doing a simple lea, it's not at all calling any constructor.

When should we declare rvalue refs if they actually need std::move?

My question is, what's the utility of defining a variable as A&&? I could perfectly define it as A& and move it exactly the same.

If a named variables is passed into a function and modified like that without warning it's very displeasing, and can lead to unpleasant debugging sessions.

When you take by rvalue reference the type system enforces useful guarantees. Either the reference is bound to an rvalue, and so modifying it isn't going to violate anyone's assumptions (it's gone soon anyway). Or you were given explicit permission to move out of it. The calling context can't accidentally give you permission to modify an lvalue, it must do so with an explicit cast (std::move).

Explicit is better than implicit. And having rvalue references interact with the type system the way they do helps make sure moving does not leave objects in an unexpected state. A glance at the calling context reveals that the variable passed into the function may be left with an unknown state, because it's explicitly surrounded with a call to std::move.

That's the benefit of rvalue references.

Why does std::move take rvalue reference as argument?

It's not an rvalue reference, but a forwarding reference; which could preserve the value category of the argument. That means std::move could take both lvalue and rvalue, and convert them to rvalue unconditionally.

Forwarding references are a special kind of references that preserve the value category of a function argument, making it possible to forward it by means of std::forward. Forwarding references are either:

1) function parameter of a function template declared as rvalue
reference to cv-unqualified type template parameter of that same
function template:

2) auto&& except when deduced from a brace-enclosed initializer list.

On the other hand, int&& is an rvalue reference; note the difference here, if a function template parameter has type T&& with template parameter T, i.e. a deduced type T, the parameter is a forwarding reference.



Related Topics



Leave a reply



Submit