显式类型转换
来自cppreference.com
用显式和隐式转换的组合进行类型之间的转换。
语法
( 类型标识 ) 一元表达式
|
(1) | ||||||||
简单类型说明符 ( 表达式列表 (可选) )简单类型说明符 ( 初始化器列表 (可选) )
|
(2) | (C++11 前) (C++11 起) | |||||||
简单类型说明符 { 初始化器列表 (可选) }
|
(3) | (C++11 起) | |||||||
简单类型说明符 { 指派初始化器列表 }
|
(4) | (C++20 起) | |||||||
typename 标识符 ( 初始化器列表 (可选) )
|
(5) | (C++11 起) | |||||||
typename 标识符 { 初始化器列表 (可选) }
|
(6) | (C++11 起) | |||||||
typename 标识符 { 指派初始化器列表 }
|
(7) | (C++20 起) | |||||||
将任意数量的值显式转换成目标类型的值。
1) 显式类型转换(cast 表示法),也被称为 C 风格转换。
2-7) 显式类型转换(函数表示法),也被称为函数风格转换。
| 类型标识 | - | 类型标识 |
| 一元表达式 | - | 一元表达式(即顶层运算符的优先级不高于 C 风格转换的表达式) |
| 简单类型说明符 | - | 简单类型说明符 |
| 表达式列表 | - | (无括号的逗号表达式以外的)任意表达式组成的逗号分隔列表 |
| 初始化器列表 | - | 初始化器子句组成的逗号分隔列表 |
| 指派初始化器列表 | - | 指派初始化器子句组成的逗号分隔列表 |
| 标识符 | - | (可有限定的)标识符(包括模板标识) |
解释
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-7) 函数风格转换会指定一个类型(简单类型说明符 或标识符 (C++11 起))和一个初始化器(剩余部分),它会构造一个具有目标类型
T 的值,该类型以指定的类型和初始化器(C++17 起)确定。
|
|
(C++17 前) | ||
|
按以下方式确定
|
(C++17 起) |
按以下方式确定转换结果:
- 如果函数风格转换具有语法 (2),并且圆括号恰好包含一个表达式,那么该转换等价于对应的 C 风格转换。
- 否则,如果
T是(可有 cv 限定的)void,那么结果是不进行初始化的void类型右值(C++11 前)纯右值(C++11 起)。
|
(C++11 前) |
|
(C++11 起) |
- 否则,如果
T是引用类型,那么该函数风格转换的效果与以指定的初始化器直接初始化一个T类型虚设变量t相同,并且结果就是初始化完成时的t。
|
(C++11 前) |
|
(C++11 起) |
- 否则结果是
T类型右值(C++11 前)纯右值(C++11 起),它指代的临时量(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));
}
|
然而,如果在有歧义的声明语句中最外层的声明符有尾随返回类型,那么只有在尾随返回类型是 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); // 对象声明:使用无歧义的语法
}
|
然而,如果有歧义的形参声明最外层的声明符有尾随返回类型,那么只有在尾随返回类型是 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; // 类型标识(非良构)
}
|
然而,如果有歧义的类型标识中最外层的抽象声明符 有尾随返回类型,那么只有在尾随返回类型是 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 | C 风格转换在 static_cast 后随 const_cast存在多种解释时非良构,即使实际不会使用这种转换 |
只会考虑实际会使用的转换 |
| CWG 2894 | C++98 | 函数风格转换可能会创建引用右值 | 只能创建引用左值 |
引用
- 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 转换 | 进行通用低层转换 |
| 标准转换 | 从一个类型到另一类型的隐式转换 |
转换运算符的 C 文档
| |