第62回 std::variant と std::any
C の union を型安全にした std::variant(指定した候補型のいずれか)と、任意型を持てる std::any(void* の型安全版)。C++17 の 2 大多態型。
ざっくり使い分け
✅ variant
- 候補型がコンパイル時に決まるケース
- 例:
variant<int, std::string, Error>
- visit / get で型安全にアクセス
- 速い・RTTI 不要
⭐ any
- 実行時にしか型が決まらないケース
- 例:プラグインが返す不明な型
- any_cast<T> で取り出す
- やや遅い・RTTI を使用
1. std::variant ― 型安全 union
基本variant
#include <variant>
std::variant<int, double, std::string> v;
v = 42; // int を保持
v = 3.14; // double を保持(切り替え)
v = std::string{"hi"};
// 現在の型インデックス
auto idx = v.index(); // 2 (string の位置)
// 特定の型を取り出す
auto s = std::get<std::string>(v); // 型が違うと bad_variant_access
auto p = std::get_if<std::string>(&v); // 違うと nullptr
// 型チェック
if (std::holds_alternative<int>(v)) { ... }
visit ― 全ケースを漏れなく扱う
std::visitパターンマッチ風
std::variant<int, std::string> v = 42;
std::visit([](auto&& x){
std::cout << x; // int でも string でも対応
}, v);
// 型ごとに違う処理
std::visit([](auto&& x) {
using T = std::decay_t<decltype(x)>;
if constexpr (std::is_same_v<T, int>)
std::cout << "int: " << x;
else
std::cout << "str: " << x;
}, v);
2. std::any ― 任意型
any任意
#include <any>
std::any a = 42;
a = std::string{"hello"}; // 型を変えて保持可能
a = 3.14;
// 取り出すとき型を明示
double d = std::any_cast<double>(a); // OK
// std::any_cast<int>(a); ← std::bad_any_cast 例外
// ポインタ版(失敗時 nullptr)
if (auto* p = std::any_cast<double>(&a)) {
std::cout << *p;
}
// 現在の型
a.type() == typeid(double); // true
a.has_value();
a.reset(); // 空に
3. 使い分け
選択肢が決まっているなら variant(高速、型チェックがコンパイル時)。不明型を受けたいなら any(柔軟だが遅い)。ほとんどの場合 variant で足りる ― any を使う前に「本当に型が不明?」と自問を。
他言語との対応:
variant ≈ Rust の enum、Haskell の sum type、TypeScript のユニオン型
any ≈ Java の Object、TypeScript の unknown、void* の型安全版
確認クイズ
Q1. variant から型不明のまま全ケースを処理する関数は?
std::get
std::any_cast
std::visit
std::switch
std::visit は全ケースに対応する lambda を受け取り、保持されている型に応じて呼び分けます。
Q2. variant と any の主な違いは?
any が古く variant が新しい
variant はコンパイル時に候補型が決まる、any は任意型
any は C++17、variant は C++20
同じ機能
variant は候補を明示するので型安全・高速。any は何でも入るので柔軟だが RTTI 依存で遅い。
Q3. variant の型違いアクセスで起こること(get<int> なのに中は string)は?
nullptr が返る
自動変換される
std::bad_variant_access 例外
未定義動作
例外が投げられます。nullptr で返したいなら std::get_if を使う。
Q4. 「設定値が bool / int / string のいずれか」を表現するのに自然な型は?
std::any
std::variant<bool, int, std::string>
void*
3 つの shared_ptr
候補が決まっているので variant が最適。「どれか」を型で表現できる上、visit で漏れを防げます。