命名空间
变体
操作

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

来自 cppreference.com
< cpp‎ | 语言
 
 
C++ 语言
一般主题
流程控制
条件执行语句
if
迭代语句(循环)
for
range-for (C++11)
跳转语句
函数
函数声明
Lambda 函数表达式
inline 说明符
动态异常说明 (直到 C++17*)
noexcept 说明符 (C++11)
异常
命名空间
类型
说明符
const/volatile
decltype (C++11)
auto (C++11)
constexpr (C++11)
consteval (C++20)
constinit (C++20)
存储持续时间说明符
初始化
 
 

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

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

attr (可选) cv-auto ref-qualifier (可选) [ identifier-list ] = expression ; (1)
attr (可选) cv-auto ref-qualifier (可选) [ identifier-list ]{ expression }; (2)
attr (可选) cv-auto ref-qualifier (可选) [ identifier-list ]( expression ); (3)
attr - 任何数量的 属性 序列
cv-auto - 可能具有 cv 限定的类型说明符 auto,也可能包括 存储类说明符 staticthread_local;在 cv 限定符中包括 volatile 已被弃用(自 C++20 起)
ref-qualifier - &&&
identifier-list - 由该声明引入的逗号分隔标识符列表,每个标识符后面都可以跟 属性说明符序列(自 C++26 起)
expression - 一个在顶层不包含逗号运算符的表达式(语法上是 赋值表达式),并且具有数组或非联合类类型。如果 expression 引用 identifier-list 中的任何名称,则声明格式错误。

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

内容

[编辑] 绑定过程

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

  • 如果 表达式 的类型为数组 A 且没有 引用限定符,那么 e 的类型为 cv A,其中 cvcv-自动 序列中的 cv 限定符,并且 e 的每个元素都通过 复制初始化(对于 (1))或 直接初始化(对于 (2,3))从 表达式 的对应元素初始化。
  • 否则,e 的定义方式与在声明中使用其名称而不是 [ 标识符列表 ] 相同。

我们使用 E 来表示 e 的类型。(换句话说,E 等效于 std::remove_reference_t<decltype((e))>。)

然后,结构化绑定声明根据 E 以三种可能的方式之一执行绑定。

  • 情况 1:如果 E 是数组类型,则名称将绑定到数组元素。
  • 情况 2:如果 E 是非联合类类型,并且 std::tuple_size<E> 是一个完整类型,并且具有名为 value 的成员(无论该成员的类型或可访问性如何),则使用“类似元组”的绑定协议。
  • 情况 3:如果 E 是非联合类类型,但 std::tuple_size<E> 不是完整类型,则名称将绑定到 E 的可访问数据成员。

下面将详细介绍这三种情况。

每个结构化绑定都有一个引用类型,在下面的描述中定义。此类型是将 decltype 应用于未加括号的结构化绑定时返回的类型。

[edit] 情况 1:绑定数组

标识符列表 中的每个标识符都成为引用数组对应元素的左值的名称。标识符的数量必须等于数组元素的数量。

每个标识符的引用类型是数组元素类型。请注意,如果数组类型 E 是 cv 限定的,那么其元素类型也是 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]

[edit] 情况 2:绑定实现元组操作的类型

表达式 std::tuple_size<E>::value 必须是一个格式良好的整型常量表达式,并且标识符的数量必须等于 std::tuple_size<E>::value

对于每个标识符,都将引入一个类型为“对 std::tuple_element<i, E>::type" 的引用的变量:如果其对应的初始化表达式是左值,则为左值引用;否则为右值引用。第 i 个变量的初始化表达式为

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

在这些初始化表达式中,e 是一个左值,如果实体 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

[edit] 情况 3:绑定到数据成员

E 的每个非静态数据成员必须是 EE 的相同基类的直接成员,并且在结构化绑定中命名为 e.name 时必须格式良好。E 不能具有匿名联合成员。标识符的数量必须等于非静态数据成员的数量。

标识符列表 中的每个标识符都成为引用 e 中下一个成员的左值的名称,按声明顺序(支持位域);左值的类型是 e.m_i 的类型,其中 m_i 指的是第 i 个成员。

第 i 个标识符的引用类型e.m_i 的类型(如果它不是引用类型),否则是 m_i 的声明类型。

#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
}

[edit] 初始化顺序

val_i标识符列表 中第 i 个标识符命名的对象或引用。

  • e 的初始化在任何 val_i 的初始化之前 排序
  • 每个 val_i 的初始化在任何 val_j 的初始化之前排序,其中 i 小于 j

[edit] 说明

结构化绑定不能 受约束

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> 是一个完整类型,则始终使用类似元组的解释,即使这会导致程序非法。

struct A { int x; };
 
namespace std
{
    template<>
    struct tuple_size<::A> { void value(); };
}
 
auto [x] = A{}; // error; the "data member" interpretation is not considered.

如果存在 引用限定符 并且 表达式 是一个右值,则临时变量的引用绑定(包括生命周期扩展)的通常规则适用。在这些情况下,隐藏变量 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)
功能测试宏 Std 功能
__cpp_structured_bindings 201606L (C++17) 结构化绑定
202403L (C++26) 带有属性的结构化绑定

[edit] 关键字

auto

[edit] 示例

#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

[edit] 缺陷报告

以下更改行为的缺陷报告已追溯应用于以前发布的 C++ 标准。

DR 应用于 发布的行为 正确的行为
CWG 2285 C++17 表达式 可以引用 标识符列表 中的名称 在这种情况下,声明是非法的
CWG 2312 C++17 mutable 的含义丢失了
在绑定到成员的情况下
其含义仍然保留
CWG 2386 C++17 使用“类似元组”的绑定协议
只要 std::tuple_size<E> 是一个完整类型
仅当 std::tuple_size<E> 有一个名为 value 的成员时使用。
有一个名为 value 的成员。
CWG 2635 C++20 结构化绑定可以被限制 禁止
CWG 2867 C++17 初始化顺序不明确 已明确
P0961R1 C++17 在类元组的情况下,成员 get
如果查找找到任何类型的 get,则使用
仅当查找找到函数
带有非类型参数的模板
P0969R0 C++17 在绑定到成员的情况下,
成员必须是公共的
仅需要在声明的上下文中可访问
在声明的上下文中

[编辑] 参考文献

  • 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)
创建一个 元组 的左值引用,或将元组解包到单个对象中
(函数模板) [编辑]