本文へスキップ
BecomeCoder

C++コース · 第18章 実務のC++ ― 分離とモダンな型 · レッスン72

std::variant と std::tuple ― 複数の型を1つに

ブラウザで完結

導入

関数の結果が、成功したら数値・失敗したら文字列」のように、値が複数の型のどれか1つになりうる場面があります。そんなときに型安全に扱えるのが std::variant です。あわせて、複数の異なる型をひとまとめにする std::tuple も見ていきます。

説明

std::variant<int, string, double> は「今この3つの型のうち、どれか1つの値を持つ」箱です。C言語の union と似ていますが、今どの型を持っているかを安全に確認できます。holds_alternative<T>(v) で型を確認し、get_if<T>(&v) でポインタとして安全に取り出します(違う型なら nullptr が返るだけで、例外は起きません)。

#include <iostream>
#include <variant>
#include <string>
using namespace std;

int main() {
    variant<int, string, double> v = 42;

    if (holds_alternative<int>(v)) {           // 今 int を持っているか確認
        cout << "int: " << get<int>(v) << endl;
    }

    v = string("こんにちは");                   // 別の型を代入し直せる

    if (auto* p = get_if<string>(&v)) {         // 安全に取り出す(違えば nullptr)
        cout << "string: " << *p << endl;
    } else {
        cout << "string ではない" << endl;
    }

    return 0;
}

すべての型を漏れなく処理したいときは std::visit を使います。渡した関数(ラムダ)が、今 variant が持っている型に応じて自動的に呼ばれます。

#include <iostream>
#include <variant>
#include <string>
using namespace std;

int main() {
    variant<int, string, double> values[] = { 10, string("abc"), 3.14 };

    for (auto& v : values) {
        visit([](auto&& x) {                 // どの型が来ても対応できる汎用ラムダ
            cout << x << endl;
        }, v);
    }
    return 0;
}
// 10 / abc / 3.14

std::tuple は3つ以上の異なる型の値をひとまとめにする組です(第14章の pair の拡張版)。make_tuple で作り、第17章の構造化束縛できれいに分解できます。

flowchart LR
  t["tuple&lt;string, int, double&gt;"]
  t -->|"auto [name, age, height] = t;"| a["name / age / height に分解"]
#include <iostream>
#include <tuple>
#include <string>
using namespace std;

tuple<string, int, double> makeUser() {
    return make_tuple("はな", 25, 160.5);
}

int main() {
    auto [name, age, height] = makeUser();   // 構造化束縛で一気に分解
    cout << name << " " << age << " " << height << endl;
    return 0;
}
// はな 25 160.5

std::get<0>(t) のように番号で取り出すこともできますが、番号はコンパイル時に決まっている必要があります(変数を添字にはできません)。v.index() を使うと、variant が今何番目の型を持っているかを調べられます。

やってみよう

右のコードを実行し、variant に別の値を代入し直したり、values 配列に要素を足したりして挙動を確かめましょう。tuple の値を書き換えて makeUser の結果がどう変わるかも見てみてください。

演習

tuple<string, int, int> t = {"たろう", 80, 95}; を構造化束縛で name, kokugo, sansuu に分解し、国語と算数の合計点を 合計: 175 の形式で表示してください。

ヒント1を見る

auto [name, kokugo, sansuu] = t; で3つの変数に分解します。

ヒント2を見る

cout << "合計: " << (kokugo + sansuu) << endl;

実際に動かしてみよう

下のエディタにC++を書いて「コンパイル & 実行」を押すと、ブラウザ内で本物のC++コンパイラ(clang + libc++)がその場でコンパイルして実行し、出力が表示されます。std::string・vector・クラス・テンプレート・STL などモダンC++がそのまま動きます。本文の例を書き換えて試しながら進めましょう(初回だけコンパイラの読み込みに時間がかかります。以降はキャッシュされます。この回は標準入力(cin)は使わず、値はコードに直接書きます)。

C/C++ — ブラウザ内で本物のコンパイラで実行

ブラウザ内で本物のC/C++コンパイラ(clang + libc++ / WebAssembly)を動かします。構造体・malloc/free・std::string・vector・クラス・テンプレート・STL などがそのまま実行できます(初回のみコンパイラの読み込みに時間がかかります。以降はブラウザにキャッシュされます)。
スクロールして表示された時点でも自動で読み込まれます。