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
”, whereT
is a cv-unqualified non-class, non-array type, the type of the expression is adjusted toT
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 Foo
s constructor copies t1
to the constructors parameter, because it is taken by value and then the member is move constructed from the parameter.
Calling Bar
s 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
Iterate Through a C++ Vector Using a 'For' Loop
Has the New C++11 Member Initialization Feature at Declaration Made Initialization Lists Obsolete
How to Create a Simple Qt Console Application in C++
C++ Vector, What Happens Whenever It Expands/Reallocate on Stack
Is Rvo (Return Value Optimization) Applicable for All Objects
How to Update a Printed Message in Terminal Without Reprinting
Custom Manipulator for C++ iOStream
Assignment Operator Inheritance
Possible Problems with Nominmax on Visual C++
So Why Is I = ++I + 1 Well-Defined in C++11
Reason Why Not to Have a Delete MACro for C++
Dark Transparent Layer Over a Qmainwindow in Qt
Image Scaling and Rotating in C/C++
How to Set a Breakpoint in Gdb That Is Conditional on the Call Stack
What Does Lpcwstr Stand for and How Should It Be Handled With