Namespaces
Variants

Structured binding declaration (since C++17)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Miscellaneous

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

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

attr  (可选) decl-specifier-seq ref-qualifier  (可选) [ sb-identifier-list ] initializer  ;
attr - 任意数量的 属性 序列
decl-specifier-seq - 以下说明符序列(遵循 简单声明 规则):
(C++26 起)
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  (可选) specifiers A 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 中的标识符数量必须等于 E 的结构化绑定大小。

(C++26 前)

sb-identifier-list 中的标识符数量为 N E 的结构化绑定大小为 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(); // 正确: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 ,而非引入的结构化绑定:

int a = 1, b = 2;
const auto& [x, y] = std::tie(a, b); // x 和 y 的类型为 int&
auto [z, w] = std::tie(a, b);        // z 和 w 的类型仍为 int&
assert(&z == &a);                    // 通过验证

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) 结构化绑定可引入包

关键词

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: 位域的默认成员初始化器
        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 解包为独立对象
(函数模板)