Why is the copy constructor called instead of the move constructor when returning?

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 returning local variables) without any strange antics. And, as a bonus, the << call is completely standard-compliant.


Your operator return by MyClass&. So you are returning an lvalue, not an rvalue that can be moved automatically.

You can avoid the copy by relying on the standard guarantees regarding NRVO.

MyClass func()
{
    MyClass m;
    m << 5;
    return m;
}

This will either elide the object entirely, or move it. All on account of it being a function local object.


Another option, seeing as you are trying to call operator<< on an rvalue, is to supply an overload dealing in rvalue references.

MyClass&& operator<<(MyClass&& target, int i) {
    target << i; // Reuse the operator you have, here target is an lvalue
    return std::move(target);
}

That will make MyClass() << 5 itself well formed (see the other answer for why it isn't), and return an xvalue from which the return object may be constructed. Though such and overload for operator<< is not commonly seen.