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 eitherint,double, orstring..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 astd::bad_variant_accessexception if the specified type in< >does not match the actually stored type.get_if<string>(&data): Similar toget, but takes a pointer to the variant as an argument. It returns a pointer to the value if the types match, andnullptrif they don’t. This allows for safe, exception-free access when combined with anifstatement.
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 thevariantcan hold.visitautomatically calls the appropriateoperator()according to the type of content indata. - Generic Lambda (C++14): Defining a lambda expression that accepts arguments with
autolike(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 thevariant.
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.
