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

时间:2018-03-12 16:22:10

标签: c++ language-lawyer c++17 ternary-operator implicit-conversion

考虑这段代码:

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部分?

4 个答案:

答案 0 :(得分:21)

引用C ++标准草案N4296,第5.16节条件运算符,第6.3段:

  
      
  • 第二个和第三个操作数中的一个或两个具有指针类型; 指针转换(4.10)和   资格转换(4.4)执行以将它们带到复合指针类型(第5条)。   结果是复合指针类型。
  •   

第5节表达式,第13.8和13.9段:

  

两个操作数p1和p2的复合指针类型,分别具有类型T1和T2,其中at   至少一个是指向成员类型或std :: nullptr_t的指针或指针,是:

     
      
  • 如果T1和T2是类似的类型(4.4),则是T1和T2的cv组合类型;
  •   
  • 否则,需要确定复合指针类型的程序格式不正确
  •   

注意:我在这里复制了5 / 13.8只是为了告诉你它没有被击中。实际上有效的是5 / 13.9,&#34;该程序格式错误&#34;。

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

  

类型为“指向cv D的指针”的prvalue,其中D是类类型,可以转换为类型为“指针”的prvalue   到cv B“,其中B是D的基类(第10条)。如果B是不可访问的(第11条)或含糊不清的(10.2)   D的基类,需要这种转换的程序是不正确的。转换的结果是a   指向派生类对象的基类子对象的指针。空指针值转换为   目标类型的空指针值。

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

答案 1 :(得分:19)

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

在你的例子中

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

cond ? foo : bar类型Base*来说,这似乎是明显的选择。

但这种逻辑并不能解决一般情况

E.g:

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是根据条件运算符规则定义的。

答案 2 :(得分:5)

  

换句话说,为什么Base* obj = foo;在没有强制转换的情况下工作但是使用?:运算符并不能解决问题?

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

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

  

是不是因为我不清楚我想使用Base部分?

正。

答案 3 :(得分:3)

  

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

根据[expr.cond] / 7,

  

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

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

其中复合指针类型在[expr.type] / 4中定义:

  

分别具有类型T1和T2的两个操作数p1和p2的复合指针类型,其中至少一个是指针或指向成员的类型或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相关的参考相关,C1和cv组合类型分别为T2或cv组合型T2和T1;

  •   
  • 如果T1是“指向cv1类型的C1的成员的指针”,T2是“指向cv2 U2类型的C2的成员的指针”,其中C1是与C2或C2相关的参考,则与C1有关的参考,cv-组合型T2和T1或cv-组合型T1和T2;

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

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

  •   

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

  

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

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

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

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

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

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