隐式转换
每当某个类型为 T1
的表达式的使用环境不接受该类型,但接受其他类型 T2
时,就会执行隐式转换;具体来说,
- 当表达式用作参数时,调用声明为
T2
作为参数的函数; - 当表达式用作操作数时,与期望
T2
的运算符一起使用; - 当初始化类型为
T2
的新对象时,包括在返回T2
的函数中的return
语句; - 当表达式在 switch 语句中使用时(
T2
是整型); - 当表达式在 if 语句或循环中使用时(
T2
是 bool)。
只有当从 T1
到 T2
存在一个唯一的隐式转换序列时,程序才是格式良好的(可以编译)。
如果存在被调用的函数或运算符的多个重载,在从 T1
到每个可用的 T2
建立隐式转换序列后,重载解析 规则将决定编译哪个重载。
注意:在算术表达式中,二元运算符的操作数上的隐式转换的目标类型由一组单独的规则确定:通常的算术转换。
内容 |
[编辑] 转换顺序
隐式转换序列按以下顺序组成:
当考虑构造函数或用户定义的转换函数的参数时,只允许一个标准转换序列(否则用户定义的转换可能会被有效地链接)。当从一个非类类型转换为另一个非类类型时,只允许一个标准转换序列。
标准转换序列按以下顺序组成:
- 左值到右值转换,
- 数组到指针转换,以及
- 函数到指针转换;
3) 零个或一个函数指针转换;
|
(自 C++17 起) |
用户定义的转换由零个或一个非显式单参数转换构造函数或非显式转换函数调用组成。
如果表达式e可以从e中复制初始化T2
,那么表达式e被称为可以隐式转换为T2
。也就是说,声明T2 t = e;对于某些临时变量t
是格式良好的(可以编译)。注意,这与直接初始化(T2 t(e))不同,在直接初始化中,显式构造函数和转换函数也会被考虑。
[编辑] 上下文转换
在以下情况下,预期类型为bool,如果声明bool t(e);是格式良好的(也就是说,考虑了像explicit T::operator bool() const;这样的显式转换函数),则执行隐式转换。表达式e被称为上下文转换为bool。
|
(自 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) {} // error until C++14 (more than one conversion function) // OK since C++14 (both functions convert to the same type int) switch (i + 0) {} // always okay (implicit conversion) }
[编辑] 值转换
值转换是更改表达式的值类别的转换。它们发生在表达式作为操作数出现在需要不同值类别的表达式的运算符时。
- 只要左值作为操作数出现在需要该操作数的右值的运算符中,左值到右值、数组到指针或函数到指针标准转换就会被应用以将表达式转换为右值。
|
(自 C++17 起) |
[编辑] 左值到右值转换
任何非函数、非数组类型T
的左值(直到 C++11)任何非函数、非数组类型T
的通用左值(自 C++11 起)可以隐式转换为右值(直到 C++11)纯右值(自 C++11 起)
- 如果
T
不是类类型,则右值(直到 C++11)纯右值(自 C++11 起)的类型是T
的 cv 无限定版本。 - 否则,右值(直到 C++11)纯右值(自 C++11 起)的类型是
T
。
如果程序需要从不完整类型进行左值到右值转换,则该程序格式不正确。
(直到 C++11) | |
当将左值到右值转换应用于表达式E时,如果以下情况,则不会访问引用的对象中包含的值: |
(自 C++11 起) |
转换的结果是左值指示的对象中包含的值。 |
(直到 C++11) | ||||||
转换的结果根据以下规则确定:
|
(自 C++11 起) |
此转换模拟将值从内存位置读入 CPU 寄存器的行为。
[编辑] 数组到指针转换
类型为“N
个T
的数组”或“未知边界的T
数组”的左值或右值可以隐式转换为类型为“指向T
的指针”的纯右值。 如果数组是纯右值,则会发生临时对象具体化。(自 C++17 起) 生成的指针引用数组的第一个元素(有关详细信息,请参见数组到指针衰减)。
[编辑] 函数到指针转换
函数类型的 左值 可以隐式转换为 纯右值 指向该函数的指针。 这不适用于非静态成员函数,因为引用非静态成员函数的左值不存在。
临时对象具现化任何完整类型 如果 struct S { int m; }; int i = S().m; // member access expects glvalue as of C++17; // S() prvalue is converted to xvalue 临时对象具现化发生在以下情况
请注意,当从相同类型的纯右值(通过 直接初始化 或 拷贝初始化)初始化对象时,不会发生临时对象具现化:此类对象直接从初始化器初始化。 这确保了“保证的拷贝省略”。 |
(自 C++17 起) |
[编辑] 整型提升
纯右值 的小型整型(如 char)和无作用域枚举类型可以转换为更大的整型(如 int)的纯右值。 特别地,算术运算符 不接受小于 int 的类型作为参数,并且整型提升会在左值到右值转换(如果适用)后自动应用。 此转换始终保留值。
本节中的以下隐式转换被归类为整型提升。
[编辑] 从整型进行提升
类型为 bool 的纯右值可以转换为类型为 int 的纯右值,其中 false 变成 0,而 true 变成 1。
对于整型 T
(除了 bool)的纯右值 val
- val 可以转换为类型为 int 的纯右值,如果 int 可以表示位域的所有值;
- 否则,val 可以转换为 unsigned int,如果 unsigned int 可以表示位域的所有值;
- 否则,val 可以根据项目 (3) 中指定的规则进行转换。
- 如果
T
是 char8_t, (自 C++20 起)char16_t, char32_t 或 (自 C++11 起)wchar_t,val 可以根据项目 (3) 中指定的规则进行转换; - 否则,如果
T
的 整型转换等级 低于 int 的等级,
- val 可以转换为类型为 int 的纯右值,如果 int 可以表示
T
的所有值; - 否则,val 可以转换为类型为 unsigned int 的纯右值。
- val 可以转换为类型为 int 的纯右值,如果 int 可以表示
T
是给定字符类型之一)中指定的情况下,val 可以转换为以下类型中第一个能够表示其底层类型所有值的纯右值- int
- unsigned int
- long
- unsigned long
|
(自 C++11 起) |
[编辑] 从枚举类型进行提升
底层类型未固定的无作用域 枚举 类型的纯右值可以转换为以下列表中第一个能够容纳其整个值范围的类型
- int
- unsigned int
- long
- unsigned long
|
(自 C++11 起) |
底层类型固定的无作用域枚举类型的纯右值可以转换为其底层类型。 此外,如果底层类型也受整型提升,则可以转换为提升后的底层类型。 为了 重载解析 的目的,转换为未提升的底层类型更好。 |
(自 C++11 起) |
请注意,所有其他转换都不是提升;例如,重载解析 选择 char -> int(提升)而不是 char -> short(转换)。
[编辑] 浮点提升
类型为 float 的 纯右值 可以转换为类型为 double 的纯右值。 值不会改变。
此转换称为浮点提升。
[编辑] 数值转换
与提升不同,数值转换可能会改变值,并可能导致精度丢失。
[编辑] 整型转换
整型或无作用域枚举类型的 纯右值 可以转换为任何其他整型。 如果转换在整型提升下列出,则它是一个提升,而不是一个转换。
- 如果目标类型是无符号的,则结果值是最小的无符号值,等于源值 模 2n
,其中 n 是用于表示目标类型的位数。
- 也就是说,根据目标类型是更宽还是更窄,带符号整数分别进行符号扩展[1] 或截断,而无符号整数分别进行零扩展或截断。
- 如果目标类型是带符号的,如果源整数可以在目标类型中表示,则值不会改变。 否则,结果 实现定义(在 C++20 之前)是目标类型中唯一等于源值模 2n
的值,其中 n 是用于表示目标类型的位数(自 C++20 起)(注意,这与 带符号整数算术运算溢出 不同,后者是未定义的)。 - 如果源类型是 bool,则值 false 转换为零,而值 true 转换为目标类型的值一(注意,如果目标类型是 int,则这是一个整型提升,而不是一个整型转换)。
- 如果目标类型是 bool,则这是一个 布尔转换(见下文)。
[edit] 浮点数转换
浮点类型的 右值 可以转换为任何其他浮点类型的右值。 |
(直到 C++23) |
浮点类型的 右值 可以转换为任何其他具有大于或等于 浮点数转换等级 的浮点类型的右值。 标准浮点类型的 右值 可以转换为任何其他标准浮点类型的右值。
|
(从 C++23 起) |
如果转换列在浮点数提升下,则它是一种提升,而不是转换。
- 如果源值可以准确地表示在目标类型中,则它不会改变。
- 如果源值在目标类型的两个可表示值之间,则结果是这两个值之一(它是实现定义的哪一个,尽管如果支持 IEEE 算术,则舍入默认值 最接近)。
- 否则,行为未定义。
[edit] 浮点-整数转换
浮点类型的 右值 可以转换为任何整数类型的右值。小数部分被截断,即小数部分被丢弃。
- 如果截断的值不能放入目标类型中,则行为未定义(即使目标类型是无符号的,模算术也不适用)。
- 如果目标类型是 bool,则这是布尔转换(见 下文)。
整数或无范围枚举类型的右值可以转换为任何浮点类型的右值。结果尽可能准确。
- 如果该值可以放入目标类型但不能完全表示,则实现定义的是选择最接近的较高值还是最接近的较低值,尽管如果支持 IEEE 算术,则舍入默认值 最接近。
- 如果该值不能放入目标类型,则行为未定义。
- 如果源类型是 bool,则值 false 被转换为零,值 true 被转换为一。
[edit] 指针转换
空指针常量 可以转换为任何指针类型,结果是该类型的空指针值。此类转换(称为空指针转换)允许转换为 cv 限定类型作为单个转换,即它不被视为数字转换和限定转换的组合。
任何(可选的 cv 限定)对象类型 T
的 右值 指针可以转换为(相同 cv 限定)void 的右值指针。结果指针表示与原始指针值相同的内存位置。
- 如果原始指针是空指针值,则结果是目标类型的空指针值。
类型为“指向(可能为 cv 限定的)Derived
的指针”的右值 ptr 可以转换为类型为“指向(可能为 cv 限定的)Base
的指针”的右值,其中 Base
是 Derived
的 基类,并且 Derived
是一个 完整 类类型。如果 Base
不可访问或不明确,则程序是非法的。
- 如果 ptr 是空指针值,则结果也是空指针值。
- 否则,如果
Base
是Derived
的 虚基类 并且 ptr 不指向类型与Derived
相似 且在它的 生命周期 或在它的构造或析构期间的对象,则行为未定义。 - 否则,结果是指向派生类对象的基类子对象的指针。
[edit] 指向成员的指针转换
空指针常量 可以转换为任何指向成员的指针类型,结果是该类型的空成员指针值。此类转换(称为空成员指针转换)允许转换为 cv 限定类型作为单个转换,即它不被视为数字转换和限定转换的组合。
类型为“指向 Base
中类型为(可能为 cv 限定的)T
的成员的指针”的 右值 可以转换为类型为“指向 Derived
中类型为(相同 cv 限定的)T
的成员的指针”的右值,其中 Base
是 Derived
的基类,并且 Derived
是一个完整类类型。如果 Base
不可访问、不明确或 Derived
的虚基类,或者它是 Derived
的一些中间虚基类的基类,则程序是非法的。
- 如果
Derived
不包含原始成员并且不是包含原始成员的类的基类,则行为未定义。 - 否则,结果指针可以用
Derived
对象解除引用,它将访问该Derived
对象的Base
基类子对象内的成员。
[edit] 布尔转换
整数、浮点数、无范围枚举、指针和指向成员的指针类型的 右值 可以转换为类型为 bool 的右值。
值零(对于整数、浮点数和无范围枚举)以及空指针和空指向成员的指针值变为 false。所有其他值变为 true。
在 直接初始化 的上下文中,bool 对象可以从类型为 std::nullptr_t 的右值(包括 nullptr)初始化。结果值为 false。但是,这不被视为隐式转换。 |
(自 C++11 起) |
[edit] 限定转换
一般来说
- 类型为指向 cv 限定 类型
T
的指针的 右值 可以转换为类型为指向更 cv 限定的相同类型T
的指针的右值(换句话说,可以添加 const 和 volatile)。 - 类型为指向类
X
中的 cv 限定类型T
的成员的指针的右值可以转换为类型为指向类X
中 更 cv 限定 类型T
的成员的指针的右值。
“限定转换”的正式定义在 下面 给出。
[edit] 相似类型
非正式地说,如果忽略顶层 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> 不是相似的。
正式地说,类型相似性是在限定分解方面定义的。
类型 T
的限定分解是 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_i+1
修饰符也被视为数组的cv_i
修饰符。
// T is “pointer to pointer to const int”, it has 3 qualification-decompositions: // n = 0 -> cv_0 is empty, U is “pointer to pointer to const int” // n = 1 -> cv_0 is empty, P_0 is “pointer to”, // cv_1 is empty, U is “pointer to const int” // n = 2 -> cv_0 is empty, P_0 is “pointer to”, // cv_1 is empty, P_1 is “pointer to”, // cv_2 is “const", U is “int” using T = const int**; // substitute any of the following type to U gives one of the decompositions: // U = U0 -> the decomposition with n = 0: U0 // U = U1 -> the decomposition with n = 1: pointer to [U1] // U = U2 -> the decomposition with n = 2: pointer to [pointer to [const U2]] using U2 = int; using U1 = const U2*; using U0 = U1*;
如果两个类型T1
和T2
存在各自的限定分解,并且满足以下所有条件,则称它们是相似的
- 它们具有相同的n。
U
所表示的类型相同。- 对于所有i,相应的
P_i
组件相同或者其中一个是“大小为N_i的数组”,另一个是“大小未知的数组”(自 C++20 起)。
// the qualification-decomposition with n = 2: // pointer to [volatile pointer to [const int]] using T1 = const int* volatile *; // the qualification-decomposition with n = 2: // const pointer to [pointer to [int]] using T2 = int** const; // For the two qualification-decompositions above // although cv_0, cv_1 and cv_2 are all different, // they have the same n, U, P_0 and P_1, // therefore types T1 and T2 are similar.
[编辑] 组合 cv-限定符
在下面的描述中,类型Tn
的最长限定分解表示为Dn
,其组件表示为cvn_i
和Pn_i
。
如果满足以下所有条件,则类型
两个类型
|
(直到 C++20) |
两个类型
如果 |
(自 C++20 起) |
// longest qualification-decomposition of T1 (n = 2): // pointer to [pointer to [char]] using T1 = char**; // longest qualification-decomposition of T2 (n = 2): // pointer to [pointer to [const char]] using T2 = const char**; // Determining the cv3_i and T_i components of D3 (n = 2): // cv3_1 = empty (union of empty cv1_1 and empty cv2_1) // cv3_2 = “const” (union of empty cv1_2 and “const” cv2_2) // P3_0 = “pointer to” (no array of unknown bound, use P1_0) // P3_1 = “pointer to” (no array of unknown bound, use P1_1) // All components except cv_2 are the same, cv3_2 is different from cv1_2, // therefore add “const” to cv3_k for each k in [1, 2): cv3_1 becomes “const”. // T3 is “pointer to const pointer to const char”, i.e., const char* const *. using T3 = /* the qualification-combined type of T1 and T2 */; int main() { const char c = 'c'; char* pc; T1 ppc = &pc; T2 pcc = ppc; // Error: T3 is not the same as cv-unqualified T2, // no implicit conversion. *pcc = &c; *pc = 'C'; // If the erroneous assignment above is allowed, // the const object “c” may be modified. }
请注意,在 C 编程语言中,const/volatile只能添加到第一层
char** p = 0; char * const* p1 = p; // OK in C and C++ const char* const * p2 = p; // error in C, OK in 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 issue 468 允许使用这种习惯用法,并且 operator void* 被替换(参见注释)。
从 C++11 开始,显式布尔转换 也可用于解决安全布尔问题。
[编辑] 缺陷报告
以下更改行为的缺陷报告已追溯应用于先前发布的 C++ 标准。
DR | 应用于 | 发布的行为 | 正确的行为 |
---|---|---|---|
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 | 指向类型不可访问对象的指针可能会 转换为指向基类子对象的指针 |
在这种情况下,行为是 未定义的 |
[编辑] 参见
C 文档 针对 隐式转换
|