Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overload resolution difference between gcc and clang involving move constructor and 'Derived(Base&&)' constructor

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?

like image 239
HighCommander4 Avatar asked Apr 23 '15 21:04

HighCommander4


1 Answers

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.

like image 74
HighCommander4 Avatar answered Nov 12 '22 09:11

HighCommander4