名稱空間
變體
操作

結構化繫結宣告 (C++17 起)

來自 cppreference.com
< 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 解包為單獨的物件
(函式模板) [編輯]