Structured binding declaration (since C++17)
将指定的名称绑定到初始化器的子对象或元素。
与引用类似,结构化绑定是现有对象的别名。不同于引用的是,结构化绑定不必是引用类型。
attr
(可选)
decl-specifier-seq
ref-qualifier
(可选)
[
sb-identifier-list
]
initializer
;
|
|||||||||
| attr | - | 任意数量的 属性 序列 | ||
| decl-specifier-seq | - |
以下说明符序列(遵循
简单声明
规则):
|
||
| ref-qualifier | - |
&
或
&&
|
||
| sb-identifier-list | - | 此声明引入的逗号分隔标识符列表 ,每个标识符后可跟随一个 属性说明符序列 (C++26 起) | ||
| initializer | - | 初始化器(见下文) |
initializer
可以是以下之一:
=
表达式
|
(1) | ||||||||
{
表达式
}
|
(2) | ||||||||
(
表达式
)
|
(3) | ||||||||
| expression | - | 任意表达式(不包括未加括号的 逗号表达式 ) |
结构化绑定声明将
sb-identifier-list
中的所有标识符作为名称引入外围作用域,并将它们绑定到
expression
所指代对象的子对象或元素。如此引入的绑定称为
结构化绑定
。
|
sb-identifier-list 中的某个标识符前可以放置省略号。这样的标识符将引入一个 结构化绑定包 。 该标识符必须声明一个 模板化实体 。 |
(since C++26) |
结构化绑定是 sb-identifier-list 中不带省略号的标识符,或是在同一标识符列表中引入的结构化绑定包的元素 (since C++26) 。
目录 |
绑定流程
结构化绑定声明首先引入一个唯一命名的变量(此处用 e 表示)来保存初始化表达式的值,具体方式如下:
-
如果
expression
具有数组类型
cv1
A且不存在 ref-qualifier ,则将 e 定义为 attr (可选) specifiersA e;,其中 specifiers 是 decl-specifier-seq 中除 auto 外的说明符序列。
- 随后, e 的每个元素将根据 initializer 的形式从 expression 的对应元素进行初始化:
-
否则,将
e
定义为
attr
(可选)
decl-specifier-seq
ref-qualifier
(可选)
einitializer ;。
我们使用
E
表示标识符表达式
e
的类型(即
E
相当于
std::
remove_reference_t
<
decltype
(
(
e
)
)
>
)。
E
的
结构化绑定数量
是指需要通过结构化绑定声明引入的结构化绑定数量。
|
sb-identifier-list
中的标识符数量必须等于
|
(C++26 前) |
|
设
sb-identifier-list
中的标识符数量为
N
,
|
(C++26 起) |
struct C { int x, y, z; }; template<class T> void now_i_know_my() { auto [a, b, c] = C(); // 正确:a、b、c 分别引用 x、y、z auto [d, ...e] = C(); // 正确:d 引用 x;...e 引用 y 和 z auto [...f, g] = C(); // 正确:...f 引用 x 和 y;g 引用 z auto [h, i, j, ...k] = C(); // 正确:包 k 为空 auto [l, m, n, o, ...p] = C(); // 错误:结构化绑定数量过少 }
结构化绑定声明根据
E
的类型采用以下三种可能方式之一执行绑定:
-
情况1:若
E为数组类型,则名称将绑定至数组元素。 -
情况2:若
E为非联合体类类型,且 std:: tuple_size < E > 是包含名为value成员的完整类型(无论该成员的类型或可访问性如何),则使用“类元组”绑定协议。 -
情况3:若
E为非联合体类类型,但 std:: tuple_size < E > 不是完整类型,则名称将绑定至E的可访问数据成员。
以下将更详细地描述这三种情况。
每个结构化绑定都有一个
引用类型
,其定义如下所述。该类型是当
decltype
应用于未加括号的结构化绑定时所返回的类型。
情形 1:绑定数组
每个结构化绑定在
sb-identifier-list
中成为引用数组对应元素的左值名称。结构化绑定的大小
E
等于数组元素的数量。
每个结构化绑定的
引用类型
是数组元素类型。请注意,如果数组类型
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
必须是良构的
整型常量表达式
,且
E
的结构化绑定大小等于
std::
tuple_size
<
E
>
::
value
。
对于每个结构化绑定,会引入一个类型为“引用到 std:: tuple_element < I, E > :: type ”的变量:若其对应初始化器为左值则为左值引用,否则为右值引用。第 I 个变量的初始化器是
-
e.
get
<
I
>
(
)
,若通过类成员访问查找在
E作用域中对标识符get的查找找到至少一个声明为函数模板的声明,且该模板的第一个模板参数是常量参数 - 否则, get < I > ( e ) ,其中 get 仅通过 实参依赖查找 进行查找,忽略非ADL查找。
在这些初始化表达式中,
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; // using Tpl = const std::tuple<float&, char&&, int>; // a 是引用 x 的结构化绑定(从 get<0>(tpl) 初始化) // decltype(a) 是 std::tuple_element<0, Tpl>::type,即 float& // b 是引用 y 的结构化绑定(从 get<1>(tpl) 初始化) // decltype(b) 是 std::tuple_element<1, Tpl>::type,即 char&& // c 是引用 tpl 第三个组件的结构化绑定,即 get<2>(tpl) // decltype(c) 是 std::tuple_element<2, Tpl>::type,即 const int
情形 3:绑定到数据成员
每个
E
的非静态数据成员必须是
E
的直接成员或
E
的同一基类成员,且在结构化绑定的上下文中以
e.
name
形式命名时必须格式正确。
E
不得包含匿名联合成员。
E
的结构化绑定大小等于其非静态数据成员的数量。
每个结构化绑定中的
sb-identifier-list
标识符都会成为左值的名称,该左值按声明顺序引用
e
的下一个成员(支持位域);该左值的类型与
e.
mI
的类型相同,其中
mI
指代第
I
个成员。
第
I
个结构化绑定的
引用类型
是:若
e.
mI
不是引用类型,则为其类型;否则为
mI
的声明类型。
#include <iostream> struct S { mutable int x1 : 2; volatile double y1; }; S f() { return S{1, 2.3}; } int main() { const auto [x, y] = f(); // x 是标识 2 位位域的 int 左值 // y 是 const volatile double 左值 std::cout << x << ' ' << y << '\n'; // 1 2.3 x = -2; // 正确 // y = -2.; // 错误:y 具有 const 限定 std::cout << x << ' ' << y << '\n'; // -2 2.3 }
初始化顺序
令 valI 为由 sb-identifier-list 中第 I 个结构化绑定命名的对象或引用:
- e 的初始化 先序于 任何 valI 的初始化。
- 每个 valI 的初始化先序于任何 valJ 的初始化,其中 I 小于 J 。
注释
|
结构化绑定不能进行 约束 : template<class T> concept C = true; C auto [x, y] = std::pair{1, 2}; // 错误:被约束 |
(自 C++20 起) |
对成员
get
的查找会像通常情况一样忽略可访问性,同时也会忽略常量模板参数的确切类型。即使存在形式错误的私有
template
<
char
*
>
void
get
(
)
;
成员,仍将采用成员解释方案。
声明中位于
[
之前的部分适用于隐藏变量
e
,而非引入的结构化绑定:
当
std::
tuple_size
<
E
>
是一个包含名为
value
成员的完整类型时,始终采用类元组解释方式,即使这会导致程序处于非良构状态:
struct A { int x; }; namespace std { template<> struct tuple_size<::A> { void value(); }; } auto [x] = A{}; // 错误;不会采用“数据成员”的解析方式。
当存在 引用限定符 且 表达式 为纯右值时,适用于临时对象的常规引用绑定规则(包括生存期延长)仍然有效。在这种情况下,隐藏变量 e 是一个引用,它会绑定到从纯右值表达式 物化 产生的临时变量,并延长其生存期。按照常规规则,如果 e 是非常量左值引用,则绑定将失败:
int a = 1; const auto& [x] = std::make_tuple(a); // 正确,非悬空引用 auto& [y] = std::make_tuple(a); // 错误,无法将 auto& 绑定到右值 std::tuple auto&& [z] = std::make_tuple(a); // 同样正确
decltype ( x ) ,其中 x 表示结构化绑定,将命名该结构化绑定的 引用类型 。在类元组情况下,该类型由 std::tuple_element 返回,即使此情况下始终会隐式引入引用,该返回类型也可能不是引用类型。这实际上模拟了绑定到结构体的行为——该结构体的非静态数据成员具有 std::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&
|
结构化绑定无法被 lambda表达式 捕获: #include <cassert> int main() { struct S { int p{6}, q{7}; }; const auto& [b, d] = S{}; auto l = [b, d] { return b * d; }; // valid since C++20 assert(l() == 42); } |
(C++20前) |
|
当 sb-identifier-list 仅包含一个可引入空结构化绑定包的标识符时,允许结构化绑定的大小为 0 。 auto return_empty() -> std::tuple<>; template <class> void test_empty() { auto [] = return_empty(); // 错误 auto [...args] = return_empty(); // 正确,args是空包 auto [one, ...rest] = return_empty(); // 错误,结构化绑定大小过小 } |
(since C++26) |
| 功能测试宏 | 值 | 标准 | 功能 |
|---|---|---|---|
__cpp_structured_bindings
|
201606L
|
(C++17) | 结构化绑定 |
202403L
|
(C++26) | 带属性的结构化绑定 | |
202406L
|
(C++26) | 作为条件的结构化绑定声明 | |
202411L
|
(C++26) | 结构化绑定可引入包 |
关键词
示例
#include <iomanip> #include <iostream> #include <set> #include <string> int main() { std::set<std::string> myset{"hello"}; for (int i{2}; i; --i) { 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"; } struct BitFields { // C++20: 位域的默认成员初始化器 int b : 4 {1}, d : 4 {2}, p : 4 {3}, q : 4 {4}; }; { const auto [b, d, p, q] = BitFields{}; std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n'; } { const auto [b, d, p, q] = []{ return BitFields{4, 3, 2, 1}; }(); std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n'; } { BitFields s; auto& [b, d, p, q] = s; std::cout << b << ' ' << d << ' ' << p << ' ' << q << '\n'; b = 4, d = 3, p = 2, q = 1; std::cout << s.b << ' ' << s.d << ' ' << s.p << ' ' << s.q << '\n'; } }
输出:
Insert is successful. The value is "Hello". The value "Hello" already exists in the set. 1 2 3 4 4 3 2 1 1 2 3 4 4 3 2 1
缺陷报告
下列行为变更缺陷报告被追溯应用于先前发布的 C++ 标准。
| 缺陷报告 | 应用于 | 发布时的行为 | 正确行为 |
|---|---|---|---|
| CWG 2285 | C++17 | 表达式 可能引用 标识符列表 中的名称 |
此情况下声明
格式错误 |
| CWG 2312 | C++17 | 情况3中 mutable 的含义丢失 | 其含义仍被保留 |
| CWG 2313 | C++17 | 情况2中,结构化绑定变量可能被重复声明 | 不可重复声明 |
| CWG 2339 | C++17 | 情况2中缺少 I 的定义 | 添加了定义 |
|
CWG 2341
( P1091R3 ) |
C++17 |
结构化绑定不能
声明为静态存储期 |
允许 |
| CWG 2386 | C++17 |
只要
std::
tuple_size
<
E
>
是完整类型
就使用“类元组”绑定协议 |
仅当
std::
tuple_size
<
E
>
具有成员
value
时使用
|
| CWG 2506 | C++17 |
若
表达式
是cv限定数组类型,
cv限定会被传递给
E
|
丢弃该cv限定 |
| CWG 2635 | C++20 | 结构化绑定可被约束 | 禁止 |
| CWG 2867 | C++17 | 初始化顺序不明确 | 予以明确 |
| P0961R1 | C++17 |
情况2中,只要查找找到任何类型的
get
就会使用成员
get
|
仅当查找找到具有常量参数的
函数模板时使用 |
| P0969R0 | C++17 | 情况3中要求成员必须为公开 |
仅要求在声明上下文中
可访问 |
参考文献
- C++23 标准 (ISO/IEC 14882:2024):
-
- 9.6 结构化绑定声明 [dcl.struct.bind] (页码: 228-229)
- C++20 标准 (ISO/IEC 14882:2020):
-
- 9.6 结构化绑定声明 [dcl.struct.bind] (页码: 219-220)
- C++17 标准 (ISO/IEC 14882:2017):
-
- 11.5 结构化绑定声明 [dcl.struct.bind] (页码: 219-220)
参见
|
(C++11)
|
创建左值引用的
tuple
或将 tuple 解包为独立对象
(函数模板) |