三元运算符隐式强制转换为基类

Ternary operator implicit cast to base class

本文关键字:转换 基类 三元 运算符      更新时间:2023-10-16

考虑这段代码:

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*"之间的条件表达式 和 '酒吧*' 缺少施法 基地* obj = !bar ?福 : 酒吧;

这意味着要编译它,我必须将代码更改为:

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

由于存在对Base*的隐式强制转换,是什么阻止编译器这样做?

换句话说,为什么Base* obj = foo;没有强制转换即可工作,而使用?:运算符却不能?是因为不清楚我是否要使用Base部分吗?

引用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),则 T1 和 T2 的 cv 组合类型;
  • 否则,需要确定复合指针类型的程序格式不正确

注意:我在这里复制了 5/13.8 只是为了向您展示它没有命中。实际生效的是 5/13.9,"程序格式不正确"。

以及第 4.10 节指针转换,第 3 段:

类型为"指向 cv D 的指针"的 prvalue,其中 D 是类类型,可以转换为类型为 "pointer "的 prvalue 到 cv B",其中 B 是 D 的基类(第 10 条)。如果 B 是无法访问的(第 11 条)或模棱两可的 (10.2) D 的基类,需要此转换的程序格式不正确。转换的结果是 指向派生类对象的基类子对象的指针。空指针值将转换为 目标类型的空指针值。

因此,Foo和Bar都来自同一个基类并不重要。重要的是指向 Foo 的指针和指向 Bar 的指针不可相互转换(无继承关系)。

允许将条件运算符转换为基本指针类型听起来不错,但在实践中会有问题。

在您的示例中

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

对于要Base*cond ? foo : bar类型来说,这似乎是显而易见的选择。

但这种逻辑不适用于一般情况。

例如:

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++有许多转换规则,只要转换是明确的,这些规则就会起作用。就像这个问题一样。不?

在这里只允许明确的情况将是极其成问题的。添加基类的简单操作可能会使程序无法编译:

初始代码库:

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

这将允许写入__ ? foo : bar;。现在你不能修改继承结构,因为几乎任何修改都会破坏以合法方式使用三元运算符的现有代码:

struct FooBarCommon {};
struct Base {};
struct Foo : Base, FooBarCommon {};
struct Bar : Base, FooBarCommon {};
struct Baz : Base {};

这似乎是一个合理的修改。按照现在的规则,只要不修改类的公共 API,就可以执行此操作。这将不再是真的,标准将只允许在明确的情况下转换为基类。

换句话说

,为什么Base* obj = foo;在没有强制转换的情况下工作,而使用?:运算符却不能?

条件表达式的类型不取决于它分配给什么。在您的情况下,编译器需要能够计算!bar ? foo : bar;,而不管它分配给什么。

在您的情况下,这是一个问题,因为foo都不能转换为bar类型,bar也不能转换为foo类型。

是因为不清楚我想使用基本部分吗?

正是。

既然存在对Base*的隐式强制转换,是什么阻止编译器这样做?

根据[expr.cond]/7,

Lvalue-to-rvalue、数组到指针和函数到指针的标准转换在第二个和第三个操作数上执行。转换后,下列情况之一应成立:

  • 第二个和第三个操作数中的一个或两个都具有指针类型;执行指针转换、函数指针转换和限定转换以使它们变为其复合指针类型。结果为复合指针类型。

其中复合指针类型在 [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 是"指向 noexcept 函数的指针",而另一种类型是"指向函数的指针",其中函数类型在其他方面相同,则为"指向函数的指针";

  • 如果 T1 是"指向 cv1 C1 的指针",T2
  • 是"指向 cv2 C2 的指针",其中 C1 与 C2 相关或 C2 与 C1 相关,则 T1 和 T2 的 cv 组合类型或 T2 和 T1 的 cv 组合类型;

  • 如果 T1 是"指向 cv1 U1 类型的 C1 成员的指针",T2 是
  • "指向 cv2 U2 类型的 C2 成员的指针",其中 C1 与 C2 引用相关或 C2 与 C1 引用相关,分别是 T2 和 T1 的 cv 组合类型或 T1 和 T2 的 cv 组合类型;

  • 如果 T1 和 T2
  • 是相似的类型,则 T1 和 T2 的 cv 组合类型;

  • 否则,需要确定复合指针类型的程序格式不正确。

现在您可以看到指向"公共基础"的指针不是复合指针类型。


换句话说,为什么Base* obj = foo;在没有强制转换的情况下工作,而使用?:运算符却不能?是因为不清楚我是否要使用Base部分吗?

问题是规则应该独立检测条件表达式的类型,而不观察初始化。

具体而言,规则应将以下两个语句中的条件表达式检测为同一类型。

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

现在,如果您毫不怀疑第二个语句中的条件表达式格式不正确1,那么在第一个语句中使其格式良好的原因是什么?


1当然,人们可以制定一个规则来使这种表达格式良好。例如,让复合指针类型包含指向明确基类型的指针。但是,这超出了这个问题的范围,应该由ISO C++委员会讨论。

exp1 ? exp2 : exp3

在条件运算符中,exp2 和 exp3 必须是相同的类型或 alteast 它应该具有类型转换函数,可以将一种类型转换为另一种类型。如果不是,那么它的格式不正确。

int i = 1;
long j = 2;
true  ? i : j; // OK
false ? i : j; // OK

string str1 = "hello";
const char* str2 = "world";
true  ? str1 : str2; // OK
false ? str1 : str2; // OK
int i = 1;
string str1 = "hello";
true  ? i : str1; // Error: No conversion from 'std::string' to 'int'
false ? i : str1; // Error: No conversion from 'std::string' to 'int'