显式类型转换
来自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 起) |
- 否则是纯右值。
解决歧义
有歧义的声明语句
在以函数风格转换表达式作为最左侧子表达式的表达式语句,和声明语句间有歧义的情况下,将它当做声明来解决歧义。这种歧义消解是纯语法的:它对语句中出现的名字不考虑除了其是否为类型名之外的含义:
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} |
示例
运行此代码
#include <cassert> #include <iostream> 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; // 转换不完整类型到不完整类型 } void cpp23_decay_copy_demo() { auto inc_print = [](int& x, const int& y) { ++x; std::cout << "x:" << x << ", y:" << y << '\n'; }; int p{1}; inc_print(p, p); // 打印 x:2 y:2,因为此处的形参 y 是 p 的别名 int q{1}; inc_print(q, auto{q}); // 打印 x:2 y:1,auto{q}(C++23)转换为纯右值, // 因此形参 y 是 q 的副本(而非 q 的别名) } // 在这个例子中,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); // 可以编译 assert(a == nullptr); cpp23_decay_copy_demo(); }
输出:
x:2 y:2 x:2 y:1
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
缺陷报告 | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
CWG 1223 (P2915R0) |
C++11 | 引入尾随返回类型带来了更多歧义 | 解决这些歧义 |
CWG 2620 | C++98 | 对有歧义的函数形参的解决方案可能会被误解 | 改善用词 |
CWG 2828 | C++98 | C 风格转换在 static_cast 后随 const_cast 存在多种解释时非良构,即使实际不会使用这种转换 |
只会考虑实际会使用的转换 |
引用
- C++23 标准(ISO/IEC 14882:2024):
- 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 转换 | 进行通用低层转换 |
标准转换 | 从一个类型到另一类型的隐式转换 |