结构化绑定声明 (自 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 可以是以下之一
= expression |
(1) | ||||||||
{ expression } |
(2) | ||||||||
( expression ) |
(3) | ||||||||
expression | - | 任何表达式(除了未加括号的逗号表达式) |
结构化绑定声明在周围作用域中引入 sb-identifier-list 中的所有标识符作为名称,并将它们绑定到由 expression 表示的对象的子对象或元素。如此引入的绑定称为结构化绑定。
sb-identifier-list 中的一个标识符可以以省略号开头。这样的标识符引入一个结构化绑定包。 该标识符必须声明一个模板实体。 |
(自 C++26 起) |
结构化绑定是 sb-identifier-list 中未以省略号开头的标识符,或在同一标识符列表中引入的结构化绑定包的元素(自 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 (可选)
e
initializer ;
。
我们使用 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(); // OK: a, b, c refer to x, y, z, respectively auto [d, ...e] = C(); // OK: d refers to x; ...e refers to y and z auto [...f, g] = C(); // OK: ...f refers x and y; g refers to z auto [h, i, j, ...k] = C(); // OK: the pack k is empty auto [l, m, n, o, ...p] = C(); // error: structured binding size is too small }
结构化绑定声明执行以下三种可能的绑定方式之一,具体取决于 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; // creates e[2], copies a into e, // then x refers to e[0], y refers to e[1] auto& [xr, yr] = a; // xr refers to a[0], yr refers to a[1]
[编辑] 情况 2:绑定实现元组操作的类型
表达式 std::tuple_size<E>::value 必须是一个形式良好的整型常量表达式,并且 E
的结构化绑定大小等于 std::tuple_size<E>::value。
对于每个结构化绑定,都会引入一个类型为“引用 std::tuple_element<I, E>::type” 的变量:如果其对应的初始化器是左值,则为左值引用,否则为右值引用。Ith 变量的初始化器是
- e.get<I>(),如果在
E
的作用域中通过类成员访问查找标识符get
找到至少一个声明,该声明是第一个模板参数是非类型参数的函数模板 - 否则,get<I>(e),其中 get 仅通过实参依赖查找查找,忽略非 ADL 查找。
在这些初始化器表达式中,如果实体 e 的类型是左值引用(仅当 ref-qualifier 是 &
或它是 &&
且初始化器表达式是左值时才会发生这种情况),则 e 是左值,否则是 xvalue(这有效地执行了一种完美转发),I 是一个 std::size_t prvalue,并且 <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 names a structured binding that refers to x (initialized from get<0>(tpl)) // decltype(a) is std::tuple_element<0, Tpl>::type, i.e. float& // b names a structured binding that refers to y (initialized from get<1>(tpl)) // decltype(b) is std::tuple_element<1, Tpl>::type, i.e. char&& // c names a structured binding that refers to the third component of tpl, get<2>(tpl) // decltype(c) is std::tuple_element<2, Tpl>::type, i.e. 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 is an int lvalue identifying the 2-bit bit-field // y is a const volatile double lvalue std::cout << x << ' ' << y << '\n'; // 1 2.3 x = -2; // OK // y = -2.; // Error: y is const-qualified std::cout << x << ' ' << y << '\n'; // -2 2.3 }
[编辑] 初始化顺序
令 valI 为 sb-identifier-list 中第 I 个结构化绑定命名的对象或引用
[编辑] 注意
结构化绑定不能被约束 template<class T> concept C = true; C auto [x, y] = std::pair{1, 2}; // error: constrained |
(自 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{}; // error; the "data member" interpretation is not considered.
如果存在 ref-qualifier 并且 expression 是 prvalue,则引用绑定到临时变量(包括生命周期延长)的常用规则适用。在这些情况下,隐藏变量 e 是绑定到从 prvalue 表达式具体化的临时变量的引用,从而延长其生命周期。像往常一样,如果 e 是非 const 左值引用,则绑定将失败
int a = 1; const auto& [x] = std::make_tuple(a); // OK, not dangling auto& [y] = std::make_tuple(a); // error, cannot bind auto& to rvalue std::tuple auto&& [z] = std::make_tuple(a); // also OK
decltype(x),其中 x 表示结构化绑定,命名该结构化绑定的引用类型。在类似元组的情况下,这是 std::tuple_element 返回的类型,即使在这种情况下始终引入隐藏引用,它也可能不是引用。这有效地模拟了绑定到结构的行为,该结构的非静态数据成员具有 std::tuple_element 返回的类型,绑定本身的引用性只是一个实现细节。
std::tuple<int, int&> f(); auto [x, y] = f(); // decltype(x) is int // decltype(y) is int& const auto [z, w] = f(); // decltype(z) is const int // decltype(w) is 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(); // error auto [...args] = return_empty(); // OK, args is an empty pack auto [one, ...rest] = return_empty(); // error, structured binding size is too small } |
(自 C++26 起) |
特性测试宏 | 值 | Std | 特性 |
---|---|---|---|
__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: default member initializer for bit-fields 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++ 标准。
DR | 应用于 | 已发布行为 | 正确行为 |
---|---|---|---|
CWG 2285 | C++17 | expression 可能引用来自 identifier-list 的名称 | 声明是 在这种情况下形式错误 |
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 | 如果 expression 是 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] (p: 228-229)
- C++20 标准 (ISO/IEC 14882:2020)
- 9.6 结构化绑定声明 [dcl.struct.bind] (p: 219-220)
- C++17 标准 (ISO/IEC 14882:2017)
- 11.5 结构化绑定声明 [dcl.struct.bind] (p: 219-220)
[编辑] 参见
(C++11) |
创建左值引用元组或将元组解包为单个对象 (函数模板) |