Let’s say I have the class MyClass
with a correct move constructor and whose copy constructor is deleted. Now I am returning this class like this:
MyClass func()
{
return MyClass();
}
In this case the move constructor gets called when returning the class object and everything works as expected.
Now let’s say MyClass
has an implementation of the <<
operator:
MyClass& operator<<(MyClass& target, const int& source)
{
target.add(source);
return target;
}
When I change the code above:
MyClass func()
{
return MyClass() << 5;
}
I get the compiler error, that the copy constructor cannot be accessed because it is deleted. But why is the copy constructor being used at all in this case?
Now I am returning this class via lvalue like this:
MyClass func() { return MyClass(); }
No, the returned expression is an xvalue (a kind of rvalue), used to initialise the result for return-by-value (things are a little more complicated since C++17, but this is still the gist of it; besides, you're on C++11).
In this case the move constructor gets called when returning the class object and everything works as expected.
Indeed; an rvalue will initialise an rvalue reference and thus the whole thing can match move constructors.
When I change the code above:
… now the expression is MyClass() << 5
, which has type MyClass&
. This is never an rvalue. It's an lvalue. It's an expression that refers to an existing object.
So, without an explicit std::move
, that'll be used to copy-initialise the result. And, since your copy constructor is deleted, that can't work.
I'm surprised the example compiles at all, since a temporary can't be used to initialise an lvalue reference (your operator's first argument), though some toolchains (MSVS) are known to accept this as an extension.
then would return
std::move(MyClass() << 5);
work?
Yes, I believe so.
However that is very strange to look at, and makes the reader double-check to ensure there are no dangling references. This suggests there's a better way to accomplish this that results in clearer code:
MyClass func()
{
MyClass m;
m << 5;
return m;
}
Now you're still getting a move (because that's a special rule when return
ing local variables) without any strange antics. And, as a bonus, the <<
call is completely standard-compliant.