隐式转换
当类型 T1
的表达式被用在不接受该类型但接受其他类型 T2
的语境中时,就会执行隐式转换;特别是在以下情况:
- 当表达式作为参数用于调用声明参数类型为
T2
的函数时; - 当表达式作为期望
T2
的运算符的操作数时; - 当初始化一个类型为
T2
的新对象时,包括函数返回T2
时使用的return
语句; - 当表达式用于 switch 语句时(
T2
为整型); - 当表达式用于 if 语句或循环时(
T2
为 bool)。
只有当存在一个从 T1
到 T2
的明确的*隐式转换序列*时,程序才格式良好(可编译)。
如果被调用的函数或运算符有多个重载,在构建了从 T1
到每个可用 T2
的隐式转换序列后,重载决议规则将决定哪个重载被编译。
注意:在算术表达式中,二元运算符操作数的隐式转换的目标类型由一套独立的规则决定:常用算术转换。
目录 |
[编辑] 转换顺序
隐式转换序列按以下顺序包含:
在考虑构造函数或用户定义转换函数的参数时,只允许一个标准转换序列(否则用户定义转换可能被有效地链接)。当从一个非类类型转换为另一个非类类型时,只允许一个标准转换序列。
标准转换序列按以下顺序包含:
- 左值到右值转换,
- *数组到指针转换*,以及
- 函数到指针转换;
3) 零个或一个*函数指针转换*;
|
(C++17 起) |
用户定义转换包含零个或一个非显式单参数转换构造函数或非显式转换函数调用。
当且仅当 T2
可以从 e 复制初始化时,表达式 e 被认为是*隐式可转换为 T2
*,即对于某个虚构的临时变量 t
,声明 T2 t = e; 是格式良好的(可以编译)。请注意,这与直接初始化(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-expression 的参数(
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) }
[编辑] 值转换
值转换是改变表达式值类别的转换。当表达式作为运算符的操作数出现,而运算符期望不同值类别的表达式时,就会发生值转换。
- 每当 glvalue 作为需要 prvalue 的运算符的操作数出现时,会应用*左值到右值*、*数组到指针*或*函数到指针*的标准转换,将表达式转换为 prvalue。
|
(C++17 起) |
[编辑] 左值到右值转换
任何非函数、非数组类型 T
的左值(C++11 前)任何非函数、非数组类型 T
的glvalue(C++11 起) 可以隐式转换为右值(C++11 前)prvalue(C++11 起)。
- 如果
T
不是类类型,则右值(C++11 前)prvalue(C++11 起) 的类型是T
的 cv 非限定版本。 - 否则,右值(C++11 前)prvalue(C++11 起) 的类型是
T
。
如果程序需要从不完全类型进行左值到右值转换,则该程序格式错误。
给定 左值(C++11 前)glvalue(C++11 起) 所指的对象为 obj
|
(C++11 前) | ||||
|
(C++11 起) |
此转换模拟将值从内存位置读取到 CPU 寄存器的行为。
[编辑] 数组到指针转换
“N
个 T
数组”或“未知边界的 T
数组”类型的左值或右值可以隐式转换为“指向 T
的指针”类型的prvalue。如果数组是 prvalue,则会发生临时对象实质化。(C++17 起) 结果指针指向数组的第一个元素(详情请参阅数组到指针退化)。
[编辑] 函数到指针转换
函数类型的左值可以隐式转换为prvalue指向该函数的指针。这不适用于非静态成员函数,因为不存在引用非静态成员函数的左值。
临时对象实质化任何完整类型 如果 struct S { int m; }; int i = S().m; // member access expects glvalue as of C++17; // S() prvalue is converted to xvalue 临时对象实质化发生在以下情况:
请注意,当从相同类型的 prvalue 初始化对象时(通过直接初始化或复制初始化),不会发生临时对象实质化:这样的对象直接从初始化器初始化。这确保了“保证的复制省略”。 |
(C++17 起) |
[编辑] 整型提升
小型整型(例如 char)和无作用域枚举类型的prvalue 可以转换为较大整型(例如 int)的 prvalue。特别是,算术运算符不接受小于 int 的类型作为参数,如果适用,在左值到右值转换后会自动应用整型提升。此转换始终保留值。
本节中的以下隐式转换被归类为*整型提升*。
请注意,对于给定源类型,整型提升的目标类型是唯一的,所有其他转换都不是提升。例如,重载决议会选择 char -> int(提升)而非 char -> short(转换)。
[编辑] 从整型提升
类型 bool 的 prvalue 可以转换为类型 int 的 prvalue,其中 false 变为 0,true 变为 1。
对于除 bool 外的整型 T
的 prvalue val
- 如果 int 可以表示位域的所有值,则 val 可以转换为类型 int 的 prvalue;
- 否则,如果 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 的 prvalue; - 否则,val 可以转换为类型 unsigned int 的 prvalue。
- 如果 int 可以表示
T
是给定字符类型之一)指定的案例中,val 可以转换为以下类型中第一个可以表示其底层类型所有值的 prvalue- int
- unsigned int
- long
- unsigned long
|
(C++11 起) |
[编辑] 从枚举类型提升
底层类型不固定的无作用域枚举类型的 prvalue 可以转换为以下列表中第一个能够容纳其整个值范围的类型的 prvalue
- int
- unsigned int
- long
- unsigned long
|
(C++11 起) |
底层类型固定的无作用域枚举类型的 prvalue 可以转换为其底层类型。此外,如果底层类型也受整型提升影响,则转换为提升后的底层类型。为了重载决议的目的,转换为未提升的底层类型更好。 |
(C++11 起) |
[编辑] 浮点型提升
类型 float 的prvalue 可以转换为类型 double 的 prvalue。值不会改变。
此转换称为*浮点型提升*。
[编辑] 数值转换
与提升不同,数值转换可能会改变值,并可能导致精度损失。
[编辑] 整型转换
整型或无作用域枚举类型的prvalue 可以转换为任何其他整型。如果转换列在整型提升下,则它是一种提升而不是转换。
- 如果目标类型是无符号的,则结果值是等于源值模 2n
的最小无符号值,其中 n 是用于表示目标类型的位数。
- 也就是说,根据目标类型是更宽还是更窄,有符号整数分别进行符号扩展[1]或截断,无符号整数分别进行零扩展或截断。
- 如果目标类型是有符号的,并且源整数可以在目标类型中表示,则值不会改变。否则,结果是实现定义的(C++20 前)目标类型中唯一的值,该值等于源值模 2n
,其中 n 是用于表示目标类型的位数(C++20 起)(请注意,这与有符号整数算术溢出不同,后者是未定义的)。 - 如果源类型是 bool,则值 false 转换为零,值 true 转换为目标类型的值一(请注意,如果目标类型是 int,这是一种整型提升,而不是整型转换)。
- 如果目标类型是 bool,这是一种布尔转换(见下文)。
[编辑] 浮点型转换
浮点类型的prvalue 可以转换为任何其他浮点类型的 prvalue。 |
(直至 C++23) |
浮点类型的prvalue 可以转换为任何其他浮点类型的 prvalue,且该目标浮点类型的浮点转换等级大于或等于源类型。 标准浮点类型的prvalue 可以转换为任何其他标准浮点类型的 prvalue。
|
(C++23 起) |
如果转换列在浮点提升下,则它是一种提升而不是转换。
- 如果源值可以在目标类型中精确表示,则它不会改变。
- 如果源值介于目标类型的两个可表示值之间,则结果是这两个值之一(具体是哪一个由实现定义,尽管如果支持 IEEE 算术,则默认四舍五入到最近)。
- 否则,行为未定义。
[编辑] 浮点-整型转换
浮点类型的prvalue 可以转换为任何整型类型的 prvalue。小数部分被截断,即小数部分被丢弃。
- 如果截断后的值不能适合目标类型,则行为是未定义的(即使目标类型是无符号的,模算术也不适用)。
- 如果目标类型是 bool,这是一种布尔转换(见下文)。
整型或无作用域枚举类型的 prvalue 可以转换为任何浮点类型的 prvalue。如果可能,结果是精确的。
- 如果值可以适合目标类型但不能精确表示,则选择最接近的更高或更低的可表示值是实现定义的,尽管如果支持 IEEE 算术,则默认四舍五入到最近。
- 如果值不能适合目标类型,则行为是未定义的。
- 如果源类型是 bool,则值 false 转换为零,值 true 转换为一。
[编辑] 指针转换
空指针常量可以转换为任何指针类型,结果是该类型的空指针值。这种转换(称为*空指针转换*)允许作为单次转换转换为 cv 限定类型,即它不被视为数值转换和限定符转换的组合。
指向任何(可选 cv 限定的)对象类型 T
的prvalue 指针可以转换为指向(相同 cv 限定的)void 的 prvalue 指针。结果指针表示与原始指针值相同的内存位置。
- 如果原始指针是空指针值,则结果是目标类型的空指针值。
类型为“指向(可能带有 cv 限定符的)Derived
的指针”的 prvalue ptr 可以转换为类型为“指向(可能带有 cv 限定符的)Base
的指针”的 prvalue,其中 Base
是 Derived
的基类,且 Derived
是一个完全的类类型。如果 Base
不可访问或不明确,则程序格式错误。
- 如果 ptr 是空指针值,则结果也是空指针值。
- 否则,如果
Base
是Derived
的虚基类,并且 ptr 不指向类型与Derived
相似且在其生命周期内或其构造或析构期间的对象,则行为是未定义的。 - 否则,结果是指向派生类对象中基类子对象的指针。
[编辑] 成员指针转换
空指针常量可以转换为任何成员指针类型,结果是该类型的空成员指针值。这种转换(称为*空成员指针转换*)允许作为单次转换转换为 cv 限定类型,即它不被视为数值转换和限定符转换的组合。
类型为“指向 Base
中类型为(可能带有 cv 限定符的)T
的成员的指针”的prvalue 可以转换为类型为“指向 Derived
中类型为(相同 cv 限定的)T
的成员的指针”的 prvalue,其中 Base
是 Derived
的基类,且 Derived
是一个完整的类类型。如果 Base
不可访问、不明确、是 Derived
的虚基类或 Derived
的某个中间虚基类的基类,则程序格式错误。
- 如果
Derived
不包含原始成员,也不是包含原始成员的类的基类,则行为是未定义的。 - 否则,结果指针可以用
Derived
对象解引用,它将访问该Derived
对象中Base
基子对象内的成员。
[编辑] 布尔转换
整型、浮点型、无作用域枚举、指针和成员指针类型的prvalue 可以转换为类型 bool 的 prvalue。
零值(对于整型、浮点型和无作用域枚举)以及空指针和空成员指针值变为 false。所有其他值变为 true。
在直接初始化的语境中,bool 对象可以从类型 std::nullptr_t 的 prvalue 初始化,包括 nullptr。结果值为 false。然而,这不被认为是隐式转换。 |
(C++11 起) |
[编辑] 限定符转换
一般来说:
- 指向cv 限定类型
T
的指针的prvalue 可以转换为指向更 cv 限定的相同类型T
的 prvalue(换句话说,可以添加 constness 和 volatility)。 - 指向类
X
中 cv 限定类型T
的成员的指针的 prvalue 可以转换为指向类X
中更 cv 限定类型T
的成员的指针的 prvalue。
“限定符转换”的正式定义见下文。
[编辑] 相似类型
非正式地说,如果忽略顶层 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 限定符 cv_i+1
也被视为数组的 cv 限定符 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
表示的类型相同。- 对应的
P_i
组件对于所有 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 起,显式 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 中读取的行为是未定义的 读取不确定 unsigned char 的行为是未定义的 |
已明确定义 |
CWG 1981 | C++11 | 上下文转换考虑显式转换函数 | 不考虑 |
CWG 2140 | C++11 | 从 std::nullptr_t 左值进行的左值到右值转换是否从内存中获取这些左值,这一点不明确 std::nullptr_t 左值是否从内存中获取这些左值,这一点不明确 |
不获取 |
CWG 2310 | C++98 | 对于派生到基的指针转换和 基到派生的成员指针转换, 派生类类型可以不完整 |
必须完整 |
CWG 2484 | C++20 | char8_t 和 char16_t 的整型提升策略不同,但它们都可以容纳 它们 |
char8_t 应该以与 char16_t 相同的方式提升 与 char16_t 相同的方式 |
CWG 2485 | C++98 | 涉及位域的整型提升未明确说明 | 改进了规范 |
CWG 2813 | C++23 | 当调用类 prvalue 的显式对象成员函数时,将发生临时对象实质化 不会发生 |
不会发生 在这种情况下 |
CWG 2861 | C++98 | 指向类型不可访问对象的指针可以转换为指向基类子对象的指针 转换为指向基类子对象的指针 |
在这种情况下,行为是 未定义的 |
CWG 2879 | C++17 | 临时对象实质化转换应用于作为期望 glvalue 的运算符操作数的 prvalue 在某些情况下不适用 |
在某些情况下不适用 |
CWG 2899 | C++98 | 左值到右值转换可以应用于具有无效值表示的左值 指示具有无效值表示的对象 |
在这种情况下,行为是 未定义的 |
CWG 2901 | C++98 | 从引用值为 -1 的 int 对象的 unsigned int 左值进行左值到右值转换的结果不明确 引用值为 -1 的 int 对象 |
已明确 |
[编辑] 另请参阅
C 文档 关于 隐式转换
|