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 によって示されるオブジェクトの部分オブジェクトまたは要素に束縛します。このように導入された束縛は structured bindings と呼ばれます。

sb-identifier-list 内の識別子の1つは、省略記号(...)を前に付けることができます。そのような識別子は 構造化バインディングパック を導入します。

その識別子は テンプレート化されたエンティティ を宣言しなければなりません。

(C++26以降)

構造化バインディングは、 sb-identifier-list 内の識別子であり、 省略記号が前に付いていないもの、または同じ識別子リストで導入された構造化バインディングパックの要素 (C++26以降) です。

目次

バインディングプロセス

構造化束縛宣言はまず、初期化子の値を保持するために一意の名前を持つ変数(ここでは e で示される)を導入します。以下の通りです:

  • expression が配列型 cv1 A を持ち、 ref-qualifier が存在しない場合、 e attr  (optional) specifiers A e; として定義する。ここで specifiers decl-specifier-seq 内の指定子の列から auto を除外したものである。
その後、 e の各要素は、 expression の対応する要素から、 initializer の形式によって指定される通りに初期化されます :
  • それ以外の場合、 e attr  (optional) decl-specifier-seq ref-qualifier  (optional) e initializer  ; と定義する。

E を使用して、識別子式 e の型を表します(つまり、 E std:: remove_reference_t < decltype ( ( 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(); // 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 という隠れた変数に適用されます:

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);                    // 成功

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) パックを導入可能な構造化バインディング

キーワード

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++規格に対して遡及的に適用されました。

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を個々のオブジェクトに展開する
(関数テンプレート)