はじめに
C++で、「intかもしれないし、stringかもしれない」といったように、複数の異なる型のうち、どれか一つを格納できる変数が欲しい場合があります。従来の共用体 (union) はこの問題を解決しますが、現在どの型が格納されているかを自分で管理する必要があり、型安全性が低いという問題がありました。
C++17で導入された std::variant は、この問題をエレガントに解決します。variant は、テンプレート引数で指定された型のリストの中から、常にどれか一つの型の値を、型安全に保持します。
この記事では、std::variant の基本的な使い方と、その中身を安全に取り出す方法、そして std::visit を使ったスマートな処理方法を解説します。
【前提】C++17とは?
C++17(シーピープラスいちなな)は、2017年に正式化されたC++言語の規格です。std::variant はこのC++17で追加された機能のため、利用するにはC++17に対応したコンパイラが必要です。
std::variant の基本的な使い方
このコードは、variant変数に異なる型の値を代入し、.index() で現在の型を調べ、get / get_if で安全に値を取り出す方法を示します。
完成コード
#include <iostream>
#include <variant> // variant, get, get_if, visit
#include <string>
using namespace std;
int main() {
// 1. int, double, stringのいずれかの値を保持できるvariantを宣言
variant<int, double, string> data;
// --- int値を代入 ---
data = 100;
// 2. .index()で、何番目の型が格納されているか調べる (intは0番目)
cout << "現在の型インデックス: " << data.index() << endl;
// 3. get<T>で、指定した型Tとして値を取り出す
cout << "int値: " << get<int>(data) << endl;
// --- string値を代入 ---
data = "こんにちは";
cout << "\n現在の型インデックス: " << data.index() << endl; // stringは2番目
cout << "string値: " << get<string>(data) << endl;
// --- 4. get_ifを使った安全なアクセス ---
// もしdataの中身がstringでなければ、p_strはnullptrになる
if (string* p_str = get_if<string>(&data)) {
cout << "ポインタ経由での値: " << *p_str << endl;
}
}
コードの解説
variant<int, double, string> data;:int,double,stringのいずれかの値を格納できるvariant変数を宣言します。.index(): 現在variantが保持している値が、テンプレート引数リストの何番目(0始まり)の型であるかを返します。get<int>(data):variantから値を取り出すための主要な方法です。< >内で指定した型と、実際に格納されている型が一致しない場合、std::bad_variant_access例外を投げるため、安全です。get_if<string>(&data):getと似ていますが、引数にvariantへのポインタを取ります。型が一致すればその値へのポインタを、一致しなければnullptrを返すため、if文と組み合わせて例外なしで安全にアクセスできます。
std::visit で全ての型を一度に処理する
variant に格納されている型に応じて処理を分岐させたい場合、if (data.index() == ...) を連鎖させるよりも、std::visit を使う方がはるかにスマートです。visit は、全ての型に対応する処理を記述した「ビジター」を受け取ります。
サンプルコード
#include <iostream>
#include <variant>
#include <string>
using namespace std;
// 1. ビジターを構造体として定義
struct MyVisitor {
void operator()(int i) { cout << "整数値: " << i << endl; }
void operator()(double d) { cout << "小数値: " << d << endl; }
void operator()(const string& s) { cout << "文字列: " << s << endl; }
};
int main() {
variant<int, double, string> data;
// --- 構造体ビジターを使う ---
data = 3.14;
visit(MyVisitor{}, data); // variantの中身(double)に応じて、適切なoperator()が呼ばれる
// --- 2. ジェネリックラムダをビジターとして使う (より簡潔) ---
auto print_visitor = [](const auto& value){
cout << "値は " << value << " です。" << endl;
};
data = "C++";
visit(print_visitor, data);
}
コードの解説
- ビジター構造体:
variantが保持しうる全ての型に対して、()演算子をオーバーロードしたメンバ関数を定義します。visitは、dataの中身の型に応じて、適切なoperator()を自動的に呼び出してくれます。 - ジェネリックラムダ (C++14):
(const auto& value)のように、引数をautoで受け取るラムダ式を定義すると、あらゆる型に対応できるビジターとして機能します。こちらの方がより簡潔でモダンな書き方です。
まとめ
今回は、C++17の std::variant を使って、複数の異なる型を安全に扱う方法を解説しました。
std::variant<T1, T2, ...>: 複数の候補の型のうち、一つの値を保持する型安全な共用体。get<T>/get_if<T>: 型をチェックしながら、安全に値を取り出す。std::visit:variantの中身の型に応じて、処理をエレガントに振り分ける。
variant は、従来 union とタグ変数を組み合わせて行っていた処理を、現代的なC++の機能で、より安全かつシンプルに記述するための強力なツールです。
