名前空間
変種
操作

構造化束縛宣言 (C++17以上)

提供: cppreference.com
< cpp‎ | language
 
 
 
 

指定した名前を初期化子の部分オブジェクトまたは要素に束縛します。

参照と同じく、構造化束縛は既存のオブジェクトへのエイリアスです。 参照と異なり、構造化束縛の型は参照型であるとは限りません。

attr(オプション) cv-auto ref-operator(オプション) [ identifier-list ] = expression ; (1)
attr(オプション) cv-auto ref-operator(オプション) [ identifier-list ] { expression } ; (2)
attr(オプション) cv-auto ref-operator(オプション) [ identifier-list ] ( expression ) ; (3)
attr - 任意個の属性の並び。
cv-auto - 型指定子 auto (cv 修飾しても構いません)。
ref-operator - & または && のいずれか。
identifier-list - この宣言によって導入される識別子のコンマ区切りのリスト。
expression - 配列型または非 union クラス型いずれかの、トップレベルにコンマ演算子を持たない式 (文法的には代入式)。 expressionidentifier-list 内のいずれかの名前を参照する場合、その宣言は ill-formed です。

構造化束縛宣言は identifier-list 内のすべての識別子を囲っているスコープに名前として導入し、 expression によって表されるオブジェクトの部分オブジェクトまたは要素にそれらを束縛します。 このように導入された束縛は構造化束縛と言います。

構造化束縛宣言は、まず、以下のように、初期化子の値を保持するために、一意な名前の変数 (ここでは e で表します) を導入します。

  • expression が配列型 A であり、 ref-operator が存在しない場合、 ecv A 型です。 ただし cvcv-auto のシーケンス内の cv 修飾子です。 e のそれぞれの要素は expression の対応する要素からコピー初期化 ((1) の場合) または直接初期化 ((2,3) の場合) されます。
  • そうでなければ、 e は、宣言内の [ identifier-list ] の代わりにその名前を使用することによって行われたかのように定義されます。

ここでは式 e の型を表すために E を使用します (別の言い方をすると、 Estd::remove_reference_t<decltype((e))> と同等です)。

構造化束縛宣言は、その後、 E に応じて、可能な3つの方法のいずれかで、束縛を行います。

  • ケース1: E が配列型の場合は、名前は配列の要素に束縛されます。
  • ケース2: E が非 union クラス型で std::tuple_size<E> が完全型の場合は、「タプルライクな」束縛規約が使用されます。
  • ケース3: E が非 union クラス型だけれども std::tuple_size<E> が完全型でない場合は、名前は E のアクセス可能なデータメンバに束縛されます。

3つのケースはそれぞれ下でより詳しく説明されます。

それぞれの構造化束縛は下の説明で定義される参照される型を持ちます。 この型は、括弧で囲っていない構造化束縛に適用したときに decltype によって返される型です。

目次

[編集] ケース1: 配列の束縛

identifier-list 内のそれぞれの識別子は、配列の対応する要素を参照する左辺値の名前になります。 識別子の数は配列の要素数と等しくなければなりません。

それぞれの識別子に対する参照される型は、配列の要素の型です。 配列の型 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 は well-formed な整数定数式でなければならず、識別子の数は std::tuple_size<E>::value と等しくなければなりません。

それぞれの識別子について、型が「std::tuple_element<i, E>::type への参照」である変数が導入されます。 対応する初期化子が左辺値であれば左辺値参照、そうでなければ右辺値参照です。 i 番目の変数に対する初期化子は、

  • クラスメンバアクセスの名前探索による E のスコープ内の識別子 get に対する名前探索が、最初のテンプレート引数が非型引数である関数テンプレートである宣言を少なくともひとつ発見した場合は、 e.get<i>()
  • そうでなければ、 get<i>(e)。 ただし get実引数依存の名前探索によって名前探索されます (非 ADL 名前探索は無視します)。

これらの初期化子式において、エンティティ e の型が左辺値参照であれば (これは ref-operator& の場合または && で初期化子式が左辺値の場合にのみ発生します)、 e は lvalue であり、そうでなければ xvalue です (これは実質的に完全転送の一種を行います)。 istd::size_t の prvalue であり、 <i> は常にテンプレート引数リストとして解釈されます。

識別子は、その後、前述の変数に束縛されるオブジェクトを参照する左辺値の名前になります。

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;
// a は x を参照する構造化束縛を表します。 decltype(a) は float& です。
// b は y を参照する構造化束縛を表します。 decltype(b) は char&& です。
// c は tpl の3番目の要素を参照する構造化束縛を表します。 decltype(c) は const int です。

[編集] ケース3: データメンバへの束縛

E のすべての非静的データメンバは E または E の同じ基底クラスの直接のメンバでなければならず、 e.name として表されたときに構造化束縛の文脈で well-formed でなければなりません。 E は無名共用体メンバを持っていてはなりません。 識別子の数は非静的データメンバの数と等しくなければなりません。

identifier-list 内のそれぞれの識別子は宣言順で e の次のメンバを参照する左辺値の名前になります (ビットフィールドはサポートされます)。 左辺値の型は cv T_i です。 ただし cvE の cv 修飾で、 T_i は i 番目の宣言された型です。

i 番目の識別子の参照される型cv T_i です。

struct S {
    int x1 : 2;
    volatile double y1;
};
S f();
 
const auto [x, y] = f(); // x は2ビットのビットフィールドを表す const int の左辺値です。
                         // y は const volatile double の左辺値です。

[編集] ノート

メンバ get の名前探索は通常通りアクセス可能性を無視し、非型テンプレート引数の正確な型も無視します。 プライベートな 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);                    // パスします。

std::tuple_size<E> が完全型であれば、たとえそれがプログラムを ill-formed にするであろうとも、常にタプルライクの解釈が用いられます。

struct A { int x; };
namespace std {
    template<> struct tuple_size<::A> {};
}
 
auto [x] = A{}; // エラー、「データメンバ」の解釈は考慮されません。

ref-operator が存在し、 expression が prvalue の場合は、一時オブジェクトへの参照束縛に対する通常のルール (生存期間の延長を含みます) が適用されます。 その場合、不可視の変数 e は prvalue 式から具体化された一時変数を束縛する参照です (その生存期間を延長します)。 通常通り、 e が非 const 左辺値参照の場合は、束縛は失敗します。

int a = 1;
const auto& [x] = std::make_tuple(a); // OK、ダングリングではありません。
auto&       [y] = std::make_tuple(a); // エラー、 auto& は右辺値の std::tuple に束縛できません。
auto&&      [z] = std::make_tuple(a); // OK。

decltype(x) (ただし x は構造化束縛を表します) は、その構造化束縛の参照される型を表します。 タプルライクのケースでは、これは std::tuple_element によって返される型です。 これは、たとえこのケースでは構造化束縛それ自体が実際に常に参照のように振る舞うのだとしても、参照であることはありません。 これは実質的に、単なる実装の詳細である束縛それ自体の参照性を持つ、非静的メンバが 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 <set>
#include <string>
#include <iomanip>
#include <iostream>
 
int main() {
    std::set<std::string> myset;
    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";
}

出力:

insert is successful. The value is "Hello"

[編集] 欠陥報告

以下の動作変更欠陥報告は以前に発行された C++ 標準に遡って適用されました。

DR 適用先 発行時の動作 正しい動作
P0961R1 C++17 in the tuple-like case, member get is used if lookup finds a get of whatever kind only if lookup finds a function template with a non-type parameter
P0969R0 C++17 in the binding-to-members case, the members are required to be public only required to be accessible in the context of the declaration

[編集] 関連項目

左辺値参照の tuple を作成したり、タプルを個々のオブジェクトに分解したりします
(関数テンプレート) [edit]