Move semantics in derived-to-base class conversions

RVO Optimisation

If no copy elision takes place [...]

Actually, copy elision will not take place (without if).

From C++ standard class.copy.elision#1:

is permitted in the following circumstances [...]:

-- in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object ([...]) with the same type (ignoring cv-qualification) as the function return type [...]

Technically, when you return a derived class and a slicing operation takes place, the RVO cannot be applied.

Technically RVO works constructing the local object on the returning space on the stack frame.

|--------------|
| local vars   |
|--------------|
| return addr  |
|--------------|
| return obj   |
|--------------|

Generally, a derived class can have a different memory layout than its parent (different size, alignments, ...). So there is no guarantee the local object (derived) can be constructed in the place reserved for the returned object (parent).


Implicit move

Now, what about implicit move?

is the buffer object above guaranteed to be moved from???

In short: no. On the contrary, it is guaranteed the object will be copied!

In this particular case implicit move will not be performed because of slicing.

In short, this happens because the overload resolution fails. It tries to match against the move-constructor (Buffer::Buffer(Buffer&&)) whereas you have a BufferBuild object). So it fallbacks on the copy constructor.

From C++ standard class.copy.elision#3:

[...] if the type of the first parameter of the selected constructor or the return_­value overload is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue.

Therefore, since the first overload resolution fails (as I have said above), the expression will be treated as an lvalue (and not an rvalue), inhibiting the move.

An interesting talk by Arthur O'Dwyer specifically refers to this case. Youtube Video.


Additional Note

On clang, you can pass the flag -Wmove in order to detect this kind of problems. Indeed for your code:

local variable 'buffer' will be copied despite being returned by name [-Wreturn-std-move]

   return buffer;

          ^~~~~~

<source>:20:11: note: call 'std::move' explicitly to avoid copying

   return buffer;

clang directly suggests you to use std::move on the return expression.