導入
「関数の結果が、成功したら数値・失敗したら文字列」のように、値が複数の型のどれか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<string, int, double>"] 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;