Рассмотрим этот фрагмент кода:
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?
fwiw: en.cppreference.com/w/cpp/language/…
@IInspectable Я бы предпочел оставить его в классах, а не в структурах, есть большая вероятность, что оба имеют одинаковое поведение, но будут безопасными.
@ user463035818 6.3) - это применимое правило, я полагаю? Кажется, что правила, предшествующие 6.3, не применяются.
Код был эквивалентным, только без большого количества шума. Изменение также не повлияло на ошибку.
@IInspectable Хорошо, тогда я верю тебе на слово
Вы пробовали с этим? Base* obj = !foo ? bar : foo; Если на этот раз вы получите ту же ошибку для bar, это означает, что есть проблема в логике компилятора. В таком состоянии вы должны задать вопрос поставщику, который предоставил вам компилятор. Я не вижу здесь никаких технических проблем.
«Поскольку существует неявное приведение к Base *, что мешает компилятору сделать это?» - зачем это пробовать? Компилятор пытается выполнить преобразование из Foo * в Bar * и фали; пытается преобразовать Bar * в Foo * и терпит неудачу. Зачем пытаться конвертировать оба в Base *? А если есть более общие базы, какой указатель выбрать?
@AbhijitPritam У меня такая же ошибка. Я думаю, маловероятно, что все 3 основных компилятора содержат ошибку, поэтому я думаю, что это в какой-то степени стандартно.
@DrewDormann, я не читал внимательно. Из того, что я увидел, я ожидал, что производные преобразования в базовые разрешены.
@SombreroChicken, вы хоть проверяли состояние if?
Clang 7.0.0 выдает несколько иное сообщение об ошибке: «ошибка: несовместимые типы операндов ('Foo *' и 'Bar *')» На самом деле это выглядит более подходящим.
@NathanOliver: Если что-то, что должно быть закрыто для этого как дублирующей цели, так как это сформулировано в более общих терминах.
@NathanOliver: На самом деле нам нужно держать наших лошадей здесь. Речь идет о типах указатель в тернарном условном выражении, а не о типах классов.
@Bathsheba Nevermind. Это другая ситуация
Неявное преобразование в void* существует для обоих типов, но мы не ожидаем, что это преобразование действительно сработает, не так ли?
В С ++ нет такой вещи, как неявное приведение типов. Сама концепция приведения означает, что это явная операция. Что вы имеете в виду, это неявное преобразование
Ну, если ты понимаешь, что я имел в виду, тогда зачем мне говорить





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?
Точно.
@Columbo, не могли бы вы указать мне какое-нибудь место, где я могу глубже разобраться в предмете?
Это базовые вещи; Перегрузите имя f и напишите F* ptr = f;. Я не понимаю, что нужно уточнить дальше.
Извините, если это произошло абразивно, он просто не знал, на что указать - пункт 5 стандарта? : s
@ Columbo, это нормально. Несмотря на годы использования C++, есть еще несколько аспектов языка, о которых я не знаю, и некоторые аспекты языка, которые мне не бросаются в глаза.
Цитата из стандартного черновика 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-квалификацию на каждом уровне», что явно не выполняется, потому что это указатели на разные типы. Правила преобразования указателя не имеют значения; вы даже не можете определить тип, в который их нужно преобразовать.
Разрешение преобразования в базовый тип указателя для условного оператора звучит неплохо, но на практике было бы проблематично.
В вашем примере
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++ имеет много правил преобразования, которые Выполнять работу, пока преобразование однозначно. Как в этом вопросе. Нет?
Я не понимаю, откуда тут проблема на практике. Вы обрисовали в общих чертах конкретные случаи, когда компилятор не мог легко определить, каким будет тип возвращаемого значения тернарного оператора, но это не все случаи. У нас уже есть правила для определения типа возвращаемого значения тернарного оператора, исключающие пример вопроса и приведенные вами примеры. Почему мы не могли просто расширить правила, чтобы включить случай в пример вопроса и исключить случаи из ваших примеров? Когда есть двусмысленность, легко определить, так почему бы не ограничить правила только однозначными случаями?
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 theBasepart?
Проблема в том, что правило должно определять тип условного выражения независимо, без соблюдения инициализации.
Чтобы быть конкретным, правило должно определять, что условные выражения в следующих двух операторах относятся к одному типу.
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.
Пора пригласить юристов. Это мой голос - главный вопрос дня.