GCC (tested with 4.9) accepts the following testcase:
struct Base {};
struct Derived : Base {
Derived();
explicit Derived(const Derived&);
explicit Derived(Derived&&);
explicit Derived(const Base&);
Derived(Base&&);
};
Derived foo() {
Derived result;
return result;
}
int main() {
Derived result = foo();
}
Clang (tested with 3.5) rejects it with the following error message:
test.cpp:13:10: error: no matching constructor for initialization of 'Derived'
return result;
^~~~~~
test.cpp:8:5: note: candidate constructor not viable: no known conversion from 'Derived' to 'Base &&' for 1st argument
Derived(Base&&);
^
test.cpp:4:5: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
Derived();
^
Who is right?
I believe Clang is correct here. GCC should not be accepting the code.
The reason is the way overload resolution for constructors for the object copy occurring in a return
statement is specified in [class.copy] p32
(emphasis mine):
When the criteria for elision of a copy/move constructor are met, [...], and the object to be copied is designated by an lvalue, [...], 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.
In this example, the criteria for elision are met (by the first bullet in [class.copy] p31
) and the object to be copied is designated by an lvalue, so this paragraph applies.
Overload resolution is first attempted as if the object were designated by an rvalue. The explicit
constructors are not candidates (see below for an explanation of why), so the Derived(Base&&)
constructor is selected. However, this falls under "the type of the first parameter of the selected constructor is not an rvalue reference to the object's type" (instead, it's an rvalue reference to the type of the object's base class), so overload resolution should be performed again, considering the object as an lvalue.
This second overload resolution fails, because the only viable constructor (again, the explicit
constructors are not candidates) has an rvalue reference parameter, which cannot bind to the lvalue. Clang shows the resulting overload resolution failure error.
To complete the explanation, here's why explicit
constructors are not candidates for either overload resolution (all emphasis is mine).
First, [dcl.init] p15
says that:
The initialization that occurs in the = form of a brace-or-equal-initializer or condition (6.4), as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1), is called copy-initialization."
Next, we look at [over.match.ctor] p1
:
For copy-initialization, the candidate functions are all the converting constructors (12.3.1) of that class.
Finally, we see that explicit
constructors are not converting constructors in [class.conv.ctor] p1
:
A constructor declared without the function-specifier
explicit
specifies a conversion from the types of its parameters to the type of its class. Such a constructor is called a converting constructor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With