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

Posted

技术标签:

【中文标题】三元运算符隐式转换为基类【英文标题】:Ternary operator implicit cast to base class 【发布时间】:2018-08-20 18:04:05 【问题描述】:

考虑这段代码:

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 : 酒吧;

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

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

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

换句话说,为什么Base* obj = foo; 在没有演员表的情况下可以工作,而使用?: 运算符却不行?是因为不清楚我要使用Base 部分吗?

【问题讨论】:

是时候让律师进来了。这是我今天的投票。 转发:en.cppreference.com/w/cpp/language/… @IInspectable 我宁愿把它放在类而不是结构上,很有可能两者具有相同的行为但更安全。 @DrewDormann 我没有仔细阅读。从我看到的情况来看,我希望派生到基础的转换是允许的 两种类型都存在到 void* 的隐式转换,但我们不希望这种转换真正触发,是吗? 【参考方案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),则 T1 和 T2 的 cv-combined 类型; 否则,需要确定复合指针类型的程序格式错误

注意:我在此处复制 5/13.8 只是为了向您展示它没有命中。实际生效的是 5/13.9,“程序格式错误”。

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

“pointer to cv D”类型的纯右值,其中 D 是类类型,可以转换为“pointer”类型的纯右值 到 cv B”,其中 B 是 D 的基类(第 10 条)。如果 B 是不可访问的(第 11 条)或模棱两可的(10.2) D 的基类,需要进行这种转换的程序格式错误。转换的结果是 指向派生类对象的基类子对象的指针。空指针值转换为 目标类型的空指针值。

因此,Foo 和 Bar 都派生自同一个基类并不重要(完全)。重要的是指向 Foo 的指针和指向 Bar 的指针不能相互转换(没有继承关系)。

【讨论】:

代码的问题是你无法确定复合指针类型,因为你陷入了最后一个项目符号,它说需要这样确定的程序格式错误,因为没有遇到其他子弹。最接近的项目符号可以说是“与引用相关”的项目符号,因为这是处理派生/基本指针的项目符号。 “相似”基本上意味着“相同类型忽略每个级别的 cv 限定”,这显然不符合,因为这些是指向不同类型的指针。指针转换规则无关;您甚至无法确定将它们转换为的类型。【参考方案2】:

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

在你的例子中

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++ 有许多确实有效的转换规则,只要转换是明确的。就像在这个问题中一样。没有? 我看不出在实践中有什么问题。您已经概述了编译器无法轻松确定三元运算符的返回类型是什么的特定情况,但并非所有情况。我们已经有确定排除问题示例和您提供的示例的三元运算符的返回类型的规则。为什么我们不能只扩展规则以包括问题示例中的案例并排除您的示例中的案例?何时有歧义很容易判断,那么为什么不只将规则限制在明确的情况下呢?【参考方案3】:

换句话说,为什么Base* obj = foo; 可以在没有演员表的情况下工作,但使用?: 运算符却不行?

条件表达式的类型不依赖于它所分配的内容。在您的情况下,编译器需要能够评估 !bar ? foo : bar; 而不管它被分配给什么。

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

是因为不清楚要使用Base部分吗?

没错。

【讨论】:

“表达式的类型不依赖于它所分配的内容。” 一般情况下并非如此。命名重载函数集的表达式本身不能被类型化(例如 decltype 失败),但在相应的上下文中获得适当的类型。也许将其调整为“条件类型..”。 @Columbo,能否请您指点我可以更深入地了解该主题的地方? 这是基本的东西;重载名称f 并写入F* ptr = f;。我看不出有什么需要进一步澄清的。 很抱歉,如果它是粗暴的,它只是不知道该指向什么——标准中的第 5 条? :s @Columbo,没关系。尽管使用 C++ 多年,但该语言仍有一些我不了解的方面,以及该语言的某些方面并没有引起我的注意。【参考方案4】:

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

根据[expr.cond]/7,

在第二个和第三个操作数上执行左值到右值、数组到指针和函数到指针的标准转换。在这些转换之后,应满足以下条件之一:

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

其中复合指针类型定义在[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 组合类型或cv-combined type分别为T2和T1;

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

如果T1和T2是相似类型,T1和T2的cv-combined类型;

否则,需要确定复合指针类型的程序是格式错误的。

现在你可以看到指向“common base”的指针不是复合指针类型。


换句话说,为什么Base* obj = foo; 可以在没有演员表的情况下工作,但使用?: 运算符却不行?是不是因为不清楚我要使用Base 部分吗?

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

具体来说,规则应该检测以下两个语句中的条件表达式是否为同一类型。

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

现在,如果您确信第二条语句中的条件表达式是非良构的1,那么在第一条语句中使它成为良构的理由是什么?


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

【讨论】:

以上是关于三元运算符隐式转换为基类的主要内容,如果未能解决你的问题,请参考以下文章

如何使用三元运算符创建指向多态类的唯一指针?

面向对象 is和as运算符,类库,委托

java三元运算符与类型强制转换

Kotlin三元运算符[重复]

将三元条件运算符转换为 if 语句?

关于三元运算符的数据类型转换问题