Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constructor called on return statement

Consider the following example:

class X {
public:
    X() = default;
    X(const X&) = default;
    X(X&&) = delete;
};

X foo() {
    X result;
    return result;
}

int main() {
    foo();
}

Clang and GCC disagree on whether this program is valid. GCC tries to call the move constructor when initializing the temporary during the call to foo(), which has been deleted leading to a compilation error. Clang handles this just fine, even with -fno-elide-constructors.

Can anyone explain why GCC is allowed to call the move constructor in this case? Isn't result an lvalue?

like image 931
Alessandro Power Avatar asked Dec 19 '17 06:12

Alessandro Power


People also ask

Can a constructor have return statement?

You do not specify a return type for a constructor. A return statement in the body of a constructor cannot have a return value.

Is copy constructor called on return?

4 – Function return value If a function returns an object from a function (by value) then a copy of the object is made. The copy constructor is invoked on return.

What is the return statement of a constructor?

There are no “return value” statements in the constructor, but the constructor returns the current class instance.

Does a constructor need a return statement?

Usually, constructors do not have a return statement. Their task is to write all necessary stuff into this , and it automatically becomes the result. But if there is a return statement, then the rule is simple: If return is called with an object, then the object is returned instead of this .


1 Answers

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.

like image 162
StoryTeller - Unslander Monica Avatar answered Sep 25 '22 23:09

StoryTeller - Unslander Monica