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

(Ошибка GCC?) Неявное преобразование в производный класс

Я столкнулся с проблемой неявного преобразования в С++. Ниже приведен минимальный пример:

struct A {
  virtual void f()=0; // abstract
};

struct Ad : A {
  virtual void f() {} // not abstract
};

struct B {
  operator Ad () const { return Ad(); }
};

void test(A const &lhs) {}

int main()
{
  B b;
  test(b);
}

То, что я хотел бы сделать компилятору: конвертировать b в переменную типа Ad (используя преобразование, определенную в B), и передать результат для проверки. Однако приведенный выше код не компилируется в GCC (с включенным С++ 11), результат не может выделить объект абстрактного типа "A".

Некоторые примечания:

  • Clang компилирует это.
  • Если вы делаете A не абстрактным, изменяя f()=0; на f() {}, код работает очень хорошо.
  • Компилятор находит оператор преобразования (как указано 2), но он не делает того, что я хотел бы сделать.
4b9b3361

Ответ 1

(Все цитаты из N4140, С++ 14 FD)

TL; DR: код хорошо сформирован, это (или была) ошибка GCC.

Правила инициализации ссылок описаны в [dcl.init.ref]/5. Сначала я покажу вам пулю, которая не покрывает ее, - если вы хотите пропустить это, переходите к третьей цитате.

В противном случае ссылка должна быть ссылкой lvalue на нелетучий тип const (т.е. cv1 должен быть const), или ссылка должна быть ссылкой rvalue.

  • Если выражение инициализатора
    • - это значение xvalue (но не бит-поле), класс prvalue, значение prvalue массива или функция lvalue и "cv1 T1" ссылаются на "cv2 T2" , или
    • имеет тип класса (т.е. T2 - тип класса), где T1 не ссылается на T2, а может быть преобразован в значение x, class prvalue или функция lvalue типа "cv3 T3", где "cv1 T1" ссылается на "cv3 T3" (см. 13.3.1.6)

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

И ссылочная компиляция определена в [dcl.init.ref]/4 1.
Теперь рассмотрим связанный 13.3.1.6:

В условиях, указанных в 8.5.3, ссылка может быть связана непосредственно к значению glvalue или классу, которое является результатом применения функция преобразования в выражение инициализатора. перегрузка разрешение используется для выбора функции преобразования для вызова. Предполагая, что "cv1 T" является базовым типом ссылки инициализируется, а "cv S" - это тип инициализатора выражение, S тип класса, функции-кандидата выбирается следующим образом:

  • Рассматриваются функции преобразования S и его базовые классы. Те неявные функции преобразования, которые не являются скрытый в S и тип вывода "lvalue reference to cv2 T2" ( при инициализации ссылки lvalue или ссылки rvalue на функция) или "cv2 T2" [..], где "cv1 T" является ссылочным (8.5.3) с "cv2 T2" , являются кандидатскими функциями. Для прямой инициализации [..].

Как вы можете видеть, ваша функция преобразования не является кандидатом после этого абзаца. Таким образом, следующая пуля в [dcl.init]/5 применима:

В противном случае:

  • Если T1 - тип класса, определяемые пользователем преобразования рассматриваются с использованием правил для копирования-инициализации объекта типа "cv1 T1" путем пользовательского преобразования (8.5, 13.3.1.4); плохо сформированный, если соответствующая неосновная копия-инициализация было бы плохо сформировано. Результат обращения к конверсии функция, как описано для инициализации без ссылки, является затем используется для прямого инициализации ссылки. Программа плохо сформированный, если прямая инициализация не приводит к прямой или если оно связано с пользовательским преобразованием.

Обратите внимание, что фраза "программа  плохо сформированный, если соответствующая неосновная копия-инициализация  было бы плохо сформировано "может означать, что как

B b;
A a = b;

плохо сформирован, программа плохо сформирована. Я считаю, что это недостаток или неопределенность в формулировке, но не причина, по которой GCC не принимает код. Разумеется, формулировка направлена ​​исключительно на инициализацию, а не на то, что в первую очередь может быть создан самый производный объект типа T1 (aka A).
Наконец, 13.3.1.4 принимает нашу функцию преобразования:

Предполагая, что "cv1 T" является типом инициализированного объекта, с T тип класса, выбранные функции выбираются как следующим образом:

  • Конструкторы преобразования (12.3.1) из T являются функциями-кандидатами.
  • Когда тип выражения инициализатора представляет собой тип класса "cv S", неявные функции преобразования S и его базы классы. [..]. Те, которые не скрыты внутри Sи выдать тип, чья неквалифицированная версия имеет тот же тип, что и T или является производным классом, являются кандидатскими функциями.

Теперь последний вопрос: в

A const& ref(Ad());

ref связан непосредственно. И это так. Таким образом, ссылка на исходный параметр привязывается напрямую, и не должен быть создан самый производный объект типа A.
Предположительно, что GCC считает, что временный тип A должен быть инициализирован, и ссылка должна быть привязана к этому временному. Или это педантично следует вышеперечисленной формулировке, что очень маловероятно.


1)

Указанные типы "cv1 T1" и "cv2 T2" , "cv1 T1" ссылаются на "cv2 T2" , если T1 - это тот же тип, что и T2, или T1 - базовый класс T2. "cv1 T1" ссылается на "cv2 T2" , если T1ссылка, связанная с T2, а cv1 - это та же самая cv-квалификация, что и или более высокая cv-квалификация, чем cv2.