显式类型转换
来自cppreference.com
用显式和隐式转换的组合进行类型之间的转换。
语法
( 新类型 ) 表达式
|
(1) | ||||||||
新类型 ( 表达式列表(可选) )
|
(2) | ||||||||
新类型 { 表达式列表(可选) }
|
(3) | (C++11 起) | |||||||
模板名 ( 表达式列表(可选) )
|
(4) | (C++17 起) | |||||||
模板名 { 表达式列表(可选) }
|
(5) | (C++17 起) | |||||||
auto ( 表达式 )
|
(6) | (C++23 起) | |||||||
auto { 表达式 }
|
(7) | (C++23 起) | |||||||
返回 新类型 类型的值。
解释
1) 遇到 C 风格转型表达式 时,编译器会尝试按以下顺序将它解释成下列转型表达式:
a)
const_cast
<
新类型>(
表达式)
;b)
static_cast
<
新类型>(
表达式)
,带扩展:额外允许将到派生类的指针或引用转型成到无歧义基类的指针或引用(反之亦然),纵使基类不可访问也是如此(即此转型忽略 private 继承说明符)。同样适用于将成员指针转型到指向无歧义非虚基类的成员的指针;c) static_cast(带扩展)后随 const_cast;
d)
reinterpret_cast
<
新类型>(
表达式)
;e) reinterpret_cast 后随 const_cast。
选择首个满足相应转型运算符要求的方式,即便它无法编译(见示例)。如果该转型能解释成多于一种 static_cast 后随 const_cast 的方式,那么它无法编译。
另外,C 风格转型写法允许在不完整类型的指针之间进行双向转型。如果表达式 和新类型 是指向不完整类型的指针,那么未指明选用 static_cast 还是 reinterpret_cast。
2) 函数风格转型表达式由一个简单类型说明符或一个 typedef 说明符构成(换言之,它是单个单词的类型名,这意味着 unsigned int(表达式
)
和 int*(表达式)
这样的表达式非法),后随带括号的单个表达式。
- 如果圆括号内只有一个表达式,此转型表达式准确等价于对应的 C 风格转型表达式。
- 如果括号中有多于一个表达式或花括号初始化列表 (C++11 起),那么新类型 必须是带有适当声明的构造函数的类。此表达式是新类型 类型的纯右值,它指代的临时量 (C++17 前)它的结果对象 (C++17 起)以表达式列表 直接初始化。
- 如果圆括号内没有表达式,那么:如果新类型 指名一个非数组完整对象类型,那么此表达式是新类型 类型的纯右值,它指代该类型的临时量 (C++17 前)它的结果对象是该类型(可能会添加 cv 限定符) (C++17 起)。如果新类型 是对象类型,那么对象会被值初始化。如果新类型 是(可有 cv 限定的)void,那么表达式是没有结果对象的 (C++17 起) void 纯右值。
3) 单个单词的类型名后随花括号初始化器列表,是指定类型的纯右值,其指代的临时量 (C++17 前)其结果对象 (C++17 起)以指定的花括号初始化器列表直接列表初始化。如果新类型 是(可有 cv 限定的)void,那么表达式是没有结果对象的 (C++17 起) void 纯右值。这是仅有的能创建数组纯右值的表达式。
同所有转型表达式,结果是:
- 左值,如果 新类型 是左值引用类型或到函数类型的右值引用类型 (C++11 起);
|
(C++11 起) |
- 否则是纯右值。
注解
功能特性测试宏 | 值 | 标准 | 备注 |
---|---|---|---|
__cpp_auto_cast |
202110L | (C++23) | auto(x) 与 auto{x} |
示例
运行此代码
double f = 3.14; unsigned int n1 = (unsigned int)f; // C 风格转型 unsigned int n2 = unsigned(f); // 函数风格转型 class C1; class C2; C2* foo(C1* p) { return (C2*)p; // 转型不完整类型到不完整类型 } // 在这个例子中,C 风格转型被转译成 static_cast // 尽管它的作用也可以和 reinterpret_cast 一致 struct A {}; struct I1 : A {}; struct I2 : A {}; struct D : I1, I2 {}; int main() { D* d = nullptr; // A* a = (A*)d; // 编译时错误 A* a = reinterpret_cast<A*>(d); // 可以编译 }
解决歧义
有歧义的声明语句
在以函数式转型表达式作为最左侧子表达式的表达式语句,和声明语句间有歧义的情况下,以将它当做声明解决歧义。此消歧义是纯语法的:它不考虑语句中出现的名字除了是否为类型名之外的含义:
struct M {}; struct L { L(M&); }; M n; void f() { M(m); // 声明,等价于 M m; L(n); // 非良构的声明,等价于 L n; L(l)(m); // 仍然是声明,等价于 L l((m)); }
然而,如果在有歧义的声明语句中最外层的声明符有尾随返回类型,那么只有在尾随返回类型是 auto 的情况下才会将该语句当做声明语句: struct M; struct S { S* operator()(); int N; int M; void mem(S s) { auto(s)()->M; // 表达式(S::M 隐藏 ::M),C++23 前非法 } }; void f(S s) { { auto(s)()->N; // 表达式,C++23 前非法 auto(s)()->M; // 函数声明,等价于 M s(); } { S(s)()->N; // 表达式 S(s)()->M; // 表达式 } } |
(C++11 起) |
有歧义的函数形参
以上歧义也可以在声明语境中发生。在该语境下,需要从以函数风格转型作为初始化器的对象声明,和涉及“带有被多余的圆括号包围的形参名的函数声明符”的声明中进行选择。解决方案同样是将任何可以是声明的构造视为声明(例如上述潜在的形参声明):
struct S { S(int); }; void foo(double a) { S w(int(a)); // 函数声明:有一个类型是 int 的形参 a S x(int()); // 函数声明:有一个类型是 int(*)() 的无名形参,该类型从 int() 调整而来 // 避免歧义的方式: S y((int(a))); // 对象声明:另外用一组圆括号包裹 S y((int)a); // 对象声明:使用 C 风格转型 S z = int(a); // 对象声明:使用无歧义的语法 }
然而,如果有歧义的形参声明最外层的声明符有尾随返回类型,那么只有在尾随返回类型是 auto 的情况下才会将它当做声明: typedef struct BB { int C[2]; } *B, C; void foo() { S a(B()->C); // 对象声明:B()->C 不能声明形参 S b(auto()->C); // 函数声明:有一个类型是 C(*)() 的无名形参,该类型从 C() 调整而来 } |
(C++11 起) |
有歧义的类型标识
函数风格转型和类型标识的相似之处也会引发歧义。解决方案是在这种语境中在语法上可以是类型标识的地方都会优先考虑类型标识:
// int() 和 int(unsigned(a)) 都可以被解析成类型标识: // int() 表示返回 int 且不接收实参的函数 // int(unsigned(a)) 表示返回 int 且接收一个类型是 unsigned 的实参的函数 void foo(signed char a) { sizeof(int()); // 类型标识(非良构) sizeof(int(a)); // 表达式 sizeof(int(unsigned(a))); // 类型标识(非良构) (int()) + 1; // 类型标识(非良构) (int(a)) + 1; // 表达式 (int(unsigned(a))) + 1; // 类型标识(非良构) }
然而,如果有歧义的类型标识最外层的抽象声明符 有尾随返回类型,那么只有在尾随返回类型是 auto 的情况下才会将它当做类型标识: typedef struct BB { int C[2]; } *B, C; void foo() { sizeof(B()->C[1]); // OK,sizeof(表达式) sizeof(auto()->C[1]); // 错误:对返回数组的函数使用 sizeof } |
(C++11 起) |
注解
功能特性测试宏 | 值 | 标准 | 备注 |
---|---|---|---|
__cpp_auto_cast |
202110L | (C++23) | auto(x) 和 auto{x} |
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1223 (P2915R0) |
C++11 | 引入尾随返回类型带来了更多歧义 | 解决这些歧义 |
CWG 2620 | C++98 | 对有歧义的函数形参的解决方案可能会被误解 | 改善用词 |
引用
- C++23 标准(ISO/IEC 14882:2023):
- 7.6.1.4 Explicit type conversion (functional notation) [expr.type.conv]
- 7.6.3 Explicit type conversion (cast notation) [expr.cast]
- C++20 标准(ISO/IEC 14882:2020):
- 7.6.1.4 Explicit type conversion (functional notation) [expr.type.conv]
- 7.6.3 Explicit type conversion (cast notation) [expr.cast]
- C++17 标准(ISO/IEC 14882:2017):
- 8.2.3 Explicit type conversion (functional notation) [expr.type.conv]
- 8.4 Explicit type conversion (cast notation) [expr.cast]
- C++14 标准(ISO/IEC 14882:2014):
- 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]
- 5.4 Explicit type conversion (cast notation) [expr.cast]
- C++11 标准(ISO/IEC 14882:2011):
- 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]
- 5.4 Explicit type conversion (cast notation) [expr.cast]
- C++03 标准(ISO/IEC 14882:2003):
- 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]
- 5.4 Explicit type conversion (cast notation) [expr.cast]
- C++98 标准(ISO/IEC 14882:1998):
- 5.2.3 Explicit type conversion (functional notation) [expr.type.conv]
- 5.4 Explicit type conversion (cast notation) [expr.cast]
参阅
const_cast 转换 | 添加或移除 const |
static_cast 转换 | 进行基本转换 |
dynamic_cast 转换 | 进行有检查的多态转换 |
reinterpret_cast 转换 | 进行通用低层转换 |
标准转换 | 从一个类型到另一类型的隐式转换 |