C++ Learning

第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 で漏れを防げます。
← 前の講座
第61回 optional