Implicit conversions
当某种类型
T1
的表达式被用于不接受该类型但接受其他类型
T2
的上下文时,就会执行隐式转换;具体包括:
-
当表达式作为实参传递给声明参数类型为
T2的函数时; -
当表达式作为操作数与期望
T2类型的运算符一起使用时; -
当初始化类型为
T2的新对象时,包括返回T2类型的函数中的return语句; -
当表达式用于
switch
语句时(
T2为整型); -
当表达式用于
if
语句或循环时(
T2为 bool 类型)。
当且仅当存在从
T1
到
T2
的唯一明确
隐式转换序列
时,该程序才是良构的(可编译的)。
如果被调用的函数或运算符存在多个重载版本,在从
T1
到每个可用
T2
的隐式转换序列构建完成后,将通过
重载决议
规则决定最终编译哪个重载版本。
注意:在算术表达式中,二元运算符操作数隐式转换的目标类型由另一套独立规则决定: 常用算术转换 。
目录 |
转换顺序
隐式转换序列按以下顺序组成:
当考虑构造函数或用户定义转换函数的参数时,只允许存在一个标准转换序列(否则用户定义转换可能会形成有效链式调用)。当从一个非类类型转换到另一个非类类型时,只允许使用标准转换序列。
标准转换序列按以下顺序组成:
- 左值到右值转换 ,
- 数组到指针转换 ,以及
- 函数到指针转换 ;
|
3)
零次或一次
函数指针转换
;
|
(since C++17) |
用户定义转换由零个或一个非显式单参数 转换构造函数 或非显式 转换函数 调用组成。
当且仅当
T2
能从
e
进行
拷贝初始化
时,表达式
e
被称为
可隐式转换为
T2
,即声明语句
T2 t
=
e
;
对于某个虚构的临时变量
t
是合法(可编译)的。注意这与
直接初始化
(
T2 t
(
e
)
)不同,后者还会考虑显式构造函数和转换函数。
上下文转换
|
在以下语境中,期望出现 bool 类型,且当声明 bool t ( e ) ; 合法时(即考虑如 explicit T :: operator bool ( ) const ; 这类显式转换函数),会进行隐式转换。此类表达式 e 被称为 上下文ually转换为 bool 。
|
(since C++11) |
在以下语境中,期望使用上下文特定类型
T
,而类类型
E
的表达式
e
仅在满足以下条件时被允许:
|
(直至 C++14) |
|
(自 C++14 起) |
此类表达式
e
被称为被
上下文隐式转换
为指定类型
T
。
注意显式转换函数不会被考虑,尽管它们在向
bool
的上下文转换中会被考虑。
(C++11 起)
-
delete 表达式
的参数(
T为任意对象指针类型); -
使用字面量类的
整型常量表达式
(
T为任意整型或无作用域枚举类型,所选用户定义转换函数必须为 constexpr ); -
switch语句的控制表达式(T为任意整型或枚举类型)。
#include <cassert> template<typename T> class zero_init { T val; public: zero_init() : val(static_cast<T>(0)) {} zero_init(T val) : val(val) {} operator T&() { return val; } operator T() const { return val; } }; int main() { zero_init<int> i; assert(i == 0); i = 7; assert(i == 7); switch (i) {} // C++14 之前的错误(存在多个转换函数) // C++14 起正常(两个函数都转换为相同的 int 类型) switch (i + 0) {} // 始终正常(隐式转换) }
值转换
值变换是改变表达式 值类别 的转换。当表达式作为运算符的操作数出现,而该运算符期望不同值类别的表达式时,就会发生值变换:
- 当泛左值作为需要纯右值操作数的运算符操作数出现时,会应用 左值到右值转换 、 数组到指针转换 或 函数到指针转换 标准转换,将表达式转换为纯右值。
|
(since C++17) |
左值到右值转换
一个
左值
(C++11 前)
一个
泛左值
(C++11 起)
,其类型为任何非函数、非数组类型
T
,可以隐式转换为
一个
右值
(C++11 前)
一个
纯右值
(C++11 起)
:
-
如果
T不是类类型,则该 右值 (C++11 前) 纯右值 (C++11 起) 的类型是T的 cv 非限定版本。 -
否则,该
右值
(C++11 前)
纯右值
(C++11 起)
的类型是
T。
如果程序需要对 不完整类型 进行左值到右值的转换,则该程序是非良构的。
给定 lvalue (until C++11) glvalue (since C++11) 所引用的对象为 obj :
|
(C++11 前) | ||||
|
(C++11 起) |
此转换模拟了将值从内存位置读取到 CPU 寄存器的操作。
数组到指针转换
类型为“
N
个
T
的数组”或“未知边界
T
数组”的
左值
或
右值
,可隐式转换为类型“指向
T
的指针”的
纯右值
。
若该数组为纯右值,则发生
临时物化
。
(C++17 起)
结果指针指向数组的首元素(详见
数组到指针退化
)。
函数到指针转换
一个 左值 函数类型可以隐式转换为指向该函数的 纯右值 指针 。这不适用于非静态成员函数,因为不存在引用非静态成员函数的左值。
临时物化
任何完整类型
若
struct S { int m; }; int i = S().m; // 自 C++17 起成员访问期待泛左值; // S() 纯右值被转换为亡值 临时物化在以下情形发生:
注意:当从同类型纯右值初始化对象时(通过 直接初始化 或 拷贝初始化 ),临时物化 不会 发生:该对象直接从初始化器初始化。这确保了“保证的拷贝消除”。 |
(C++17 起) |
整型提升
纯右值 的小型整型类型(如 char )和无作用域枚举类型可转换为较大整型类型(如 int )的纯右值。特别地, 算术运算符 不接受小于 int 的类型作为参数,若适用时,整型提升会在左值到右值转换后自动执行。此转换始终保留原值。
以下本节中的隐式转换被归类为 整型提升 。
请注意,对于给定的源类型,整型提升的目标类型是唯一的,所有其他转换都不属于提升。例如, 重载决议 会优先选择 char -> int (提升)而非 char -> short (转换)。
整型提升
类型为 bool 的纯右值可转换为类型为 int 的纯右值,其中 false 转换为 0 , true 转换为 1 。
对于除
bool
之外的整型
T
的纯右值
val
:
- 若 int 能表示该位域的所有值,则 val 可转换为 int 类型的纯右值;
- 否则,若 unsigned int 能表示该位域的所有值,则 val 可转换为 unsigned int ;
- 否则, val 可按第(3)项规定的规则进行转换。
-
若
T是 char8_t , (C++20 起) char16_t , char32_t 或 (C++11 起) wchar_t ,则 val 可按第 (3) 项规则转换; -
否则,若
T的 整数转换等级 低于 int 的等级:
-
-
若
int
能表示
T的所有值,则 val 可转换为类型为 int 的纯右值; - 否则, val 可转换为类型为 unsigned int 的纯右值。
-
若
int
能表示
T
为指定字符类型)的情况下,
val
可被转换为以下类型中首个能表示其底层类型所有值的纯右值:
-
- int
- unsigned int
- long
- unsigned long
|
(C++11 起) |
枚举类型的提升
底层类型未固定的无作用域 枚举 类型的纯右值,可转换为以下列表中首个能容纳其整个值范围的类型的纯右值:
- int
- unsigned int
- long
- unsigned long
|
(自 C++11 起) |
|
底层类型固定的无作用域枚举类型的纯右值可转换为其底层类型。此外,若底层类型同样适用于整型提升,则可转换为提升后的底层类型。就 重载决议 而言,转换为未提升的底层类型是更优选择。 |
(since C++11) |
浮点提升
类型为 float 的 纯右值 可被转换为类型为 double 的纯右值。该值保持不变。
这种转换称为 浮点提升 。
数值转换
与提升不同,数值转换可能会改变数值,并可能导致精度损失。
整型转换
整数类型或非限定作用域枚举类型的 纯右值 可转换为任意其他整数类型。若该转换属于整型提升范畴,则视为提升而非转换。
-
若目标类型为无符号类型,则结果值等于源值对
2
n
取模的最小无符号值,其中 n 表示目标类型的位宽。
-
- 也就是说,根据目标类型是更宽还是更窄,有符号整数会进行符号扩展 [1] 或截断处理,而无符号整数则会分别进行零扩展或截断处理。
-
若目标类型为有符号类型,且源整数值可在目标类型中表示,则值不变。否则结果是
由实现定义
(C++20 前)
目标类型中等于源值模
2
n
的唯一值,其中 n 是用于表示目标类型的位数 (C++20 起) (注意这与 有符号整数算术溢出 不同,后者是未定义行为)。 - 若源类型是 bool ,则值 false 被转换为零,值 true 被转换为目标类型的值一(注意若目标类型是 int ,此为整数提升而非整数转换)。
- 若目标类型是 bool ,此为 布尔转换 (见下文)。
浮点转换
|
浮点类型的 纯右值 可被转换为其他任意浮点类型的纯右值。 |
(C++23 前) |
|
浮点类型的 纯右值 可被转换为具有更高或相等 浮点转换等级 的其他任意浮点类型的纯右值。 标准浮点类型的 纯右值 可被转换为其他任意标准浮点类型的纯右值。
可使用
|
(C++23 起) |
如果转换列在浮点提升下,则它是提升而非转换。
- 若源值在目标类型中能够精确表示,则其值保持不变。
- 若源值介于目标类型的两个可表示值之间,则结果为这两个值之一(具体选择哪个值由实现定义,但若支持IEEE算术,默认采用 就近舍入 规则)。
- 否则,行为未定义。
浮点-整型转换
浮点类型的 纯右值 可转换为任意整数类型的纯右值。小数部分会被截断,即直接舍弃小数部分。
- 如果截断后的值无法放入目标类型,则行为未定义(即使目标类型是无符号类型,模运算也不适用)。
- 如果目标类型是 bool ,这属于布尔转换(参见 下文 )。
整数或无作用域枚举类型的纯右值可转换为任何浮点类型的纯右值。若可能,结果将是精确的。
- 如果该值能够容纳到目标类型中但无法精确表示,具体选择最接近的高位可表示值还是最接近的低位可表示值由实现定义,但如果支持 IEEE 算术,默认采用 就近舍入 规则。
- 如果该值无法容纳到目标类型中,则行为未定义。
- 如果源类型是 bool ,则值 false 被转换为零,值 true 被转换为一。
指针转换
一个 空指针常量 可以转换为任意指针类型,转换结果是该类型的空指针值。这种转换(称为 空指针转换 )允许直接转换为cv限定类型作为单次转换,即不被视为数值转换和限定转换的组合。
指向任何(可选地带有 cv 限定符)对象类型
T
的
纯右值
指针可转换为指向(具有相同 cv 限定符的)
void
的纯右值指针。转换后的指针与原始指针值指向内存中的同一位置。
- 若原指针为空指针值,则结果为目标类型的空指针值。
一个类型为“指向(可能带有 cv 限定符的)
Derived
的指针”的纯右值
ptr
,可被转换为类型“指向(可能带有 cv 限定符的)
Base
的指针”的纯右值,其中
Base
是
Derived
的
基类
,且
Derived
是
完整
的类类型。若
Base
不可访问或存在二义性,则程序非良构。
- 若 ptr 是空指针值,则结果同样是空指针值。
-
否则,若
Base是Derived的 虚基类 ,且 ptr 未指向一个类型与Derived相似 、且处于其 生存期 或构造/析构期间的对象,则行为未定义。 - 否则,结果将指向派生类对象的基类子对象。
指向成员指针的转换
一个 空指针常量 可以转换为任意成员指针类型,转换结果是该类型的空成员指针值。这种转换(称为 空成员指针转换 )允许直接转换为cv限定类型,即不被视为数值转换和限定转换的组合。
一个类型为“指向
Base
类成员(类型为可能带有cv限定符的
T
)的指针”的
纯右值
,可转换为类型为“指向
Derived
类成员(类型为完全相同的cv限定符的
T
)的指针”的纯右值,其中
Base
是
Derived
的基类,且
Derived
是完整类类型。若
Base
是
Derived
的不可访问基类、歧义基类、虚基类,或是
Derived
某个中间虚基类的基类,则程序非良构。
-
如果
Derived不包含原始成员且不是包含原始成员的类的基类,则行为未定义。 -
否则,生成的指针可以通过
Derived对象解引用,并将访问该Derived对象中Base基类子对象内的成员。
布尔转换
整型、浮点型、无作用域枚举类型、指针类型及成员对象指针类型的 纯右值 可转换为 bool 类型的纯右值。
值为零(对于整型、浮点型和无作用域枚举)以及空指针和空成员指针值会变为 false 。所有其他值会变为 true 。
|
在 直接初始化 的语境中, bool 对象可以通过类型为 std::nullptr_t 的纯右值(包括 nullptr )进行初始化。其结果为 false 。但此行为不被视为隐式转换。 |
(since C++11) |
限定性转换
一般来说:
-
类型为指向
cv限定
类型
T的指针的纯右值,可转换为指向具有更多cv限定(即可以添加const和volatile限定符)的相同类型T的指针纯右值。 -
类型为指向类
X中cv限定类型T成员指针的纯右值,可转换为指向类X中 具有更多cv限定 类型T成员指针的纯右值。
“限定转换”的形式化定义在 下文 给出。
相似类型
非正式地说,如果忽略顶层 cv 限定符,两种类型是 相似 的:
- 它们是相同类型;或
- 它们都是指针,且所指向的类型相似;或
- 它们都是指向同一类成员的指针,且所指向的成员类型相似;或
- 它们都是数组,且数组元素类型相似。
例如:
- const int * const * 与 int ** 是相似的;
- int ( * ) ( int * ) 与 int ( * ) ( const int * ) 不是相似的;
- const int ( * ) ( int * ) 与 int ( * ) ( int * ) 不是相似的;
- int ( * ) ( int * const ) 与 int ( * ) ( int * ) 是相似的(它们是同一类型);
- std:: pair < int , int > 与 std:: pair < const int , int > 不是相似的。
形式上,类型相似性是通过限定分解来定义的。
类型的
限定符分解
是指一组分量
cv_i
和
P_i
构成的序列,使得类型
T
可表示为“
cv_0 P_0 cv_1 P_1 ... cv_n−1 P_n−1 cv_n U
”,其中
n
为非负整数。
-
每个
cv_i是一组 const 和 volatile 限定符,且 -
每个
P_i是
-
- “指向”,
-
“指向
C_i类成员的指针,其类型为”, - “ N_i 个元素的数组”,或
- “未知边界数组”。
如果
P_i
表示数组,则元素类型上的 cv 限定符
cv_i+1
也会被视为数组的 cv 限定符
cv_i
。
// T 是“指向常量整型指针的指针”,它有 3 层限定符分解: // n = 0 -> cv_0 为空,U 是“指向常量整型指针的指针” // n = 1 -> cv_0 为空,P_0 是“指向”, // cv_1 为空,U 是“指向常量整型的指针” // n = 2 -> cv_0 为空,P_0 是“指向”, // cv_1 为空,P_1 是“指向”, // cv_2 是“const”,U 是“整型” using T = const int**; // 将以下任意类型代入 U 可得到对应的分解: // U = U0 -> 对应 n = 0 的分解:U0 // U = U1 -> 对应 n = 1 的分解:指向 [U1] 的指针 // U = U2 -> 对应 n = 2 的分解:指向 [指向 [常量 U2]] 的指针 using U2 = int; using U1 = const U2*; using U0 = U1*;
两种类型
T1
和
T2
是
相似
的,如果存在它们各自的限定分解,且两个限定分解满足以下所有条件:
- 它们具有相同的 n 。
-
由
U表示的类型相同。 -
对应的
P_i组件对于所有 i 都相同 或者一个是“ N_i 的数组”而另一个是“未知边界的数组” (C++20 起) 。
// 限定符分解(n = 2 的情况): // 指向[指向[const int]的volatile指针]的指针 using T1 = const int* volatile *; // 限定符分解(n = 2 的情况): // 指向[指向[int]的指针]的const指针 using T2 = int** const; // 对于上述两种限定符分解 // 尽管 cv_0、cv_1 和 cv_2 均不相同 // 但它们具有相同的 n、U、P_0 和 P_1 // 因此类型 T1 和 T2 是相似的
cv限定符的组合
在以下描述中,类型
Tn
的最长限定分解记作
Dn
,其组成部分记作
cvn_i
和
Pn_i
。
|
若满足以下所有条件,则类型为
两种类型
|
(C++20 前) |
|
两种类型
若
|
(C++20 起) |
// T1 的最长限定符分解(n = 2): // 指向 [指向 [char] 的指针] 的指针 using T1 = char**; // T2 的最长限定符分解(n = 2): // 指向 [指向 [const char] 的指针] 的指针 using T2 = const char**; // 确定 D3 的 cv3_i 和 T_i 分量(n = 2): // cv3_1 = 空(空 cv1_1 与空 cv2_1 的并集) // cv3_2 = “const”(空 cv1_2 与 “const” cv2_2 的并集) // P3_0 = “指向”(无未知边界数组,使用 P1_0) // P3_1 = “指向”(无未知边界数组,使用 P1_1) // 除 cv_2 外所有分量相同,cv3_2 与 cv1_2 不同, // 因此为每个 k ∈ [1, 2) 的 cv3_k 添加 “const”:cv3_1 变为 “const”。 // T3 是“指向 const 指针的指针,该指针指向 const char”,即 const char* const *。 using T3 = /* T1 和 T2 的限定符合并类型 */; int main() { const char c = 'c'; char* pc; T1 ppc = &pc; T2 pcc = ppc; // 错误:T3 与 cv 非限定 T2 类型不同, // 无隐式转换。 *pcc = &c; *pc = 'C'; // 若允许上述错误赋值, // const 对象 “c” 可能被修改。 }
请注意在C语言中, const / volatile 仅能添加在第一层级:
char** p = 0; char * const* p1 = p; // 在 C 和 C++ 中均有效 const char* const * p2 = p; // 在 C 中错误,在 C++ 中有效
函数指针转换
void (*p)(); void (**pp)() noexcept = &p; // error: cannot convert to pointer to noexcept function struct S { typedef void (*p)(); operator p(); }; void (*q)() noexcept = S(); // error: cannot convert to pointer to noexcept function |
(C++17 起) |
安全布尔问题
在C++11之前,设计一个可用于布尔上下文的类(例如 if ( obj ) { ... } )存在一个问题:给定用户定义的转换函数,如 T :: operator bool ( ) const ; ,隐式转换序列允许在该函数调用后额外添加一个标准转换序列,这意味着生成的 bool 值可以转换为 int ,从而允许出现诸如 obj << 1 ; 或 int i = obj ; 的代码。
针对此问题的早期解决方案可见于 std::basic_ios ,它最初定义了 operator void * ,使得诸如 if ( std:: cin ) { ... } 的代码能够编译通过,因为 void * 可转换为 bool ;但 int n = std:: cout ; 无法编译,因为 void * 不可转换为 int 。这种方式仍允许无意义代码(例如 delete std:: cout ; )通过编译。
许多C++11之前的第三方库采用了一种更为精细的解决方案,即 安全布尔惯用法 。 std::basic_ios 也通过 LWG问题468 支持此惯用法,且 operator void * 已被替代(参见 注释 )。
自 C++11 起, explicit bool 转换 也可用于解决安全布尔问题。
缺陷报告
下列行为变更缺陷报告被追溯应用于先前发布的C++标准。
| 缺陷报告 | 适用标准 | 发布时行为 | 正确行为 |
|---|---|---|---|
| CWG 170 | C++98 |
当派生类不具有原始成员时
指向成员转换的行为不明确 |
予以明确 |
| CWG 172 | C++98 | 枚举类型提升基于其底层类型 | 改为基于其值范围 |
|
CWG 330
( N4261 ) |
C++98 |
从
double
*
const
(
*
p
)
[
3
]
到 double const * const ( * p ) [ 3 ] 的转换无效 |
改为有效 |
| CWG 519 | C++98 |
空指针值转换为其他指针类型时
不保证保留 |
始终保留 |
| CWG 616 | C++98 |
对任何未初始化对象和无效值指针对象
进行左值到右值转换的行为始终未定义 |
允许不确定的
unsigned
char
使用无效指针 由实现定义 |
| CWG 685 | C++98 |
若枚举类型具有固定底层类型
整型提升时未优先考虑该底层类型 |
优先考虑 |
| CWG 707 | C++98 |
整数到浮点数转换
在所有情况下都有定义行为 |
若被转换值超出
目标范围则行为未定义 |
| CWG 1423 | C++11 |
std::nullptr_t
在直接初始化和复制初始化中
均可转换为 bool |
仅限直接初始化 |
| CWG 1773 | C++11 |
出现在潜在求值表达式中的名称表达式
即使命名对象未被odr使用仍可能在 左值到右值转换期间被求值 |
不予求值 |
| CWG 1781 | C++11 |
std::nullptr_t
到
bool
被视为隐式转换
尽管仅对直接初始化有效 |
不再视为
隐式转换 |
| CWG 1787 | C++98 |
读取缓存在寄存器中的不确定
unsigned char 的行为未定义 |
改为良定义 |
| CWG 1981 | C++11 | 上下文转换考虑显式转换函数 | 不予考虑 |
| CWG 2140 | C++11 |
不清楚从
std::nullptr_t
左值进行
左值到右值转换是否会从内存获取这些左值 |
不获取 |
| CWG 2310 | C++98 |
对于派生类到基类指针转换和
基类到派生类成员指针转换 派生类类型可以不完整 |
必须完整 |
| CWG 2484 | C++20 |
char8_t
和
char16_t
具有不同的整型
提升策略,但两者均可容纳 |
char8_t
应采用
与 char16_t 相同方式提升 |
| CWG 2485 | C++98 | 涉及位域的整型提升规范不完善 | 改进规范 |
| CWG 2813 | C++23 |
调用类纯右值的显式对象成员函数时
会发生临时物化 |
此情况下
不会发生 |
| CWG 2861 | C++98 |
指向类型不可访问对象的指针可被
转换为指向基类子对象的指针 |
此情况下
行为未定义 |
| CWG 2879 | C++17 |
临时物化转换应用于期望左值的
运算符的纯右值操作数 |
某些情况下不应用 |
| CWG 2899 | C++98 |
左值到右值转换可应用于
具有无效值表示的左值对象 |
此情况下
行为未定义 |
| CWG 2901 | C++98 |
从引用值为
-
1
的
int
对象的
unsigned int 左值进行左值到右值转换的结果不明确 |
予以明确 |
参见
|
C 文档
关于
隐式转换
|