Тернарный оператор неявный приведение в базовый класс

Рассмотрим этот фрагмент кода:

struct Base
{
    int x;
};

struct Bar : Base
{
    int y;
};

struct Foo : Base
{
    int z;
};

Bar* bar = new Bar;
Foo* foo = new Foo;

Base* returnBase()
{
    Base* obj = !bar ? foo : bar;
    return obj;
}

int main() {
    returnBase();
    return 0;
}

Это не работает под Clang или GCC, давая мне:

ошибка: условное выражение между различными типами указателей "Foo *" и "Bar" отсутствует литой Base * obj =! bar? foo: bar;

Для этого нужно скомпилировать код:

Base* obj = !bar ? static_cast<Base*>(foo) : bar;

Поскольку существует неявный приведение к Base*, что мешает компилятору сделать это?

Другими словами, почему Base* obj = foo; работать без броска, но с помощью ?: оператор не делает? Это потому, что не ясно, что я хочу использовать Base часть?

Ответы

Ответ 1

Цитирование из C++ стандартного проекта N4296, раздел 5.16 Условный оператор, пункт 6.3:

  • Один или оба из второго и третьего операндов имеют тип указателя; конверсии указателей (4.10) и преобразования квалификации (4.4) выполняются, чтобы привести их к их составному типу указателя (раздел 5). Результат - составной тип указателя.

Раздел 5 Выражения, пункты 13.8 и 13.9:

Тип составного указателя двух операндов p1 и p2, имеющих типы T1 и T2, соответственно, где по меньшей мере один является указателем или указателем на тип элемента или std :: nullptr_t, является:

  • если T1 и T2 аналогичны типам (4.4), cv-комбинированный тип T1 и T2;
  • в противном случае программа, которая требует определения типа составного указателя, плохо сформирована.

Примечание. Я скопировал 5/13.8 здесь, чтобы показать вам, что он не попал. Фактически это 5/13.9, "программа плохо сформирована".

И Раздел 4.10 Преобразование указателей, пункт 3:

Prvalue типа "указатель на cv D", где D - тип класса, может быть преобразован в prvalue типа "указатель на cv B", где B является базовым классом (п.10) D. Если B является недоступным (п. 11) или двусмысленным (10.2) базовым классом D, программа, которая требует этого преобразования, плохо сформирована. Результатом преобразования является указатель на подобъект базового класса объекта производного класса. Значение нулевого указателя преобразуется в значение нулевого указателя для типа назначения.

Таким образом, не имеет значения (вообще), что и Foo, и Bar производятся из одного базового класса. Имеет значение только то, что указатель на Foo и указатель на Bar не конвертируются друг в друга (отношения наследования отсутствуют).

Ответ 2

Разрешить преобразование в базовый тип указателя для условного оператора звучит неплохо, но на практике это будет проблематично.

В вашем примере

struct Base {};
struct Foo : Base {};
struct Bar : Base {};

Может показаться очевидным выбором для типа cond? foo: bar cond? foo: bar для Base*.

Но эта логика не выдерживает общего случая

Например:

struct TheRealBase {};
struct Base : TheRealBase {};
struct Foo : Base {};
struct Bar : Base {};

Должен ли cond? foo: bar cond? foo: bar быть типа Base* или типа TheRealBase*?

Как насчет:

struct Base1 {};
struct Base2 {};
struct Foo : Base1, Base2 {};
struct Bar : Base1, Base2 {};

Какой тип должен быть cond? foo: bar cond? foo: bar теперь?

Или как сейчас:

struct Base {};

struct B1 : Base {};
struct B2 : Base {};

struct X {};

struct Foo : B1, X {};
struct Bar : B2, X {};


      Base
      /  \
     /    \   
    /      \
  B1        B2 
   |   X    |
   | /   \  |
   |/     \ |
  Foo      Bar

Ой !! Удачи, рассуждая о типе cond? foo: bar cond? foo: bar. Я знаю, уродливые уродливые, непрактичные и охотящиеся достойны, но для этого стандарта все равно должны быть правила.

Вы понимаете.

И также имейте в виду, что std::common_type определяется в терминах условных правил оператора.

Ответ 3

Другими словами, почему Base* obj = foo; работать без броска, но с помощью ?: оператор не делает?

Тип условного выражения не зависит от того, для чего он назначен. В вашем случае компилятор должен иметь возможность оценивать !bar? foo: bar; !bar? foo: bar; независимо от того, для чего он назначен.

В вашем случае это проблема, поскольку ни foo преобразованный в тип bar ни bar нельзя преобразовать в тип foo.

Это потому, что не ясно, что я хочу использовать базовую часть?

Точно.

Ответ 4

Поскольку существует неявный приведение к Base*, что мешает компилятору сделать это?

Согласно [expr.cond]/7,

Стандартные преобразования Lvalue-to-rvalue, array-to-pointer и standard-to-pointer выполняются во втором и третьем операндах. После этих преобразований выполняется одно из следующих действий:

  • ...
  • Один или оба из второго и третьего операндов имеют тип указателя; преобразования указателей, преобразования указателей функций и преобразования квалификации, чтобы привести их к их составному типу указателя. Результат - составной тип указателя.

где тип составного указателя определен в [expr.type]/4:

Тип составного указателя двух операндов p1 и p2, имеющих типы T1 и T2, соответственно, где по меньшей мере один является указателем или типом-указателем-членом или std :: nullptr_t, является:

  • если оба p1 и p2 являются нулевыми константами указателя, std :: nullptr_t;

  • если либо p1, либо p2 является константой нулевого указателя, T2 или T1, соответственно;

  • если T1 или T2 - "указатель на cv1 void", а другой тип - "указатель на cv2 T", где T - тип объекта или void, "указатель на cv12 void", где cv12 является объединением cv1 и cv2;

  • если T1 или T2 является "указателем на функцию без исключения", а другой тип - "указатель на функцию", где типы функций в остальном одинаковы, "указатель на функцию";

  • если T1 является "указателем на cv1 C1", а T2 является "указателем на cv2 C2", где C1 является ссылкой, связанным с C2 или C2, ссылается на C1, cv-комбинированный тип T1 и T2 или cv-объединенный тип T2 и T1, соответственно;

  • если T1 является "указателем на член C1 типа cv1 U1", а T2 является "указателем на элемент C2 типа cv2 U2", где C1 является ссылкой, связанным с C2 или C2, связан ссылкой на C1, cv-комбинированный тип T2 и T1 или cv-комбинированный тип T1 и T2, соответственно;

  • если T1 и T2 являются аналогичными типами, cv-комбинированный тип T1 и T2;

  • в противном случае программа, которая требует определения типа составного указателя, плохо сформирована.

Теперь вы можете видеть, что указатель на "common base" не является составным типом указателя.


Другими словами, почему Base* obj = foo; работать без броска, но с помощью ?: оператор не делает? Это потому, что не ясно, что я хочу использовать Base часть?

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

Чтобы быть конкретным, правило должно обнаруживать условные выражения в следующих двух операторах одного типа.

Base* obj = !bar ? foo : bar;
bar ? foo : bar;

Теперь, если вы не сомневаетесь в том, что условное выражение во втором утверждении плохо сформировано 1 что аргументирует его формирование в первом утверждении?


1 Конечно, можно сделать правило, чтобы сделать такое выражение хорошо сформированным.Например, пусть составные типы указателей включают указатель на недвусмысленный базовый тип.Однако это нечто, что выходит за рамки этого вопроса, и должно обсуждаться комитетом ИСО C++.