Неявное приведение тернарного оператора к базовому классу

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

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, что дает мне:

error: conditional expression between distinct pointer types ‘Foo*’ and ‘Bar*’ lacks a cast Base* obj = !bar ? foo : bar;

Это означает, что для его компиляции мне нужно изменить код на:

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

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

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

Пора пригласить юристов. Это мой голос - главный вопрос дня.

Bathsheba 12.03.2018 17:26

fwiw: en.cppreference.com/w/cpp/language/…

largest_prime_is_463035818 12.03.2018 17:26

@IInspectable Я бы предпочел оставить его в классах, а не в структурах, есть большая вероятность, что оба имеют одинаковое поведение, но будут безопасными.

Hatted Rooster 12.03.2018 17:30

@ user463035818 6.3) - это применимое правило, я полагаю? Кажется, что правила, предшествующие 6.3, не применяются.

Drew Dormann 12.03.2018 17:31

Код был эквивалентным, только без большого количества шума. Изменение также не повлияло на ошибку.

IInspectable 12.03.2018 17:31

@IInspectable Хорошо, тогда я верю тебе на слово

Hatted Rooster 12.03.2018 17:34

Вы пробовали с этим? Base* obj = !foo ? bar : foo; Если на этот раз вы получите ту же ошибку для bar, это означает, что есть проблема в логике компилятора. В таком состоянии вы должны задать вопрос поставщику, который предоставил вам компилятор. Я не вижу здесь никаких технических проблем.

Abhijit Pritam Dutta 12.03.2018 17:34

«Поскольку существует неявное приведение к Base *, что мешает компилятору сделать это?» - зачем это пробовать? Компилятор пытается выполнить преобразование из Foo * в Bar * и фали; пытается преобразовать Bar * в Foo * и терпит неудачу. Зачем пытаться конвертировать оба в Base *? А если есть более общие базы, какой указатель выбрать?

max66 12.03.2018 17:35

@AbhijitPritam У меня такая же ошибка. Я думаю, маловероятно, что все 3 основных компилятора содержат ошибку, поэтому я думаю, что это в какой-то степени стандартно.

Hatted Rooster 12.03.2018 17:35

@DrewDormann, я не читал внимательно. Из того, что я увидел, я ожидал, что производные преобразования в базовые разрешены.

largest_prime_is_463035818 12.03.2018 17:36

@SombreroChicken, вы хоть проверяли состояние if?

Abhijit Pritam Dutta 12.03.2018 17:39

Clang 7.0.0 выдает несколько иное сообщение об ошибке: «ошибка: несовместимые типы операндов ('Foo *' и 'Bar *')» На самом деле это выглядит более подходящим.

IInspectable 12.03.2018 17:39

@NathanOliver: Если что-то, что должно быть закрыто для этого как дублирующей цели, так как это сформулировано в более общих терминах.

Bathsheba 12.03.2018 17:47

@NathanOliver: На самом деле нам нужно держать наших лошадей здесь. Речь идет о типах указатель в тернарном условном выражении, а не о типах классов.

Bathsheba 12.03.2018 17:50

@Bathsheba Nevermind. Это другая ситуация

NathanOliver 12.03.2018 17:50

Неявное преобразование в void* существует для обоих типов, но мы не ожидаем, что это преобразование действительно сработает, не так ли?

n. 'pronouns' m. 12.03.2018 17:51

В С ++ нет такой вещи, как неявное приведение типов. Сама концепция приведения означает, что это явная операция. Что вы имеете в виду, это неявное преобразование

PlasmaHH 12.03.2018 23:51

Ну, если ты понимаешь, что я имел в виду, тогда зачем мне говорить

Hatted Rooster 12.03.2018 23:53
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
49
18
3 169
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

In other words, why does Base* obj = foo; work without a cast but using the ?: operator doesn't?

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

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

Is it because it's not clear that I want to use the Base part?

Точно.

«Тип выражения не зависит от того, чему оно присвоено». В общем, это не так. Выражение, называющее перегруженный набор функций, не может быть типизировано само по себе (например, decltype не работает), но приобретает правильный тип в соответствующем контексте. Возможно, измените это на "тип условного ..".
Columbo 12.03.2018 17:42

@Columbo, не могли бы вы указать мне какое-нибудь место, где я могу глубже разобраться в предмете?

R Sahu 12.03.2018 17:46

Это базовые вещи; Перегрузите имя f и напишите F* ptr = f;. Я не понимаю, что нужно уточнить дальше.

Columbo 12.03.2018 17:54

Извините, если это произошло абразивно, он просто не знал, на что указать - пункт 5 стандарта? : s

Columbo 12.03.2018 18:21

@ Columbo, это нормально. Несмотря на годы использования C++, есть еще несколько аспектов языка, о которых я не знаю, и некоторые аспекты языка, которые мне не бросаются в глаза.

R Sahu 12.03.2018 18:29
Ответ принят как подходящий

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

  • One or both of the second and third operands have pointer type; pointer conversions (4.10) and qualification conversions (4.4) are performed to bring them to their composite pointer type (Clause 5). The result is of the composite pointer type.

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

The composite pointer type of two operands p1 and p2 having types T1 and T2, respectively, where at least one is a pointer or pointer to member type or std::nullptr_t, is:

  • if T1 and T2 are similar types (4.4), the cv-combined type of T1 and T2;
  • otherwise, a program that necessitates the determination of a composite pointer type is ill-formed.

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

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

A prvalue of type “pointer to cv D”, where D is a class type, can be converted to a prvalue of type “pointer to cv B”, where B is a base class (Clause 10) of D. If B is an inaccessible (Clause 11) or ambiguous (10.2) base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a pointer to the base class subobject of the derived class object. The null pointer value is converted to the null pointer value of the destination type.

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

Проблема с кодом заключается в том, что вы не можете определить тип составного указателя, потому что вы попадаете в последний пункт, в котором говорится, что программа, которая требует такого определения, плохо сформирована, поскольку ни один из других пунктов не выполняется. Самая близкая марка, возможно, связана со ссылками, поскольку она обрабатывает производные / базовые указатели. «Подобный» в основном означает «тот же тип, игнорирующий cv-квалификацию на каждом уровне», что явно не выполняется, потому что это указатели на разные типы. Правила преобразования указателя не имеют значения; вы даже не можете определить тип, в который их нужно преобразовать.

T.C. 13.03.2018 01:18

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

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

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

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

Но эта логика не подходит для общего случая.

Например.:

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

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

Как насчет:

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

Какого типа теперь должен быть 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. Я знаю, некрасиво, уродливо, непрактично и достойно охоты, но в стандарте все равно должны быть правила для этого.

Вы уловили суть.

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

Хм ... Это отличные примеры ситуаций, когда не мог работать, из-за неоднозначности. Но C++ имеет много правил преобразования, которые Выполнять работу, пока преобразование однозначно. Как в этом вопросе. Нет?

Drew Dormann 12.03.2018 18:13

Я не понимаю, откуда тут проблема на практике. Вы обрисовали в общих чертах конкретные случаи, когда компилятор не мог легко определить, каким будет тип возвращаемого значения тернарного оператора, но это не все случаи. У нас уже есть правила для определения типа возвращаемого значения тернарного оператора, исключающие пример вопроса и приведенные вами примеры. Почему мы не могли просто расширить правила, чтобы включить случай в пример вопроса и исключить случаи из ваших примеров? Когда есть двусмысленность, легко определить, так почему бы не ограничить правила только однозначными случаями?

Jordan Melo 14.03.2018 20:32

Since an implicit cast to a Base* exists, what is preventing the compiler from doing so?

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

Lvalue-to-rvalue, array-to-pointer, and function-to-pointer standard conversions are performed on the second and third operands. After those conversions, one of the following shall hold:

  • ...
  • One or both of the second and third operands have pointer type; pointer conversions, function pointer conversions, and qualification conversions are performed to bring them to their composite pointer type. The result is of the composite pointer type.

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

The composite pointer type of two operands p1 and p2 having types T1 and T2, respectively, where at least one is a pointer or pointer-to-member type or std​::​nullptr_­t, is:

  • if both p1 and p2 are null pointer constants, std​::​nullptr_­t;

  • if either p1 or p2 is a null pointer constant, T2 or T1, respectively;

  • if T1 or T2 is “pointer to cv1 void” and the other type is “pointer to cv2 T”, where T is an object type or void, “pointer to cv12 void”, where cv12 is the union of cv1 and cv2;

  • if T1 or T2 is “pointer to noexcept function” and the other type is “pointer to function”, where the function types are otherwise the same, “pointer to function”;

  • if T1 is “pointer to cv1 C1” and T2 is “pointer to cv2 C2”, where C1 is reference-related to C2 or C2 is reference-related to C1, the cv-combined type of T1 and T2 or the cv-combined type of T2 and T1, respectively;

  • if T1 is “pointer to member of C1 of type cv1 U1” and T2 is “pointer to member of C2 of type cv2 U2” where C1 is reference-related to C2 or C2 is reference-related to C1, the cv-combined type of T2 and T1 or the cv-combined type of T1 and T2, respectively;

  • if T1 and T2 are similar types, the cv-combined type of T1 and T2;

  • otherwise, a program that necessitates the determination of a composite pointer type is ill-formed.

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


In other words, why does Base* obj = foo; work without a cast but using the ?: operator doesn't? Is it because it's not clear that I want to use the Base part?

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

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

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

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


1 Of course one can make a rule to make such expression well-formed. For example, let composite pointer types include the pointer to an unambiguous base type. However, this is something beyond this question, and should be discussed by ISO C++ committee.

Другие вопросы по теме