三元运算符隐式转换为基数 class

Ternary operator implicit cast to base class

考虑这段代码:

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

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?

没错。

引自 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,"the program is ill-formed".

和第 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 都来自同一个基 class 并不重要(完全)。唯一重要的是指向 Foo 的指针和指向 Bar 的指针不能相互转换(没有继承关系)。

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

在你的例子中

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 类型好运。我知道,丑陋丑陋,non-practical 值得猎杀,但标准仍然必须对此有规定。

你明白了。

还要记住 std::common_type 是根据条件运算符规则定义的。


Hm... These are excellent examples of situations that couldn't work, because of ambiguity. But C++ has many conversion rules that do work as long as the conversion is unambiguous. Like in this question. No?

在这里只允许明确的情况将是非常有问题的。添加基数 class 的简单操作可能会使程序无法编译:

初始代码库:

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 {};

这似乎是一个合理的修改。按照现在的规则,只要您不修改 classes 的 public API,您就可以这样做。这将不再适用,标准将仅在明确的情况下允许转换为基础 class。

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;

现在,如果你确信第二条语句中的条件表达式是ill-formed1,那么把它变成well-formed的理由是什么第一个声明?


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

exp1 ? exp2 : exp3

在条件运算符中,exp2 & exp3 必须是相同的类型,或者至少它应该有类型转换函数,将一种类型转换为另一种类型。如果不是,那就是ill-formed.

    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'