std::variant is a type-safe union that can hold one of several specified types. Since C++17 it offers a powerful alternative to classical inheritance for representing a value that may be one of a set of alternatives. In this article we will explore why std::variant is useful, how it works, and demonstrate common patterns and pitfalls through code examples.
1. Why Use std::variant?
| Traditional Polymorphism |
std::variant |
| Requires a common base class |
No common base needed |
| Virtual function overhead |
Zero runtime cost beyond type tagging |
| Subclass proliferation |
Simple to add or remove types |
| Potential for slicing |
Guarantees correct type on access |
When you have a value that can be one of several discrete types, especially when the types are unrelated, std::variant keeps the type system explicit and compile‑time safe. It also eliminates the indirection and dynamic dispatch costs of virtual functions.
2. Basic Syntax
#include <variant>
#include <string>
#include <iostream>
#include <vector>
using Value = std::variant<int, double, std::string>;
int main() {
Value v = 42; // holds an int
v = 3.14; // holds a double
v = std::string{"Hello"}; // holds a string
std::visit([](auto&& arg){
std::cout << arg << '\n';
}, v);
}
- Definition:
using Value = std::variant<int, double, std::string>;
- Construction: Implicit conversion from any alternative type.
- Access: `std::get
(v)` or `std::visit`.
3. Common Operations
| Operation |
Function |
Example |
| Check type |
`std::holds_alternative |
(v)|if (std::holds_alternative(v)) {}` |
| Get value |
`std::get |
(v)|int i = std::get(v);` |
| Visit |
std::visit(fn, v) |
See example above |
| Index |
v.index() |
Returns 0‑based index of current alternative |
| Swap |
std::swap(v1, v2) |
Swaps contents safely |
Error Handling
`std::get
(v)` throws `std::bad_variant_access` if the stored type is not `T`. Use `std::get_if(&v)` for a null‑terminated pointer when the type may be absent.
—
### 4. Example: A JSON‑Like Value
“`cpp
#include
#include
#include
#include