[C++17] How to Use std::variant | Handling Multiple Types with Type-Safe Unions

目次

Introduction

In C++, you may want a variable that can store one of several different types, such as “maybe an int, maybe a string.” Conventional unions solve this problem, but they have low type safety because you have to manage which type is currently stored yourself.

std::variant introduced in C++17 solves this problem elegantly. variant holds a value of one of the types specified in the template argument list in a type-safe manner at all times.

In this article, I will explain the basic usage of std::variant, how to safely retrieve its contents, and smart processing methods using std::visit.

Prerequisite: What is C++17?

C++17 is the C++ language standard formalized in 2017. Since std::variant is a feature added in C++17, you need a compiler that supports C++17 to use it.

Basic Usage of std::variant

This code demonstrates how to assign values of different types to a variant variable, check the current type with .index(), and safely retrieve the value with get / get_if.

Complete Code

#include <iostream>
#include <variant> // variant, get, get_if, visit
#include <string>

using namespace std;

int main() {
    // 1. Declare a variant that can hold an int, double, or string
    variant<int, double, string> data;
    
    // --- Assign int value ---
    data = 100;
    // 2. Check which type index is stored with .index() (int is 0th)
    cout << "Current type index: " << data.index() << endl;
    // 3. Retrieve value as specified type T with get<T>
    cout << "int value: " << get<int>(data) << endl;

    // --- Assign string value ---
    data = "Hello";
    cout << "\nCurrent type index: " << data.index() << endl; // string is 2nd
    cout << "string value: " << get<string>(data) << endl;
    
    // --- 4. Safe access using get_if ---
    // If the content of data is not string, p_str becomes nullptr
    if (string* p_str = get_if<string>(&data)) {
        cout << "Value via pointer: " << *p_str << endl;
    }
}

Execution Result

Current type index: 0
int value: 100

Current type index: 2
string value: Hello
Value via pointer: Hello

Code Explanation

  • variant<int, double, string> data;: Declares a variant variable that can store either int, double, or string.
  • .index(): Returns the zero-based index of the type currently held by the variant in the template argument list.
  • get<int>(data): The primary method to retrieve a value from a variant. It is safe because it throws a std::bad_variant_access exception if the specified type in < > does not match the actually stored type.
  • get_if<string>(&data): Similar to get, but takes a pointer to the variant as an argument. It returns a pointer to the value if the types match, and nullptr if they don’t. This allows for safe, exception-free access when combined with an if statement.

Processing All Types at Once with std::visit

If you want to branch processing based on the type stored in variant, using std::visit is much smarter than chaining if (data.index() == ...). visit accepts a “visitor” that describes the processing for all types.

Sample Code

#include <iostream>
#include <variant>
#include <string>

using namespace std;

// 1. Define visitor as a struct
struct MyVisitor {
    void operator()(int i) { cout << "Integer: " << i << endl; }
    void operator()(double d) { cout << "Decimal: " << d << endl; }
    void operator()(const string& s) { cout << "String: " << s << endl; }
};

int main() {
    variant<int, double, string> data;
    
    // --- Use struct visitor ---
    data = 3.14;
    visit(MyVisitor{}, data); // Appropriate operator() is called based on variant content (double)

    // --- 2. Use generic lambda as visitor (More concise) ---
    auto print_visitor = [](const auto& value){
        cout << "Value is " << value << "." << endl;
    };
    
    data = "C++";
    visit(print_visitor, data);
}

Code Explanation

  • Visitor Struct: Define member functions overloading the () operator for all types that the variant can hold. visit automatically calls the appropriate operator() according to the type of content in data.
  • Generic Lambda (C++14): Defining a lambda expression that accepts arguments with auto like (const auto& value) functions as a visitor that can handle any type. This is a more concise and modern way of writing.

Summary

In this article, I explained how to safely handle multiple different types using std::variant in C++17.

  • std::variant<T1, T2, ...>: A type-safe union that holds one value out of multiple candidate types.
  • get<T> / get_if<T>: Safely retrieve values while checking types.
  • std::visit: Elegantly dispatch processing according to the type of content in the variant.

variant is a powerful tool for writing processes that were conventionally done by combining union and tag variables, in a safer and simpler manner using modern C++ features.

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

この記事を書いた人

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

目次