Подтвердить что ты не робот

Разность разрешений перегрузки между gcc и clang с использованием конструктора move и конструктора Derived (Base &&)

GCC (тестируется с 4.9) принимает следующий тестовый тест:

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 (проверено с 3.5) отклоняет его со следующим сообщением об ошибке:

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();
    ^

Кто прав?

4b9b3361

Ответ 1

Я считаю, что Клэнг здесь верен. GCC не должен принимать код.

Причина заключается в том, как разрешение перегрузки для конструкторов для копии объекта, встречающееся в операторе return, указано в [class.copy] p32 (выделено мной):

Когда выполняются критерии для исключения конструктора копирования/перемещения, [...], а подлежащий копированию объект обозначается lvalue, [...], разрешение перегрузки для выбора конструктора для копии сначала выполняются так, как если бы объект был обозначен rvalue. Если первый ошибка перегрузки не выполняется или не выполнялась, , или если тип первый параметр выбранного конструктора не является значением rvalue ссылка на тип объекта (возможно, с квалификацией cv), перегрузка разрешение выполняется снова, считая объект как lvalue.

В этом примере критерии elision удовлетворяются (с помощью первой пули в [class.copy] p31), а объект, который нужно скопировать, обозначается lvalue, поэтому этот параграф применяется.

Сначала разрешается перегрузка, как если бы объект был обозначен rvalue. Конструкторы explicit не являются кандидатами (см. Ниже объяснение причины), поэтому выбирается конструктор Derived(Base&&). Однако это подпадает под "тип первого параметра выбранного конструктора не является ссылкой rvalue на тип объекта" (вместо этого это ссылка rvalue на тип базового класса объекта), поэтому разрешение перегрузки должно быть выполнено снова, рассматривая объект как lvalue.

Это второе разрешение перегрузки выходит из строя, поскольку единственный жизнеспособный конструктор (опять же, конструкторы explicit не являются кандидатами) имеет параметр ссылки rvalue, который не может привязываться к lvalue. Clang показывает полученную ошибку с ошибкой разрешения перегрузки.


Чтобы завершить объяснение, вот почему конструкторы explicit не являются кандидатами для разрешения перегрузки (все внимание мое).

Во-первых, [dcl.init] p15 говорит, что:

Инициализация, возникающая в форме = скользящий или равный-инициализатор или условие (6.4), а также аргумент передача, возвращает функцию, выбрасывая исключение (15.1), обрабатывая исключение (15.3) и инициализация агрегатного члена (8.5.1), называемой копировальной инициализацией.

Далее мы рассмотрим [over.match.ctor] p1:

Для инициализации копирования, функции-кандидаты все преобразования конструкторы (12.3.1) этого класса.

Наконец, мы видим, что конструкторы explicit не преобразуют конструкторы в [class.conv.ctor] p1:

Конструктор объявляет без функции-спецификатора explicit указывает преобразование типов своих параметров в тип своего класса. Такой конструктор называется преобразованием Конструктор.