はじめに
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++の機能で、より安全かつシンプルに記述するための強力なツールです。