Consider the following example:
class X {
public:
X() = default;
X(const X&) = default;
X(X&&) = delete;
};
X foo() {
X result;
I'm gonna quote C++17 (n4659), since the wording there is the most clear, but previous revisions say the same, just less clearly to me. Here is [class.copy.elision]/3, emphasis mine:
In the following copy-initialization contexts, a move operation might be used instead of a copy operation:
If the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, or
[...]
overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. — end note ]
So this is why in fact both Clang and GCC will try to call the move c'tor first. The difference in behavior is because Clang adheres to the text in bold differently. Overload resolution happened, found a move c'tor, but calling it is ill-formed. So Clang performs it again and finds the copy c'tor.
GCC just stops in its tracks because a deleted function was picked in overload resolution.
I do believe Clang is correct here, in spirit if anything else. Trying to move the returned value and copying as a fallback is the intended behavior of this optimization. I feel GCC should not stop because it found a deleted move c'tor. Neither compiler would if it was a defaulted move c'tor that's defined deleted. The standard is aware of a potential problem in that case ([over.match.funcs]/8):
A defaulted move special function ([class.copy]) that is defined as deleted is excluded from the set of candidate functions in all contexts.