三元运算符隐式强制转换为基类
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 下不起作用,给我:
错误:不同指针类型"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 或
如果 P1 和 P2 都是空指针常量,则 std::nullptr_t;
- 如果 T1
p2 是空指针常量,则分别为 T2 或 T1;
或 T2 是"指向 cv1 void 的指针",另一种类型是"指向 cv2 T 的指针",其中 T 是对象类型或 void,则为"指向 cv12 void 的指针",其中 cv12 是 cv1 和 cv2 的并集;
- 如果 T1 是"指向 cv1 C1 的指针",T2
如果 T1 或 T2 是"指向 noexcept 函数的指针",而另一种类型是"指向函数的指针",其中函数类型在其他方面相同,则为"指向函数的指针";
- 如果 T1 是"指向 cv1 U1 类型的 C1 成员的指针",T2 是
是"指向 cv2 C2 的指针",其中 C1 与 C2 相关或 C2 与 C1 相关,则 T1 和 T2 的 cv 组合类型或 T2 和 T1 的 cv 组合类型;
- 如果 T1 和 T2
"指向 cv2 U2 类型的 C2 成员的指针",其中 C1 与 C2 引用相关或 C2 与 C1 引用相关,分别是 T2 和 T1 的 cv 组合类型或 T1 和 T2 的 cv 组合类型;
是相似的类型,则 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'
- 从基类实例调用派生类方法而不进行强制转换
- C++将派生类转换为基类时'object slicing'期间发生的情况
- 派生类(构造函数具有参数)和基类(构造函数缺少参数)之间没有可行的转换
- 调用不在基类中的派生类函数而不进行动态强制转换,以最大程度地提高性能
- 在将派生类指针类型转换为派生类指针后,从基类指针调用派生类函数
- 我们可以在不知道其真实类型的情况下将基类指针转换为派生类指针吗?
- 从未使用过到基类的转换
- 基类到派生类的强制转换向量
- 有没有办法复制派生类指针的向量而不将其强制转换为基类?
- 为什么此代码不将基类强制转换为 c++ 中的派生类?
- 如何在运行时将指向派生类的 void* 正确转换为指向基类的 void*
- 从基类的共享指针向下转换到派生类的引用
- 为了访问方法,从基类动态转换为派生类
- 派生类 插入和提取运算符重载以及基类与派生类之间的强制转换
- 将空基类优化对象强制转换为另一种类型是否会破坏严格的别名?
- 基类到派生模板类的强制转换指针,而不知道类型
- 带有基类指针的强制转换向量,指向子类
- 通过转换基类指针来设置派生类字段
- 强制转换基类问题
- 强制转换基类保护方法的返回指针的替代方法