構造化束縛宣言 (C++17以降)
指定された名前を初期化子のサブオブジェクトまたは要素に束縛します。
参照と同様に、構造化束縛は既存のオブジェクトへのエイリアスです。参照とは異なり、構造化束縛は参照型である必要はありません。
attr (省略可能) decl-specifier-seq ref-qualifier (省略可能) [ sb-identifier-list ] initializer ; |
|||||||||
| attr | - | 任意の数の属性のシーケンス | ||
| decl-specifier-seq (宣言指定子シーケンス) | - | 以下の指定子のシーケンス (単純宣言の規則に従います)
| ||
| 参照修飾子 | - | & または && のいずれか | ||
| sb-identifier-list | - | この宣言によって導入されるコンマ区切りの識別子のリスト。各識別子には属性指定子シーケンスが続く場合があります(C++26以降) | ||
| initializer | - | 初期化子 (下記参照) |
initializerは以下のいずれかです
= expression |
(1) | ||||||||
{ expression } |
(2) | ||||||||
( expression ) |
(3) | ||||||||
| 式 | - | 任意の式 (括弧で囲まれていないコンマ式を除く) |
構造化束縛宣言は、sb-identifier-list内のすべての識別子を囲むスコープ内の名前として導入し、それらをexpressionによって示されるオブジェクトのサブオブジェクトまたは要素に束縛します。このように導入された束縛は構造化束縛と呼ばれます。
|
sb-identifier-list内の識別子の1つは、省略記号が先行する場合があります。そのような識別子は構造化束縛パックを導入します。 識別子はテンプレート化されたエンティティを宣言する必要があります。 |
(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 (省略可能)
einitializer ;として定義します。
eの識別子式の型 (すなわち、Eはstd::remove_reference_t<decltype((e))>と同等) を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に応じて3つの可能な方法のいずれかで束縛を実行します。
- ケース1:
Eが配列型の場合、名前は配列要素に束縛されます。 - ケース2:
Eが非共用体クラス型で、std::tuple_size<E>がvalueという名前のメンバーを持つ完全型である場合 (そのようなメンバーの型やアクセス可能性に関係なく)、"タプルライクな"束縛プロトコルが使用されます。 - ケース3:
Eが非共用体クラス型だが、std::tuple_size<E>が完全型でない場合、名前はEのアクセス可能なデータメンバーに束縛されます。
これら3つのケースは、以下で詳細に説明されています。
各構造化束縛には、以下の説明で定義される参照型があります。この型は、括弧で囲まれていない構造化束縛にdecltypeを適用したときに返される型です。
[編集] ケース1: 配列の束縛
sb-identifier-list内の各構造化束縛は、配列の対応する要素を参照する左辺値の名前になります。Eの構造化束縛サイズは配列要素の数に等しくなります。
各構造化束縛の参照型は配列要素型です。配列型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]
[編集] ケース2: タプル操作を実装する型の束縛
式std::tuple_size<E>::valueは、整形式の整数定数式である必要があり、Eの構造化束縛サイズはstd::tuple_size<E>::valueと等しくなります。
各構造化束縛に対して、型が「std::tuple_element<I, E>::typeへの参照」である変数が導入されます。対応する初期化子が左辺値であれば左辺値参照、そうでなければ右辺値参照です。I番目の変数の初期化子は次のとおりです。
-
Eのスコープ内でクラスメンバーアクセスルックアップによって識別子getを検索した結果、最初のテンプレートパラメータが非型パラメータである関数テンプレートである宣言が少なくとも1つ見つかった場合、e.get<I>() - それ以外の場合、get<I>(e)。ここで、getは、非ADLルックアップを無視して、引数依存ルックアップによってのみ検索されます。
これらの初期化子式では、エンティティeの型が左辺値参照である場合 (これはref-qualifierが&であるか、&&であり初期化子式が左辺値である場合にのみ発生します) 、eは左辺値であり、それ以外の場合はx値です (これにより一種の完全転送が効果的に実行されます)。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 }
[編集] 初期化順序
sb-identifier-list内のI番目の構造化束縛によって名前が付けられたオブジェクトまたは参照をvalIとします。
- eの初期化は、いずれかのvalIの初期化よりも先行して順序付けられます。
- 各valIの初期化は、IがJより小さい場合のいずれかのvalJの初期化よりも先行して順序付けられます。
[編集] 注釈
|
構造化束縛は制約できません。 template<class T> concept C = true; C auto [x, y] = std::pair{1, 2}; // error: constrained |
(C++20以降) |
メンバーgetの検索は、通常通りアクセス可能性を無視し、非型テンプレートパラメータの正確な型も無視します。たとえ整形式でなくても、privateな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
xが構造化束縛を表す場合のdecltype(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&
|
構造化束縛はラムダ式によってキャプチャできません。 #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には、空の構造化束縛パックのみを導入できる識別子が厳密に1つだけ含まれている必要があります。 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) | 構造化束縛はパックを導入できます |
[編集] キーワード
[編集] 例
#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
[編集] Defect reports (欠陥報告)
以下の動作変更を伴う欠陥報告が、以前に公開された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で、メンバーはpublicである必要があった | アクセス可能であることのみが要求される 宣言のコンテキストで |
[編集] 参照
- C++23標準 (ISO/IEC 14882:2024)
- 9.6 構造化束縛宣言 [dcl.struct.bind] (p: 228-229)
- C++20 standard (ISO/IEC 14882:2020)
- 9.6 構造化束縛宣言 [dcl.struct.bind] (p: 219-220)
- C++17 standard (ISO/IEC 14882:2017)
- 11.5 構造化束縛宣言 [dcl.struct.bind] (p: 219-220)
[編集] 関連項目
| (C++11) |
左辺値参照のtupleを生成するか、タプルを個別のオブジェクトにアンパックする (関数テンプレート) |