【C++17】std::variant の使い方 | 型安全な共用体で複数の型を扱う

目次

はじめに

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;
    }
}

コードの解説

  1. variant<int, double, string> data;: int, double, string のいずれかの値を格納できる variant 変数を宣言します。
  2. .index(): 現在 variant が保持している値が、テンプレート引数リストの何番目(0始まり)の型であるかを返します。
  3. get<int>(data): variant から値を取り出すための主要な方法です。< > 内で指定した型と、実際に格納されている型が一致しない場合、std::bad_variant_access例外を投げるため、安全です。
  4. 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);
}

コードの解説

  1. ビジター構造体: variant が保持しうる全ての型に対して、()演算子をオーバーロードしたメンバ関数を定義します。visit は、data の中身の型に応じて、適切な operator() を自動的に呼び出してくれます。
  2. ジェネリックラムダ (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++の機能で、より安全かつシンプルに記述するための強力なツールです。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次