隐式转换

来自cppreference.com
< cpp‎ | language
 
 
 
表达式
概述
值类别(左值 lvalue 、右值 rvalue 、亡值 xvalue )
求值顺序(序列点)
常量表达式
不求值表达式
初等表达式
lambda 表达式(C++11)
字面量
整数字面量
浮点字面量
布尔字面量
字符字面量,包含转义序列
字符串字面量
空指针字面量(C++11)
用户定义字面量(C++11)
运算符
赋值运算符a=b, a+=b, a-=b, a*=b, a/=b, a%=b, a&=b, a|=b, a^=b, a<<=b, a>>=b
自增与自减++a, --a, a++, a--
算术运算符+a, -a, a+b, a-b, a*b, a/b, a%b, ~a, a&b, a|b, a^b, a<<b, a>>b
逻辑运算符a||b, a&&b, !a
比较运算符a==b, a!=b, a<b, a>b, a<=b, a>=b, a<=>b(C++20)
成员访问运算符a[b], *a, &a, a->b, a.b, a->*b, a.*b
其他运算符a(...), a,b, a?b:c
运算符的替代表示
优先级和结合性
折叠表达式(C++17)
new 表达式
delete 表达式
throw 表达式
alignof
sizeof
sizeof...(C++11)
typeid
noexcept(C++11)
运算符重载
类型转换
隐式转换
const_cast
static_cast
reinterpret_cast
dynamic_cast
显式转换 (T)a, T(a)
用户定义转换
 

凡是在语境中使用了某种表达式类型 T1,但语境不接受该类型,而接受另一类型 T2 的时候,会进行隐式转换,具体是:

  • 调用以 T2 为参数声明函数时,以该表达式为参数;
  • 运算符期待 T2 ,而以该表达式为运算数;
  • 初始化 T2 类型新对象,包括在返回 T2 的函数中的 return 语句;
  • 将表达式用于 switch 语句( T2 是整数类型);
  • 将表达式用于 if 语句或循环( T2bool )。

程序为良式(能编译),仅若存在一个从 T1T2 的无歧义隐式转换序列

若有多个函数或运算符的重载会被调用,则在从 T1 到每个可用的 T2 构造隐式转化序列后,重载决议规则决定编译哪个重载。

注意:算术表达式中,给二元运算符上的运算数上的隐式转换目标类型由一组有别的规则,通常算术转换决定。

目录

[编辑] 转换顺序

隐式转换序列由下列内容构成,以此顺序:

1) 零或一个标准转换序列
2) 零或一个用户定义转换
3) 零或一个标准转换序列

考虑给构造函数或用户定义转换函数的参数时,只允许一个标准转换序列(否则能等效地连锁用户定义序列)。从一个内建类型转换到另一内建类型时,只允许一个标准转换序列。

标准转换序列由下列内容构成,以此顺序:

1) 零或一个左值变换
2) 零或一个数值提升数值转换
3) 零或一个函数指针转换
(C++17 起)
4) 零或一个限定调整

用户定义转换由零或一个非 explicit 单实参构造函数或非 explicit 转换函数调用构成。

若且唯若 T2 能从表达式 e 复制初始化,即对于某虚设的临时对象 t ,声明 T2 t = e; 为良式(能编译),才说表达式 e 可隐式转换为 T2 。注意这有别于直接初始化T2 t(e) ),其中会额外考虑 explicit 构造函数和转换函数。

[编辑] 按语境转换

下列五种语境期待 bool 类型,且若 bool t(e); 为良式则构建隐式转换序列。即是说,考虑用户定义的显式转换函数,如 explicit T::operator bool() const; 。称这种表达式 e可按语境转换为 bool

  • ifwhilefor 的控制条件;
  • 逻辑运算符 !&&||
  • 条件运算符 ?:
  • static_assert
  • noexcept
(C++11 起)

下列语境中,期待语境限定类型 T ,而类类型 E 表达式 e 仅若E 拥有单个非 explicit 用户定义转换函数以转换到可允许类型 (C++14 前)可允许类型中恰好有一个类型 T ,满足 E 拥有非 explicit 转换函数,其返回类型为(可有 cv 限定的) T 或到(可有 cv 限定的) T 的引用,且 e 可隐式转换为 T (C++14 起)才得到允许。我们说这种表达式 e 可按语境隐式转换到指定的类型 T注意不考虑 explicit 转换函数,即使在按语境转换到 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) { } // 始终 OK (隐式转换)
}

[编辑] 值变换

值变换是更改表达式值类别的转换。凡是表达式作为期待不同值类别表达式的运算符的运算数时,之变换发生。

[编辑] 左值到右值转换

任何非函数、非数组类型 T泛左值能隐式转换为同类型的纯右值。若 T 为非类类型,则此转换亦移除 cv 限定符。若泛左值拥有 std::nullptr_t 类型,则作为结果的纯右值是空指针常量 nullptr

除非遇到不求值语境( sizeof 、 typeid 、 noexcept 或 decltype 的运算数中),此转换等效地以原泛左值为构造函数参数,复制构造 T 临时对象,然后将该临时对象作为纯右值返回。

此转换模仿从内存位置读取值到 CPU 寄存器中的行动。

若泛左值所指代的对象含有不确定值(例如由默认初始化非类类型静态变量获得),则行为未定义

除非不确定值拥有可为 cv 限定的无符号字符类型,且不缓存于 CPU 寄存器,或者正式而言:

  • 存储期是静态或线程局域;
  • 或已构造指向它的指针;
  • 或已将它绑定到引用。

若泛左值含有为 delete 所非法化的指针值,则行为亦为实现定义(而非未定义)。

(C++11 起)

[编辑] 数组到指针转换

NT 的数组”或“ T 的未知边界数组”类型的左值右值能隐式转换为“指向 T 的指针”类型的纯右值若数组是纯右值,则发生临时量实质化 (C++17 起)产生的指针指向数组首元素(细节参阅数组到指针退化)。

临时量实质化

任何完整类型 T纯右值能转换为同类型 T 的亡值。此转换从纯右值初始化 T 类型临时对象,通过以临时对象为结果对象求值该纯右值。

T 是类类型或类类型数组,则它必须有可访问且非被删除的析构函数

struct X { int n; };
int k = X().n; // C++17 开始成员访问期待泛左值;
               // X() 纯右值被转换为亡值

临时量实质化在下例情形出现:

(C++17 起)

[编辑] 函数到指针

函数类型 T左值能隐式转换成指向该函数的指针纯右值。这不应用于非静态成员函数,因为不存在指代非静态成员函数的左值。

[编辑] 数值提升

[编辑] 整数类型提升

小整数类型(如 char )的纯右值能转换为较大整数类型(如 int )的纯右值。具体而言,算术运算符不接受小于 int 的类型为参数,而在左值到右值转换后自动应用整数提升,若它可应用。此转换始终保持原值。

以下类型转换被分类为是整数类型提升:

  • signed charsigned short 能转换为 int
  • unsigned charunsigned short 能转换为 int ,若 int 能保有其整个值范围,否则能转换为 unsigned int
  • char 能转换为 intunsigned int ,取决于底层类型: signed charunsigned char (见上述);
  • wchar_tchar16_tchar32_t 能转换为以下列表中能保有其整个值范围的首个类型: intunsigned intlongunsigned longlong longunsigned long long
  • 底层类型不固定的无作用域枚举类型能转换为以下列表中能保有其整个值范围的首个类型: intunsigned intlongunsigned long long longunsigned long long 、扩展整数类型(以大小顺序,有符号优先于无符号) (C++11 起)。若值范围更大,则不应用整数类型提升;
  • 底层类型固定的无作用域枚举类型能转换为其底层类型,而若底层类型亦可进行整数类型提升,则还有提升后的底层类型。到未提升的底层类型的转换对于重载决议的目的更好;
(C++11 起)
  • 位域类型能转换为 int ,若 int 能表示位域的整个值范围,否则能转换为 unsigned int ,若 unsigned int 能表示位域的整个值范围,否则不应用整数类型提升;
  • bool 类型能转换为 int ,值 false 变为 0true 变为 1

注意所有其他转换都不是提升;例如重载决议选择 char -> int (提升)优先于 char -> short (转换)。

[编辑] 浮点类型提升

float 类型纯右值能转换为 double 类型值。值不更改。

[编辑] 数值转换

不同于提升,数值转换可以更改值,而且有潜在的精度损失。

[编辑] 整数类型转换

整数类型和无作用域限定的枚举类型的纯右值可转换至任意其他整数类型。如果符合前面整数类型提升的条件,那么就不是整数类型转换。

  • 如果目标类型是无符号型,其结果是与原值 2n
    结果相等的最小无符号值。其中n是用于表示目标类型的位数。
也就是说,取决于目标类型位数更多还是更少,有符号整数会符号扩展[脚注 1]或截断,无符号整数会0-扩展或截断。
  • 如果目标类型是有符号型,当原值可被目标类型表示出来时值不变。否则该值取决于实现。(请与有符号数溢出区分开,它属于未定义行为)。
  • 如果原类型是bool, false转换成0, true转换成目标类型里的某一个值。(如果目标类型是int, 那么是整数类型提升而不是整数类型转换)
  • 如果目标类型是bool, 这属于布尔转换(见下)。

[编辑] 浮点类型转换

浮点类型纯右值可转换成另一浮点类型的纯右值。如果符合前面浮点类型提升的条件,那么就不是浮点类型转换。

  • 当原值可由目标类型精确表示时,值不变。
  • 如果原值介于目标类型的两个有效值之间,转换结果是这两个值之一(究竟是哪一个取决于实现,尽管当支持IEEE算术时会约为最相近的那一个)。
  • 否则,行为未定义。

[编辑] 浮点整数转换

  • 浮点类型纯右值可转换为任一类型整数纯右值。小数部分被截取,也就是舍弃了小数部分。如果该值超出了目标类型范围,行为未定义(哪怕目标类型是无符号型,也不使用模运算)。如果目标类型是bool, 这属于布尔转换(见下)。
  • 整数纯右值和无作用域限定的枚举类型可转换至任一浮点类型。如果该值不能被正确表示,向上还是向下取有效值取决于实现。尽管当支持IEEE算数时会约为最相近值。如果该值超出了目标类型范围,行为未定义。如果原类型是bool, false转换成0, true变成1.

[编辑] 指针转换

  • 空指针常量(见NULL)可转换至任一指针类型,其结果是对应类型的空指针。这种转换(称为空指针转换)允许发生在cv限定的单次转换上,也就是说, 并不认为它是数值转换和限定转换的组合。
  • (可能cv限定的)T类型指针右值可转换为(cv限定情况相同的)void类型指针。转换而成的指针指向原指针指向的内存地址。如果原指针是空指针,那么转换结果是目标类型的空指针。
  • (可能cv限定的)子类指针可转换为它的可访问的, 明确的(cv限定情况相同的)基类指针。转换结果为原指针指向对象的基类子对象。如果原指针是空指针,那么转换结果是目标类型的空指针。

[编辑] 成员指针转换

  • 空指针常量(见NULL)可转换至任一成员指针类型, 其结果是对应类型的空指针。 这种转换(称为空成员指针转换)允许发生在cv限定的单次转换上,也就是说, 并不认为它是数值转换和限定转换的组合。
  • 指向基类B成员T的成员指针纯右值可转换为指向子类D同一成员T的成员指针。如果B不可访问或指代不清,或是D的虚基类或是D的中间虚基类,这种转换为不良的(无法编译)。转换而成的指针可在D内解引用,会操作在D的对应子对象B的对应成员上 如果原指针是空指针,那么转换结果是目标类型的空指针。

[编辑] 布尔转换

整数,浮点数,无作用域限定的枚举类型,指针,成员指针的纯右值能够转换成为bool类型的纯右值。

0(针对整数,浮点数,无作用域限定的枚举类型),空指针,空成员指针能转换为false. 其他所有转换为true

std::nullptr_t类型纯右值,包括nullptr, 能使用直接初始化变为bool其结果为false (C++11 起)

[编辑] 限定转换

  • cv 限定T 类型指针纯右值能够转换为更多 cv 限定的同类型 T 指针。也就是说,能添加 constvolatile 能够添加。
  • X 的 cv 限定的 T 类型成员指针纯右值能够转换为更多 cv限 定的同类型T成员指针。

"更多" cv限定意味着:

  • 非限定类型指针能够转换为const类型指针;
  • 非限定类型指针能够转换为volatile类型指针;
  • 非限定类型指针能够转换为const volatile类型指针;
  • const类型指针能够转换为const volatile类型指针;
  • volatile类型指针能够转换为const volatile类型指针。

对于多重指针,应用以下限制条件: cv1
0
限定的指向cv1
1
限定的指向 ... cv1
n-1
限定的指向cv1
n
限定的T类型多重指针P1可转换为cv2
0
限定的指向cv2
1
限定的指向 ... cv2
n-1
限定的指向cv2
n
限定的T类型多重指针P2,仅当

  • 两个指针的重数n相同;
  • 如果 P1 有const在某一重cv1
    k
    限定中(除开第0重),P2 对应重数也要有constcv2
    k
    中;
  • 如果 P1 有volatile在某一重cv1
    k
    限定中(除开第0重),P2 对应重数也要有volatilecv2
    k
    中;
  • 在某一重k中,P2有比 P1更多的cv限定,那么P2每一重中都要有const(除开第0重)直到k: cv2
    1
    , cv2
    2
    ... cv2
    k
    .
  • 多重成员指针和混合指针同理;
  • 多重指针,包括指向已知长度和未知长度的数组的每一重(cv限定的元素组成的数组,认为其cv限定情况与元素相同);
(C++14 起)
  • 0重指针转换已在指针转换中提及。
char** p = 0;
const char** p1 = p; // error: level 2 more cv-qualified but level 1 is not const
const char* const * p2 = p; // OK: level 2 more cv-qualified and const added at level 1
volatile char * const * p3 = p; // OK: level 2 more cv-qual and const added at level 1
volatile const char* const* p4 = p2; // OK: 2 more cv-qual and const was already at 1
 
double *a[2][3];
double const * const (*ap)[3] = a; // OK since C++14

注意在 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; // 错误:不能转换到指向 noexcept 函数的指针
 
struct S
{
    typedef void (*p)();
    operator p();
};
void (*q)() noexcept = S(); // 错误:不能转换到指向 noexcept 函数的指针
(C++17 起)

[编辑] 安全 bool 问题

在 C++11 引入显式转换函数之前,设计一个能用于布尔语境的类(比如, if(obj) { ... } )会出现一个问题:给出一个自定义转换函数,比如 T::operator bool() const; , 隐式转换序列允许再多一步标准转换序列,也就是 bool 结果会转换成 int ,允许诸如 obj << 1;int i = obj; 的代码。

一个早期的解决方案可参见 std::basic_ios ,它定义 operator!operator void*(C++11 前),使得如 if(std::cin) {...} 的代码能编译,因为 void* 能转换为 bool ,但int n = std::cout; 不能,因为 void* 不可转换至 int 。这仍然允许无意义代码能编译,如 delete std::cout; 。许多 C++11 前的第三方库设计带有更为复杂的解决方案,称作安全 Bool 手法

显式 bool 转换亦能用于解决安全 bool 问题

explicit operator bool() const { ... }
(C++11 起)

[编辑] 脚注

  1. 仅当使用补码表示时才发生,且仅针对定宽整数类型。请注意,目前所有平台上的C++编译器使用的都是补码运算

[编辑] 缺陷报告

下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。

DR 应用于 出版时的行为 正确行为
CWG 616 C++11 任何未初始化的左值对象到右值的转换是未定义行为 允许不定值的 unsigned char
CWG 1423 C++11 std::nullptr_t 可直接或复制构造至 bool 只允许直接构造
CWG 330 C++14 double * const (*p)[3] 转换至 double const * const (*p)[3] 非法 合法

[编辑] 参阅

隐式转换C 文档