Namespaces
Variants

Explicit type conversion

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

通过显式和隐式转换的组合实现类型间的转换。

目录

语法

( 类型标识 ) 一元表达式 (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) 显式类型转换(强制转换表示法),也称为 C 风格转换
2-7) 显式类型转换(函数表示法),也称为 函数式转型
type-id - 一个 类型标识
unary-expression - 一元表达式(其顶层运算符的 优先级 不高于C风格转换)
simple-type-specifier - 一个 简单类型说明符
expression-list - 逗号分隔的表达式列表(不含未加括号的 逗号表达式
initializer-list - 逗号分隔的 初始化子句 列表
designated-initializer-list - 逗号分隔的 指定初始化子句 列表
identifier - (可能限定的)标识符(包含 模板标识符

说明

1) 当遇到 C 风格强制转换时,编译器会按以下顺序尝试将其解释为下列转换表达式:
a) const_cast < type-id  > ( unary-expression  ) ;
b) static_cast < 类型标识  > ( 一元表达式  ) ,具有扩展功能:指向 派生类 的指针或引用额外允许被转换为指向明确基类的指针或引用(反之亦然),即使该基类是 不可访问的 (即此转换会忽略私有继承说明符)。同样的规则适用于将 成员指针 转换为明确非虚基类的成员指针;
c) 一个 static_cast (带扩展)后接 const_cast
d) reinterpret_cast < 类型标识  > ( 一元表达式  ) ;
e) 一个 reinterpret_cast 后跟 const_cast
满足相应转型运算符要求的首个选择会被选中,即使该选择是病式的(参见示例)。如果使用 static_cast 后接 const_cast 的组合方式,且该转换能以多种方式解释时,该转换是病式的。
此外,C风格类型转换能够转换自、转换至以及在不完整类类型的指针之间进行转换。如果 type-id unary-expression 的类型均为指向不完整类类型的指针,则未指定选择的是 static_cast 还是 reinterpret_cast
2-7) 函数式转换指定一个 类型 简单类型说明符  标识符  (C++11 起) )和一个 初始化器 (剩余部分),它构造目标类型 T 的值,该类型由指定的类型 和初始化器 (C++17 起) 确定:

T 是指定的类型。

(C++17 前)

T 按如下方式确定:

  • 若指定类型是推导类类型的占位符,则 T 是通过 类模板推导 的重载决议所选择函数的返回类型。
(C++23 起)
  • 否则, T 是指定的类型。
(C++17 起)
转换结果的确定规则如下:
  • 若函数式转换的语法为 (2) ,且括号内仅有一个表达式,则该转换等价于对应的C风格转换。
  • 否则,若 T 为(可能带cv限定符的) void ,则结果为 右值 (C++11 前) 纯右值 (C++11 起) 类型的 void 且不执行初始化。
  • 若初始化器不是 ( ) ,则程序非良构。
(C++11 前)
  • 若初始化器在 包展开 (若存在)后不是 ( ) { } ,则程序非良构。
(C++11 起)
  • 否则,若 T 是引用类型,函数式转换的效果等同于用指定初始化器 直接初始化 一个类型为 T 的虚构变量 t ,且结果为初始化后的 t
  • 结果为左值。
(C++11 前)
  • T 是左值引用类型或函数类型的右值引用,则结果为左值。
  • 否则,结果为亡值。
(C++11 起)
  • 否则,结果为 右值 (C++11 前) 纯右值 (C++11 起) 类型的 T 表示临时对象 (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));
}

然而,若存在歧义的声明语句中最外层声明符具有 尾随返回类型 ,则仅当尾随返回类型以 auto 开头时,该语句才会被视作声明语句:

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);  // 对象声明:该语法无歧义
}

然而,若歧义参数声明中最外层的声明符具有 尾随返回类型 ,则仅当该声明以 auto 开头时,才会通过将其视为声明来解决歧义:

typedef struct BB { int C[2]; } *B, C;
void foo()
{
    S a(B()->C);    // 对象声明:B()->C 不能声明参数
    S b(auto()->C); // 函数声明:拥有一个未命名参数,类型为 C(*)()
                    // 由 C() 调整而来
}
(since 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; // 类型标识(格式错误)
}

然而,如果歧义 类型标识 中最外层的 抽象声明符 具有 尾随返回类型 ,则仅当其以 auto 开头时,才会通过将其视为类型标识来解析歧义:

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 当存在多个 static_cast 后接 const_cast 的解释时,
C风格转换格式错误,无论这些转换是否实际使用
仅考虑可能
被使用的
转换方式
CWG 2894 C++98 函数式转换可能创建引用右值 只能创建引用左值

参考文献

  • C++23 标准 (ISO/IEC 14882:2024):
  • 7.6.1.4 显式类型转换(函数式表示法)[expr.type.conv]
  • 7.6.3 显式类型转换(强制转换表示法)[expr.cast]
  • C++20 标准 (ISO/IEC 14882:2020):
  • 7.6.1.4 显式类型转换(函数式表示法)[expr.type.conv]
  • 7.6.3 显式类型转换(强制转换表示法)[expr.cast]
  • C++17 标准 (ISO/IEC 14882:2017):
  • 8.2.3 显式类型转换(函数式表示法)[expr.type.conv]
  • 8.4 显式类型转换(强制转换表示法)[expr.cast]
  • C++14 标准 (ISO/IEC 14882:2014):
  • 5.2.3 显式类型转换(函数式表示法)[expr.type.conv]
  • 5.4 显式类型转换(强制转换表示法)[expr.cast]
  • C++11 标准 (ISO/IEC 14882:2011):
  • 5.2.3 显式类型转换(函数表示法)[expr.type.conv]
  • 5.4 显式类型转换(强制转换表示法)[expr.cast]
  • C++03 标准 (ISO/IEC 14882:2003):
  • 5.2.3 显式类型转换(函数表示法)[expr.type.conv]
  • 5.4 显式类型转换(强制转换表示法)[expr.cast]
  • C++98 标准 (ISO/IEC 14882:1998):
  • 5.2.3 显式类型转换(函数表示法)[expr.type.conv]
  • 5.4 显式类型转换(强制转换表示法)[expr.cast]

参见

const_cast 转换 添加或移除 const 限定符
static_cast 转换 执行基础类型转换
dynamic_cast 转换 执行带检查的多态转换
reinterpret_cast 转换 执行通用底层转换
标准转换 类型间的隐式转换
C 文档 关于 强制转换运算符