return lvalue move instead of copy?

Given code like

   struct S1 { S1(S1 &&); };
   struct S2: S1 {};
   S1 f(S2 s) { return s; }

and the rules for when s in the return statement is treated like an rvalue when picking the ctor to use ([class.copy.elision]/3 in C++17, [class.copy]/32 in C++14, and [class.copy]/32 plus CWG1579 in C++11), my interpretation is that the code is correct and should pick the S1 move ctor.

However, any version of Clang up to recent trunk rejects the code, trying to call the implicitly deleted S1 copy ctor. GCC >= 8.1 accepts the code, and I wonder whether Clang or GCC (and my interpretation) is wrong.

GCC does not implement one part of the relevant language rule. Specifically, the rule is ([class.copy.elision]p3):

“[After performing overload resolution for an implicit move from an rvalue,] if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly-cv-qualified), [a copy is used instead of a move]”

In your example, the selected constructor is the S1 move constructor, whose parameter type (S1&&) is not an rvalue reference to S2. So a copy is performed instead of a move.

This rule makes some amount of sense: you may not want to “slice out” the base class of an object by move, because that would leave the derived class object in a half-unspecified state (the derived portion would be in its original state but the base class subobject would be in a moved-from state), which could break the class’s invariants and cause the derived object’s destructor to fail.

Thanks, that's the part I missed. (Filed <https://gcc.gnu.org/bugzilla/show_bug.cgi?id=87150> "move ctor wrongly chosen in return stmt (derived vs. base)" now.)