三元运算符隐式转换为基数 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'
考虑这段代码:
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 theBase
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'