Should a Move Constructor Take a Const or Non-Const Rvalue Reference

Should a move constructor take a const or non-const rvalue reference?

It should be a non-const rvalue reference.

If an object is placed in read-only memory, you can't steal resources from it, even if its formal lifetime is ending shortly. Objects created as const in C++ are allowed to live in read-only memory (using const_cast to try to change them results in undefined behavior).

Why is a non-const rvalue move constructor called in this case?

Here is a slightly modified version of your code:

#include <iostream>

#if 0
using T = int;
#else
struct T {T(int){}};
#endif

using namespace std;
class A {
public:
A (T const &&i) { cout << "const rvalue constructor"; }
A (T &&i) { cout << "non const rvalue constructor"; }
};

T const
foo (void)
{
const T i = 3;
return i;
}

int main()
{
A a(foo());
}

When T == int, you get the non-const overload. When T is a class type, you get the const overload. This behavior falls out of section 8.2.2 [expr.type]/p2:

If a prvalue initially has the type “cv T”, where T is a cv-unqualified non-class, non-array type, the type of the expression is adjusted to T prior to any further analysis.

Translation: The language doesn't have const-qualified scalar prvalues. They simply don't exist.

Why is const rvalue reference implicitly converted into const reference?

Some simpler code with the same reference binding:

int main()
{
const foo f;
const foo& g = std::move(f);
const foo&& h = std::move(f);
}

The expression std::move(f) is best described as an xvalue of type const foo.

So we have a reference being initialized by an xvalue of type const foo.

Both of g and h are legal and the reference binds directly. const foo&& can only bind to an rvalue, but const foo& can bind to both lvalues and rvalues. (An xvalue is an rvalue).

There is no implicit conversion as suggested in the title, the reference binds directly to the object designated by the initializer.


In the posted code there is overload resolution between two constructors with the same binding as g and h in my example. Overload resolution selects parameter type const foo&& as a better match than const foo& to an argument of type const foo and category "xvalue", since there is a ranking rule that rvalue arguments prefer rvalue references if all else is equal.

But if you remove either of those two constructors then the other one will be selected.

Why is `T(const T&&)` called a move constructor?

Here are some of the differences between move constructors and other constructors:

  • Move constructors can be defaulted
  • Move constructors don't prevent a type from being a "literal type"
  • Non-trivial move constructors prevent a type from being a "trivially copyable type"
  • Move constructors prevent the implicit move constructor from being generated
  • Move constructors may be automatically called by standard library functions

As far as I can tell, for all of those, not calling X(const X &&) a move constructor gives undesirable results.

You give an alternative: it might be called a copy constructor instead. That too seems to have undesirable results: it would suppress the implicit copy constructor.

Whether a move constructor actually moves doesn't matter. A POD type may have a move constructor too. It'll just be making a copy, but it's still called a move constructor.

Constructor - Copying Const Reference VS Moving Copied Value

Don't use strings but a type where you can observe copying and moving:

#include <iostream>

struct test {
test() { std::cout << "constructor\n"; }
test(const test&) { std::cout << "copy\n";}
test(test&&) { std::cout << "move\n"; }
};

class Foo {
public:
Foo(test t) : t(std::move(t)) {}
private:
test t;
};

class Bar {
public:
Bar(const test& t) : t(t) {}
private:
test t;
};

int main() {
test t1;
Foo f{t1};
std::cout << "...........\n";
test t2;
Bar b{t2};
}

Output:

constructor
copy
move
...........
constructor
copy

Calling Foos constructor copies t1 to the constructors parameter, because it is taken by value and then the member is move constructed from the parameter.

Calling Bars constructor takes a reference (no copy, no move) and uses that to copy construct the member.


If the intention was to avoid the copying, you would provide a constructor that takes a test&&:

struct Baz {
Baz(test&& t) : t(std::move(t)) {}
test t;
};

Then Baz z(test{}); will construct a test and the member will be move constructed from that temporary. No copies involved.

Why rvalue reference member would be const?

Why rvalue reference member would be const?

Don't assume that it's const. You should assume that unique_ptr(const unique_ptr&) is merely the best match, from the available constructors.

Because in constructor temp is a rvalue reference

Surprise! It is not an r-value reference.

The variable temp is bound to an r-value, when the constructor is called. And now that it's a named variable, it's no longer a "temporary". It has become an l-value.

Since you know that the value was an r-value when the constructor was called, you can safely move the members, converting them back to an r-value.

C(C &&temp)
: mVector(std::move(temp.mVector))
// ^^^^^^^^^ We know that temp CAME FROM an r-value,
// so it can safely be moved.
, mSize(temp.mSize)
{}

C++11 rvalue reference vs const reference

std::move doesn't actually move anything out of it's own. It's just a fancy name for a cast to a T&&. Calling test like this test(std::move(x)); only shows that a T&& is implicitly convertible to a const T&. The compiler sees that test only accepts const T& so it converts the T&& returned from std::move to a const T&, that's all there is to it.

Returning const reference parameter without copying

You can't move from a const value, so the function can't take by const &.

You could do this, so the caller has to supply an rvalue, either moving or explicitly copying lvalues they wish to pass.

heavy_obj return_obj_or_default(heavy_obj&& t, bool ret) {
if(ret) {
return std::move(t);
} else {
return heavy_obj();
}
}

Or you could do this, so the caller doesn't have to do anything special, but it will implicitly copy lvalues.

heavy_obj return_obj_or_default(heavy_obj t, bool ret) {
if(ret) {
return std::move(t);
} else {
return heavy_obj();
}
}

Confusion between rvalue references and const lvalue references as parameter

If a move constructor accepted a const lvalue reference, then such declaration of a move constructor would be indistinguishable from a copy constructor. There has to be a way to distinguish them, and the language has been specified such that a move constructor takes an rvalue reference as the argument.

A move constructor that would accept a const lvalue reference would not allow the moved from object to be modified, so you couldn't do anything that you couldn't do in a copy constructor. In fact, such move constructor would be in every way identical to a copy constructor. Why would one call something a move constructor when it is exactly the same as a copy constructor?


PS. Your experiment reveals an interesting fact: As long as a class has a copy constructor (and the move constructor is not explicitly deleted), whether it has a move constructor or not does not affect how that object can be used. In any case where a move is appropriate, a copy can be used instead if the class has no move constructor.



Related Topics



Leave a reply



Submit