C++/構造化束縛宣言

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++

概要[編集]

C++17で導入された構造化束縛宣言( Structured binding declaration )は、タプルや構造体、配列から複数の値を簡単に取り出す機能です。従来はタプルの要素にアクセスするためにstd::get<>()関数を使う必要がありましたが、構造化束縛宣言を使えばコードがよりシンプルになります。

この機能が導入された背景には、タプルや構造体から値を取り出す際の記述の簡潔化と、一時的なオブジェクトからの値の抽出の2点があげられます。

構文[編集]

構造化束縛宣言はautoキーワードと角括弧[]を使って宣言します。以下に基本的な構文と使用例を示します。

// タプルの構造化束縛
auto [x, y, z] = getTuple(); 

// 構造体の構造化束縛  
struct Data { int x; double y; };
Data d = ...;
auto [x, y] = d;

// 配列の構造化束縛
int array[] = {1, 2, 3};
auto [a, b, c] = array;

スコープとライフタイム[編集]

構造化束縛で宣言された変数のスコープは、その変数が定義されたスコープ内になります。ライフタイムは抽出元のオブジェクトによって決まります。

std::tuple<int, double> getTuple() {
    return {1, 3.14};
}

void process() {
    auto [x, y] = getTuple(); // xとyのライフタイムはこの関数内
    // xとyの操作...
}

参照への束縛も可能で、その場合は抽出元への参照が束縛されます。

std::tuple<int, double&> getTuple() {
    double d = 3.14;
    return {1, d};
}

void process() {
    auto &[x, y] = getTuple(); // yはdouble&
    // yを変更するとdも変更される
}
C++の束縛
プログラミング言語におけるデータの束縛(Binding; バインディング)とは、名前(変数)にデータを関連付けることを指します。C++言語でも様々な種類の束縛があり、その正確な理解が重要になります。

最も基本的な束縛は、変数宣言時の束縛です。

int x{42}; // xに42が束縛される

値の束縛に加え、C++では参照による束縛も可能です。

int y{123};
int& z{y}; // zにyの参照が束縛される

この場合、zはyそのものを参照するエイリアスになり、zを変更するとyの値も変わります。

構造化束縛宣言が導入されるまでは、タプルから値を取り出す際はstd::get()関数で行う必要がありました。

std::tuple<int, double> pair = {1, 3.14};
int x = std::get<0>(pair); // 1が取り出される
double y = std::get<1>(pair); // 3.14が取り出される

構造化束縛宣言では、この操作が1行で書けます。

auto [x, y] = pair; // xに1、yに3.14が束縛される

さらに構造化束縛宣言では、束縛する変数に参照を指定できます。

auto& [xref, yref] = pair; // xrefとyrefにpairの要素の参照が束縛される

この場合、xref、yrefはpairの要素それ自身を参照するエイリアスになります。

束縛の概念は、ラムダ式やテンプレートなど、C++の様々な仕組みの理解に重要になります。構造化束縛宣言ではタプルなどのデータから簡潔に値を取り出せますが、その「束縛」の意味を正しく理解することが肝心です。束縛の概念を確実に身につけることで、C++のコードをよりマスターできるはずです。


利点と使用例[編集]

構造化束縛宣言の主な利点は以下の通りです。

  • 複数の値をシンプルに扱える
  • タプルの要素へのアクセスが簡単
  • 構造体のメンバー変数への分解が可能
  • ネストされた構造から値を簡単に取り出せる

以下に具体的な使用例を示します。

配列からの値の取り出し
int arr[] = {1, 2, 3};
auto [x, y, z] = arr; // x=1, y=2, z=3
std::tupleからの値の取り出し
std::tuple<int, double, std::string> get_data() {
    return {10, 3.14, "hello"};
}

void tuple_usage() {
    auto [x, y, str] = get_data();
    // x == 10, y == 3.14, str == "hello"
}
構造体のメンバーへアクセス
struct Point { 
    int x, y;
};

void print_point(Point p) {
    auto [x, y] = p; // Point構造体の分解
    std::cout << "(" << x << ", " << y << ")\n";
}
ネストされた構造からの値の取り出し
std::tuple<int, std::pair<double, char>> get_pair() {
    return {1, {3.14, 'x'}};
}

void nested_usage() {
    auto [x, p] = get_pair();
    auto [y, c] = p; // std::pairからの分解
    // x == 1, y == 3.14, c == 'x'
}

注意点[編集]

構造化束縛宣言を使う際の主な注意点は以下の通りです。

非修飾参照の扱い
構造化束縛で非修飾参照を宣言した場合、一時オブジェクトから値を抽出する際に未定義動作になる可能性がある。
std::pair<int, int> get_pair() {
    return {1, 2}; // 一時オブジェクト
}

void bad_example() {
    auto [x, y] = get_pair(); // OK
    auto& [x_ref, y_ref] = get_pair(); // 未定義動作の可能性あり
}
構造体から値を取り出す際の注意
名前付き構造体のメンバーから値を取り出す際、構造化束縛宣言の変数名とメンバー名が同じ場合に問題が発生する。この場合はメンバー呼び出しが優先されるので注意が必要。
struct Point { int x, y; };

void bad_example(Point p) {
    int x{10}; // xという名前の変数
    auto [x, y] = p; // x == p.x, y == p.yとはならない
}

他の言語との比較[編集]

RubyPythonSwiftRustなどの言語にも構造化束縛と同等の機能があります。特にRustのlet (x, y) = pointといった構文はC++の構造化束縛宣言と良く似ています。

一方で、C++の構造化束縛宣言には他言語にはない独自の制限もあります。例えばC++ではメンバー変数名と同じ名前の変数を構造化束縛で宣言できません。このような細かな違いに注意が必要です。

まとめ[編集]

構造化束縛宣言はタプルや構造体、配列から複数の値を簡単に取り出すための機能で、コードの簡潔さとデータの扱いやすさが大きな利点です。一方で一時オブジェクトへの参照束縛など、注意が必要な点もあります。この新機能を使いこなすことで、C++コードをよりクリーンに保つことができるでしょう。

コード例
#include <iostream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>

auto main() -> int {
    // 配列の要素を個々の変数の初期値に使う
    int const ary[]{2, 3, 5, 7, 11};
    auto [i1, i2, i3, i4, i5] = ary;
    std::cout << "i1 = " << i1 << ", i2 = " << i2 << ", i3 = " << i3
              << ", i4 = " << i4 << ", i5 = " << i5 << std::endl;

    // タプルの要素を個々の変数の初期値に使う
    auto tpl = std::tuple{42, 3.14};
    auto [ii, dd] = tpl;
    std::cout << "ii = " << ii << ", dd = " << dd << std::endl;

    // std::vectorの初期化
    auto const vec = std::vector<std::pair<int, std::string>> {
        {1, "one"}, {2, "two"}, {3, "three"}};

    // 構造化束縛を使って各要素を分解
    for (const auto &[num, word] : vec) {
        std::cout << "Number: " << num << ", Word: " << word << std::endl;
    }

    return 0;
}