Structured binding declaration (since C++17)
指定された名前を初期化子の部分オブジェクトまたは要素にバインドします。
参照と同様に、構造化バインディングは既存のオブジェクトへのエイリアスです。参照とは異なり、構造化バインディングは参照型である必要はありません。
attr
(オプション)
decl-specifier-seq
ref-qualifier
(オプション)
[
sb-identifier-list
]
initializer
;
|
|||||||||
| attr | - | 任意の数の 属性 のシーケンス | ||
| decl-specifier-seq | - |
以下の指定子のシーケンス(
単純宣言
の規則に従う):
|
||
| ref-qualifier | - |
&
または
&&
のいずれか
|
||
| sb-identifier-list | - | この宣言によって導入される識別子のカンマ区切りリスト 、各識別子の後には 属性指定子シーケンス が続く場合がある (C++26以降) | ||
| initializer | - | 初期化子(下記参照) |
initializer
は以下のいずれかになります:
=
式
|
(1) | ||||||||
{
式
}
|
(2) | ||||||||
(
式
)
|
(3) | ||||||||
| expression | - | 任意の式(括弧で囲まれていない コンマ演算子 を除く) |
構造化束縛宣言は、
sb-identifier-list
内の全ての識別子を周囲のスコープにおける名前として導入し、それらを
expression
によって示されるオブジェクトの部分オブジェクトまたは要素に束縛します。このように導入された束縛は
structured bindings
と呼ばれます。
|
sb-identifier-list 内の識別子の1つは、省略記号(...)を前に付けることができます。そのような識別子は 構造化バインディングパック を導入します。 その識別子は テンプレート化されたエンティティ を宣言しなければなりません。 |
(C++26以降) |
構造化バインディングは、 sb-identifier-list 内の識別子であり、 省略記号が前に付いていないもの、または同じ識別子リストで導入された構造化バインディングパックの要素 (C++26以降) です。
目次 |
バインディングプロセス
構造化束縛宣言はまず、初期化子の値を保持するために一意の名前を持つ変数(ここでは e で示される)を導入します。以下の通りです:
-
expression
が配列型
cv1
Aを持ち、 ref-qualifier が存在しない場合、 e を attr (optional) specifiersA e;として定義する。ここで specifiers は decl-specifier-seq 内の指定子の列から auto を除外したものである。
-
その後、
e
の各要素は、
expression
の対応する要素から、
initializer
の形式によって指定される通りに初期化されます
:
- 初期化子構文 (1) の場合、要素は copy-initialized されます。
- 初期化子構文 (2,3) の場合、要素は direct-initialized されます。
-
それ以外の場合、
e
を
attr
(optional)
decl-specifier-seq
ref-qualifier
(optional)
einitializer ;と定義する。
E
を使用して、識別子式
e
の型を表します(つまり、
E
は
std::
remove_reference_t
<
decltype
(
(
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 はそれぞれ x, y, z を参照 auto [d, ...e] = C(); // OK: d は x を参照; ...e は y と z を参照 auto [...f, g] = C(); // OK: ...f は x と y を参照; g は z を参照 auto [h, i, j, ...k] = C(); // OK: パック k は空 auto [l, m, n, o, ...p] = C(); // エラー: 構造化バインディングのサイズが小さすぎます }
構造化バインディング宣言は、
E
に応じて、以下の3つの方法のいずれかでバインディングを実行します:
-
ケース1:
Eが配列型の場合、名前は配列要素にバインドされます。 -
ケース2:
Eが非共用体クラス型であり、かつ std:: tuple_size < E > がメンバーvalueを持つ完全型である場合(そのメンバーの型やアクセス可能性に関わらず)、「タプルライク」なバインドプロトコルが使用されます。 -
ケース3:
Eが非共用体クラス型であるが、 std:: tuple_size < E > が完全型でない場合、名前はEのアクセス可能なデータメンバーにバインドされます。
以下の3つのケースについて、それぞれ詳細に説明します。
各構造化バインディングには、以下に定義される
参照型
があります。この型は、括弧で囲まれていない構造化バインディングに
decltype
を適用した際に返される型です。
Case 1: 配列のバインド
sb-identifier-list
内の各構造化バインディングは、配列の対応する要素を参照する左辺値の名前となります。構造化バインディングのサイズは
E
の配列要素数と等しくなります。
各構造化バインディングの
参照される型
は配列要素の型です。配列型
E
がcv修飾されている場合、その要素型も同様に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]を参照
Case 2: タプル操作を実装する型のバインディング
式
std::
tuple_size
<
E
>
::
value
は適切な形式の
整数定数式
でなければならず、
E
の構造化バインディングサイズは
std::
tuple_size
<
E
>
::
value
と等しくなければならない。
各構造化バインディングに対して、「 std:: tuple_element < I, E > :: type への参照」型の変数が導入されます:対応する初期化子が左値の場合は左辺値参照、それ以外の場合は右辺値参照です。 I 番目の変数の初期化子は以下の通りです。
-
e.
get
<
I
>
(
)
、識別子
getをEのスコープでクラスメンバアクセス探索によって検索した際、最初のテンプレートパラメータが定数パラメータである関数テンプレートの宣言が少なくとも1つ見つかる場合 - それ以外の場合、 get < I > ( e ) 、ここで get は 実引数依存探索 によってのみ検索され、非ADL探索は無視される
これらの初期化子式において、
e
は、エンティティ
e
の型がlvalue参照である場合(これは
ref-qualifier
が
&
である場合、または
&&
であり初期化子式がlvalueである場合にのみ発生)lvalueとなり、それ以外の場合はxvalueとなります(これは事実上、一種の完全転送を実行します)。
I
は
std::size_t
のprvalueであり、
<
I
>
は常にテンプレートパラメータリストとして解釈されます。
変数は storage duration が e と同じです。
構造化バインディングは、その変数にバインドされたオブジェクトを参照するlvalueの名前となります。
参照型 は、 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 の第3要素 get<2>(tpl) を参照する構造化束縛を表す // decltype(c) は std::tuple_element<2, Tpl>::type、すなわち const int
Case 3: データメンバへのバインド
E
のすべての非静的データメンバは、
E
の直接のメンバであるか、
E
の同一の基底クラスでなければならず、
e.
name
として名前が付けられたときに、構造化バインディングのコンテキストで適切に形成されていなければなりません。
E
は匿名共用体メンバを持つことはできません。
E
の構造化バインディングサイズは、非静的データメンバの数と等しくなります。
sb-identifier-list
内の各構造化バインディングは、
e
の次のメンバを宣言順序で参照するlvalueの名前となる(ビットフィールドはサポートされている)。このlvalueの型は
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; // OK // y = -2.; // エラー: yはconst修飾されている std::cout << x << ' ' << y << '\n'; // -2 2.3 }
初期化順序
valI を、 sb-identifier-list 内のI番目の構造化バインディングによって名前付けられたオブジェクトまたは参照とする:
- 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
(
)
;
メンバーが存在する場合、たとえill-formedであっても、メンバー解釈が使用されることになります。
宣言のうち
[
より前の部分は、導入された構造化バインディングではなく、
e
という隠れた変数に適用されます:
tuple-like の解釈は、
std::
tuple_size
<
E
>
が完全型であり、メンバー
value
を持つ場合、たとえそれがプログラムを不適格にする場合でも、常に使用されます:
struct A { int x; }; namespace std { template<> struct tuple_size<::A> { void value(); }; } auto [x] = A{}; // エラー; 「データメンバ」の解釈は考慮されない。
参照が一時オブジェクトにバインドされる通常の規則(ライフタイム延長を含む)は、 ref-qualifier が存在し、かつ expression がprvalueである場合に適用されます。これらのケースでは、隠れ変数 e はprvalue式から materialized された一時変数にバインドされる参照となり、そのライフタイムを延長します。通常通り、 e が非const lvalue参照である場合、バインドは失敗します:
int a = 1; const auto& [x] = std::make_tuple(a); // OK、ダングリング参照ではない auto& [y] = std::make_tuple(a); // エラー、右辺値std::tupleにauto&をバインドできない auto&& [z] = std::make_tuple(a); // こちらもOK
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&
|
構造化バインディングは ラムダ式 によってキャプチャできませんでした: #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(); // エラー auto [...args] = return_empty(); // OK、argsは空のパック auto [one, ...rest] = return_empty(); // エラー、構造化バインディングのサイズが小さすぎる } |
(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: ビットフィールドのデフォルトメンバ初期化子 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++規格に対して遡及的に適用されました。
| 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
>
が完全型である限り
「tuple-like」束縛プロトコルが使用されていた |
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を個々のオブジェクトに展開する
(関数テンプレート) |