Explicit type conversion
通过显式和隐式转换的组合实现类型间的转换。
目录 |
语法
(
类型标识
)
一元表达式
|
(1) | ||||||||
简单类型说明符
(
表达式列表
(可选)
)
简单类型说明符
(
初始化器列表
(可选)
)
|
(2) |
(C++11 前)
(C++11 起) |
|||||||
简单类型说明符
{
初始化器列表
(可选)
}
|
(3) | (C++11 起) | |||||||
简单类型说明符
{
指定初始化器列表
}
|
(4) | (C++20 起) | |||||||
typename
标识符
(
初始化器列表
(可选)
)
|
(5) | (C++11 起) | |||||||
typename
标识符
{
初始化器列表
(可选)
}
|
(6) | (C++11 起) | |||||||
typename
标识符
{
指定初始化器列表
}
|
(7) | (C++20 起) | |||||||
将任意数量的值显式转换为目标类型的值。
| type-id | - | 一个 类型标识 |
| unary-expression | - | 一元表达式(其顶层运算符的 优先级 不高于C风格转换) |
| simple-type-specifier | - | 一个 简单类型说明符 |
| expression-list | - | 逗号分隔的表达式列表(不含未加括号的 逗号表达式 ) |
| initializer-list | - | 逗号分隔的 初始化子句 列表 |
| designated-initializer-list | - | 逗号分隔的 指定初始化子句 列表 |
| identifier | - | (可能限定的)标识符(包含 模板标识符 ) |
说明
const_cast
<
type-id
>
(
unary-expression
)
;
static_cast
<
类型标识
>
(
一元表达式
)
,具有扩展功能:指向
派生类
的指针或引用额外允许被转换为指向明确基类的指针或引用(反之亦然),即使该基类是
不可访问的
(即此转换会忽略私有继承说明符)。同样的规则适用于将
成员指针
转换为明确非虚基类的成员指针;
reinterpret_cast
<
类型标识
>
(
一元表达式
)
;
T
的值,该类型由指定的类型
和初始化器
(C++17 起)
确定:
|
|
(C++17 前) | ||
|
|
(C++17 起) |
- 若函数式转换的语法为 (2) ,且括号内仅有一个表达式,则该转换等价于对应的C风格转换。
-
否则,若
T为(可能带cv限定符的) void ,则结果为 右值 (C++11 前) 纯右值 (C++11 起) 类型的 void 且不执行初始化。
|
(C++11 前) |
|
(C++11 起) |
-
否则,若
T是引用类型,函数式转换的效果等同于用指定初始化器 直接初始化 一个类型为T的虚构变量 t ,且结果为初始化后的 t 。
|
(C++11 前) |
|
(C++11 起) |
-
否则,结果为
右值
(C++11 前)
纯右值
(C++11 起)
类型的
T表示临时对象 (C++17 前) 其结果对象为 (C++17 起) ,该对象通过指定初始化器进行 直接初始化 。
二义性解析
有歧义的声明语句
当表达式语句(其最左侧子表达式为函数式转型表达式)与声明语句之间存在歧义时,该歧义通过将其视为声明来解决。这种消歧处理是纯语法层面的:它不考虑语句中出现名称的含义,仅判断这些名称是否为类型名称:
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() 调整而来 } |
(since 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 1893 | C++11 | 函数式转换未考虑包展开 | 考虑包展开 |
| CWG 2351 | C++11 | void { } 格式错误 | 改为格式正确 |
| CWG 2620 | C++98 | 歧义函数参数的解析可能被误解 | 改进措辞表述 |
| CWG 2828 | C++98 |
当存在多个
static_cast
后接
const_cast
的解释时,
C风格转换格式错误,无论这些转换是否实际使用 |
仅考虑可能
被使用的 转换方式 |
| CWG 2894 | C++98 | 函数式转换可能创建引用右值 | 只能创建引用左值 |
参考文献
- C++23 标准 (ISO/IEC 14882:2024):
-
- 7.6.1.4 显式类型转换(函数式表示法)[expr.type.conv]
-
- 7.6.3 显式类型转换(强制转换表示法)[expr.cast]
- C++20 标准 (ISO/IEC 14882:2020):
-
- 7.6.1.4 显式类型转换(函数式表示法)[expr.type.conv]
-
- 7.6.3 显式类型转换(强制转换表示法)[expr.cast]
- C++17 标准 (ISO/IEC 14882:2017):
-
- 8.2.3 显式类型转换(函数式表示法)[expr.type.conv]
-
- 8.4 显式类型转换(强制转换表示法)[expr.cast]
- C++14 标准 (ISO/IEC 14882:2014):
-
- 5.2.3 显式类型转换(函数式表示法)[expr.type.conv]
-
- 5.4 显式类型转换(强制转换表示法)[expr.cast]
- C++11 标准 (ISO/IEC 14882:2011):
-
- 5.2.3 显式类型转换(函数表示法)[expr.type.conv]
-
- 5.4 显式类型转换(强制转换表示法)[expr.cast]
- C++03 标准 (ISO/IEC 14882:2003):
-
- 5.2.3 显式类型转换(函数表示法)[expr.type.conv]
-
- 5.4 显式类型转换(强制转换表示法)[expr.cast]
- C++98 标准 (ISO/IEC 14882:1998):
-
- 5.2.3 显式类型转换(函数表示法)[expr.type.conv]
-
- 5.4 显式类型转换(强制转换表示法)[expr.cast]
参见
const_cast
转换
|
添加或移除 const 限定符 |
static_cast
转换
|
执行基础类型转换 |
dynamic_cast
转换
|
执行带检查的多态转换 |
reinterpret_cast
转换
|
执行通用底层转换 |
| 标准转换 | 类型间的隐式转换 |
|
C 文档
关于
强制转换运算符
|
|