C++ 参考手册
- C++11
- C++14
- C++17
- C++20
- C++ 编译器支持情况表
- 独立与宿主实现
- C++ 语言
- 变量模板(C++14 起)
- 整数字面量
- 聚合初始化
- 比较运算符
- 默认比较(C++20 起)
- 转义序列
- for 循环
- while 循环
- 用户定义转换
- SFINAE
- 主函数
- ASCII 码表
- 标识符
- 类型
- 内存模型
- 对象
- 基本概念
- 表达式
- 声明
- 初始化
- 函数
- 语句
- 类
- 运算符重载
- 模板
- 异常
- 事务性内存
- 占位符类型说明符 (C++11 起)
- decltype 说明符
- 函数声明
- final 说明符 (C++11 起)
- override 说明符(C++11 起)
- 引用声明
- 移动构造函数
- 移动赋值运算符
- 枚举声明
- constexpr 说明符(C++11 起)
- 列表初始化 (C++11 起)
- 构造函数与成员初始化器列表
- using 声明
- nullptr,指针字面量
- 基础类型
- 类型别名,别名模版 (C++11 起)
- 形参包
- 联合体声明
- 字符串字面量
- 用户定义字面量 (C++11 起)
- 属性说明符序列(C++11 起)
- Lambda 表达式 (C++11 起)
- noexcept 说明符 (C++11 起)
- noexcept 运算符 (C++11 起)
- alignof 运算符(C++11 起)
- alignas 说明符 (C++11 起)
- 存储类说明符
- 基于范围的 for 循环 (C++11 起)
- static_assert 声明
- 隐式转换
- 代用运算符表示
- 自增/自减运算符
- 折叠表达式(C++17 起)
- 类模板实参推导(C++17 起)
- 模板形参与模板实参
- if 语句
- inline 说明符
- 结构化绑定声明 (C++17 起)
- switch 语句
- 字符字面量
- 命名空间
- 求值顺序
- 复制消除
- consteval 说明符 (C++20 起)
- constinit 说明符 (C++20 起)
- 协程 (C++20)
- 模块 (C++20 起)
- 约束与概念 (C++20 起)
- new 表达式
- do-while 循环
- continue 语句
- break 语句
- goto 语句
- return 语句
- 动态异常说明
- throw 表达式
- try 块
- 命名空间别名
- 类声明
- cv(const 与 volatile)类型限定符
- 默认初始化
- 值初始化(C++03 起)
- 零初始化
- 复制初始化
- 直接初始化
- 常量初始化
- 引用初始化
- 值类别
- C++ 运算符优先级
- 布尔字面量
- 浮点字面量
- typedef 说明符
- 显式类型转换
- static_cast 转换
- dynamic_cast 转换
- const_cast 转换
- reinterpret_cast 转换
- delete 表达式
- 构造函数与成员初始化器列表
- this 指针
- 访问说明符
- 友元声明
- virtual 函数说明符
- explicit 说明符
- 静态成员
- 默认构造函数
- 复制构造函数
- 复制赋值运算符
- 析构函数
- 类模板
- 函数模板
- 显式(全)模板特化
- 汇编声明
- C++ 的历史
- 作用域
- 生存期
- 定义与单一定义规则(ODR)
- 名字查找
- 有限定的名字查找
- 无限定的名字查找
- 如同规则
- 未定义行为
- 翻译阶段
- 常量表达式
- 赋值运算符
- 算术运算符
- 逻辑运算符
- 成员访问运算符
- 其他运算符
- sizeof 运算符
- typeid 运算符
- 指针声明
- 数组声明
- 语言链接
- 详述类型说明符
- 默认实参
- 变长实参
- 实参依赖查找
- 重载决议
- 重载函数的地址
- 注入类名
- 非静态数据成员
- 非静态成员函数
- 嵌套类
- 派生类
- 空基类优化
- 抽象类
- 位域
- 转换构造函数
- 成员模板
- 模板实参推导
- 部分模板特化
- sizeof... 运算符
- 待决名
- 函数 try 块
- 扩充命名空间 std
- 字母缩写
- RAII
- 三/五/零之法则
- PImpl
- 零开销原则
- 类型
- 隐式转换
- 注释
- C++ 关键词
- 预处理器
- C++ 标准库头文件
- 具名要求
- 功能特性测试 (C++20)
- 工具库
- 类型支持(基本类型、RTTI、类型特性)
- 概念库 (C++20)
- 错误处理
- 动态内存管理
- 日期和时间工具
- 字符串库
- 容器库
- 迭代器库
- 范围库 (C++20)
- 算法库
- 数值库
- 输入/输出库
- 文件系统库
- 本地化库
- 正则表达式库
- 原子操作库
- 线程支持库
- 实验性 C++ 特性
- 有用的资源
- 索引
- std 符号索引
- 协程支持 (C++20)
- C++ 关键词
结构化绑定声明 (C++17 起)
绑定指定名称到初始化器的子对象或元素。
类似引用,结构化绑定是既存对象的别名。不同于引用的是,结构化绑定的类型不必为引用类型。
attr(可选) cv-auto ref-运算符(可选) [ 标识符列表 ] = 表达式 ;
|
(1) | ||||||||
attr(可选) cv-auto ref-运算符(可选) [ 标识符列表 ] { 表达式 } ;
|
(2) | ||||||||
attr(可选) cv-auto ref-运算符(可选) [ 标识符列表 ] ( 表达式 ) ;
|
(3) | ||||||||
attr | - | 任意数量的属性的序列 |
cv-auto | - | 可有 cv 限定的 auto 类型说明符,亦可包含存储类说明符 static 或 thread_local ;在 cv 限定符中包含 volatile 是被弃用的 (C++20 起)
|
ref-运算符 | - | & 或 && 之一
|
标识符列表 | - | 此声明所引入的各标识符的逗号分隔的列表 |
表达式 | - | 顶层没有逗号运算符的表达式(文法上为赋值表达式),且具有数组或非联合类之一的类型。若 表达式 涉及任何来自 标识符列表 的名字,则声明非良构。 |
结构化绑定声明将 标识符列表 中的所有标识符,引入作为其外围作用域中的名字,并将它们绑定到 表达式 所指代的对象的各个子对象或元素。以此方式引入的绑定被称作结构化绑定。
结构化绑定声明首先引入一个唯一命名的变量(此处以 e
指代)来保有其初始化器的值,方式如下:
- 若 表达式 具有数组类型
A
且不存在 ref-运算符,则e
具有类型 cvA
,其中 cv 是 cv-auto 序列中的 cv 限定符,且e
的各个元素从 表达式 的对应元素进行复制(对于 (1))或直接(对于 (2,3))初始化。 - 否则
e
如同于声明中以其名取代[
标识符列表]
一般定义。
我们用 E
代表表达式 e 的类型。(换言之,E
等价于 std::remove_reference_t<decltype((e))>)。
然后,结构化绑定以三种可能方式之一进行绑定,取决于 E
:
- 情况 1:若
E
是数组类型,则绑定各个名字到各个数组元素。 - 情况 2:若
E
是非联合类类型且 std::tuple_size<E> 是完整类型,则使用“元组式”绑定协议。 - 情况 3:若
E
是非联合类类型但 std::tuple_size<E> 不是完整类型,则绑定各个名字到E
的各个可访问数据成员。
三种情况的每一种,下文中都有更详细的描述。
每个结构化绑定都有一个被引用类型,在后文的描述中定义。此类型是对无括号的结构化绑定应用 decltype
所返回的类型。
情况 1:绑定数组
标识符列表 中的每个标识符均成为指代数组的对应元素的左值。标识符的数量必须等于数组的元素数量。
每个标识符的被引用类型都是数组的元素类型。注意若数组类型 E
为 cv 限定的,则其元素亦然。
int a[2] = {1,2}; auto [x,y] = a; // 创建 e[2],复制 a 到 e,然后 x 指代 e[0],y 指代 e[1] auto& [xr, yr] = a; // xr 指代 a[0],yr 指代 a[1]
情况 2:绑定元组式类型
表达式 std::tuple_size<E>::value 必须是良构的整数常量表达式,且标识符的数量必须等于 std::tuple_size<E>::value。
对于每个标识符,引入一个类型为“std::tuple_element<i, E>::type 的引用”的变量:若其对应初始化器是左值,则为左值引用,否则为右值引用。第 i 个变量的初始化器
- 若在
E
的作用域中对标识符get
按类成员访问进行的查找中,至少找到一个声明是首个模板形参为非类型形参的函数模板,则为 e.get<i>() - 否则为 get<i>(e),其中 get 只进行实参依赖查找,忽略非 ADL 的查找。
这些初始化器表达式中,若实体 e
的类型为左值引用(这仅在 ref-运算符 为 &
,或为 &&
且初始化器为左值时才发生),则 e 为左值,否则为亡值(这实际上进行了一种完美转发),i 是 std::size_t 的纯右值,而且始终将 <i> 解释为模板形参列表。
变量拥有与 e
相同的存储期。
然后该标识符变成指代与上述变量绑定的对象的左值。
第 i 个标识符的被引用类型为 std::tuple_element<i, E>::type。
float x{}; char y{}; int z{}; std::tuple<float&,char&&,int> tpl(x,std::move(y),z); const auto& [a,b,c] = tpl; // a 指名指代 x 的结构化绑定;decltype(a) 为 float& // b 指名指代 y 的结构化绑定;decltype(b) 为 char&& // c 指名指代 tpl 的第 3 元素的结构化绑定;decltype(c) 为 const int
情况 3:绑定到数据成员
E
的所有非静态数据成员必须都是 E
或 E
的同一基类的直接成员,必须在指名为 e.name
时于结构化绑定的语境中是良构的。E
不能有匿名联合体成员。标识符的数量必须等于非静态数据成员的数量。
标识符列表 中的各个标识符,按声明顺序依次成为指代 e
的各个成员的左值的名字(支持位域);左值的类型是 cv T_i
,其中 cv
是 E
的 cv 限定符且 T_i
是第 i 个成员的声明类型。
第 i 个标识符的被引用类型是 cv T_i
。
struct S { int x1 : 2; volatile double y1; }; S f(); const auto [x, y] = f(); // x 是标识 2 位位域的 const int 左值 // y 是 const volatile double 左值
注解
对成员 get
的查找照常忽略可访问性,亦忽略非类型模板形参的确切类型。出现私有的 template<char*> void get(); 成员将导致使用成员解释方案,即便非良构也是如此。
声明中 [
之前的部分应用于隐藏变量 e
,而非引入的各个标识符。
若 std::tuple_size<E> 是完整类型,则始终使用元组式解释方案,即使导致程序非良构也是如此:
struct A { int x; }; namespace std { template<> struct tuple_size<::A> {}; } auto [x] = A{}; // 错误;不考虑“数据成员”解释方案。
若存在 ref-运算符 且 表达式 为纯右值,则应用将引用绑定到临时量的通常规则(包括生存期延续)。这些情况下,隐藏变量 e
是绑定到从纯右值表达式实质化的临时变量,并延长其生存期的一个引用。如往常情况,若 e
是非 const 左值引用,则绑定失败:
int a = 1; const auto& [x] = std::make_tuple(a); // OK,非悬垂引用 auto& [y] = std::make_tuple(a); // 错误,不能绑定 auto& 到右值 std::tuple auto&& [z] = std::make_tuple(a); // 亦 OK
decltype(x) 指名结构化绑定的被引用类型,其中 x
代表一个结构化绑定。在元组式的情况下,它是 std::tuple_element 所返回的类型,它可能不是引用,即使在此情况下结构化绑定自身实际上始终表现为类似引用。这相当于模拟了绑定到其各个非静态数据成员具有 tuple_element
所返回的类型的结构体的行为,而绑定自身的引用性质则仅是实现细节。
std::tuple<int, int&> f(); auto [x, y] = f(); // decltype(x) 为 int // decltype(y) 为 int& const auto [z, w] = f(); // decltype(z) 为 const int // decltype(w) 为 int&
示例
#include <set> #include <string> #include <iomanip> #include <iostream> int main() { std::set<std::string> myset; if (auto [iter, success] = myset.insert("Hello"); success) std::cout << "insert is successful. The value is " << std::quoted(*iter) << '\n'; else std::cout << "The value " << std::quoted(*iter) << " already exists in the set\n"; }
输出:
insert is successful. The value is "Hello"
缺陷报告
下列更改行为的缺陷报告追溯地应用于以前出版的 C++ 标准。
DR | 应用于 | 出版时的行为 | 正确行为 |
---|---|---|---|
P0961R1 | C++17 | 元组式情况中,若查找找到任何类型的 get 则使用成员 get
|
仅若查找找到拥有非类型模板形参的函数模板才使用 |
P0969R0 | C++17 | 绑定到成员情况中,要求成员为公开 | 仅要求在声明的语境中可访问 |
参阅
创建左值引用的 tuple ,或将 tuple 解包为独立对象 (函数模板) |