Ответ 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
указывает преобразование типов своих параметров в тип своего класса. Такой конструктор называется преобразованием Конструктор.