命名空间
变体
操作

结构化绑定声明 (C++17 起)

来自 cppreference.cn
< cpp‎ | 语言
 
 
C++ 语言
 
 

将指定的名称绑定到初始化器的子对象或元素。

与引用一样,结构化绑定是现有对象的别名。与引用不同,结构化绑定不必是引用类型。

attr (可选) decl-specifier-seq ref-qualifier (可选) [ sb-identifier-list ] initializer ;
属性 - 任意数量的属性序列
声明说明符序列 - 以下说明符序列(遵循简单声明的规则)
(C++26 起)
ref-qualifier - &&&
sb-identifier-list - 此声明引入的逗号分隔标识符列表,每个标识符后可跟一个属性说明符序列(C++26 起)
initializer - 一个初始化器(见下文)


initializer 可以是以下之一

= 表达式 (1)
{ expression } (2)
( expression ) (3)
表达式 - 任何表达式(除了未加括号的逗号表达式


结构化绑定声明将sb-identifier-list中的所有标识符作为名称引入周围作用域,并将它们绑定到由expression表示的对象的子对象或元素。如此引入的绑定称为*结构化绑定*。

sb-identifier-list中的一个标识符可以前缀一个省略号。这样的标识符引入一个*结构化绑定包*。

标识符必须声明一个模板实体

(C++26 起)

结构化绑定是sb-identifier-list中的一个标识符 ,它不以省略号开头,或者是同一标识符列表中引入的结构化绑定包的元素(C++26 起)

目录

[编辑] 绑定过程

结构化绑定声明首先引入一个唯一命名的变量(此处表示为e)来保存初始化器的值,如下所示

  • 如果expression具有数组类型cv1 A 且没有ref-qualifier,则将e定义为attr (可选) specifiers A e;,其中specifiersdecl-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中的标识符数量必须等于E的结构化绑定大小。

(直到 C++26)

给定sb-identifier-list中的标识符数量为NE的结构化绑定大小为S

  • 如果没有结构化绑定包,N必须等于S
  • 否则,非包元素的数量(即N - 1)必须小于或等于S,且结构化绑定包的元素数量为S - N + 1(可以为零)。
(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”的变量:如果其相应的初始化器是左值,则为左值引用;否则为右值引用。第I个变量的初始化器是

  • e.get<I>(),如果在E作用域中通过类成员访问查找标识符get找到至少一个声明,该声明是一个函数模板,其第一个模板参数是非类型参数
  • 否则,get<I>(e),其中get仅通过实参依赖查找查找,忽略非ADL查找。

在这些初始化表达式中,如果实体e的类型是左值引用(这只发生在ref-qualifier&,或者它是&&且初始化表达式是左值),则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 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
}

[编辑] 初始化顺序

valIsb-identifier-list中第I个结构化绑定命名的对象或引用。

  • e的初始化先于任何valI的初始化。
  • 每个valI的初始化先于任何valJ的初始化,其中I小于J

[编辑] 注意

结构化绑定不能被约束

template<class T>
concept C = true;
 
C auto [x, y] = std::pair{1, 2}; // error: constrained
(C++20 起)

成员get的查找通常忽略可访问性,也忽略非类型模板参数的确切类型。一个私有的template<char*> void get();成员将导致使用成员解释,即使它格式错误。

[前面的声明部分适用于隐藏变量e,而不是引入的结构化绑定

int a = 1, b = 2;
const auto& [x, y] = std::tie(a, b); // x and y are of type int&
auto [z, w] = std::tie(a, b);        // z and w are still of type int&
assert(&z == &a);                    // passes

如果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-qualifierexpression是纯右值,则适用于引用绑定到临时对象(包括生命周期延长)的通常规则。在这些情况下,隐藏变量e是一个绑定到从纯右值表达式实体化的临时变量的引用,从而延长其生命周期。通常,如果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 前)


结构化绑定大小允许为0,只要sb-identifier-list中包含一个且仅一个标识符,该标识符只能引入一个空的结构化绑定包。

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 起)
功能测试宏 标准 特性
__cpp_structured_bindings 201606L (C++17) 结构化绑定
202403L (C++26) 带属性的结构化绑定
202406L (C++26) 作为条件的结构化绑定声明
202411L (C++26) 结构化绑定可以引入一个包

[编辑] 关键词

auto

[编辑] 示例

#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++ 标准。

缺陷报告 应用于 发布时的行为 正确的行为
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中,成员要求是public的 仅要求在声明的上下文中可访问
在声明的上下文中

[编辑] 参考

  • 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)
创建左值引用 tuple 或将 tuple 解包为单独的对象
(函数模板) [编辑]