std::variant is a type-safe union introduced in C++17 that allows you to store one of several predefined types in a single variable while preserving type information at runtime. Unlike a C-style union, std::variant provides a rich set of operations for construction, destruction, visitation, and querying, and it guarantees that only the active member is alive. It also offers strong exception safety guarantees and integrates seamlessly with the standard library.
1. Basic definition
#include <variant>
#include <string>
#include <iostream>
using Value = std::variant<int, double, std::string>;
int main() {
Value v = 42; // holds an int
v = 3.14; // now holds a double
v = std::string{"hello"}; // now holds a std::string
std::cout << std::get<std::string>(v) << '\n';
}
The type Value can hold an int, a double, or a std::string. At any moment, only one of these types is active.
2. Construction and assignment
std::variant supports:
- Direct initialization from any of its alternatives.
- Copy/move construction and assignment.
- Default construction, which initializes the first alternative (
intin the example above) to its default value.
Value a; // a holds int{0}
Value b = 5; // holds int{5}
Value c = std::in_place_index <2>, std::string{"world"}; // holds std::string{"world"}
The std::in_place_index or std::in_place_type constructors allow you to specify which alternative to construct.
3. Visiting
The most powerful feature of std::variant is the std::visit function. It accepts a visitor (a callable object) and applies it to the active alternative.
struct Printer {
void operator()(int i) const { std::cout << "int: " << i << '\n'; }
void operator()(double d) const { std::cout << "double: " << d << '\n'; }
void operator()(const std::string& s) const { std::cout << "string: " << s << '\n'; }
};
Value v = 3.14;
std::visit(Printer{}, v); // prints "double: 3.14"
std::visit ensures that the correct overload is called, avoiding the need for manual type checks.
4. Querying the active alternative
- `std::holds_alternative (v)` returns `true` if `v` currently holds type `T`.
- `std::get (v)` returns the stored value of type `T`; it throws `std::bad_variant_access` if `T` is not the active type.
- `std::get_if (&v)` returns a pointer to the stored value or `nullptr` if not active.
if (std::holds_alternative<std::string>(v)) {
std::cout << "string value: " << std::get<std::string>(v) << '\n';
}
5. Comparison with C-style union
| Feature | std::variant | C-style union |
|---|---|---|
| Type safety | Guaranteed; only one active member. | Manual; potential undefined behavior. |
| Construction/destruction | Automatically handles all types. | Requires manual constructors/destructors. |
| Exception safety | Strong guarantee. | Risk of resource leaks. |
| Runtime type information | index() returns current type index. |
No built-in runtime info. |
| Visitation | std::visit provides compile-time dispatch. |
No standard visitor pattern. |
| Size | May be larger due to additional metadata. | Minimal, just the largest member. |
6. Practical use cases
- Parsing heterogeneous data: When parsing JSON, a node may contain a string, number, boolean, null, array, or object.
std::variantcleanly represents these possibilities. - Event handling: An event system where events can be of multiple distinct types can use a variant to hold any event object.
- State representation: Finite state machines where a state can be one of several structs can use a variant to hold the current state.
7. Performance considerations
While std::variant incurs a small runtime overhead due to its internal bookkeeping (index and exception safety mechanisms), this cost is often negligible compared to the clarity and safety benefits. For performance-critical code where memory layout matters, a traditional union combined with manual tagging might still be preferable.
8. Summary
std::variant is a powerful, type-safe alternative to unions, providing automatic lifetime management, visitation, and robust type queries. Its integration with the standard library makes it an indispensable tool for modern C++ developers who need to work with values that can be one of several types without sacrificing safety or expressiveness.