In modern C++ (C++17 and beyond), std::variant provides a powerful tool for type-safe polymorphism without the overhead of dynamic allocation. Unlike classic inheritance hierarchies, std::variant can hold one of several types, enforce compile‑time safety, and allow you to handle each case elegantly. This article demonstrates practical usage patterns, common pitfalls, and performance considerations.
1. Basic Syntax and Construction
#include <variant>
#include <iostream>
#include <string>
using Response = std::variant<int, std::string, double>;
Response r1 = 42; // int
Response r2 = std::string("ok"); // std::string
Response r3 = 3.1415; // double
std::variant is an aggregate that stores the type information and the actual value. The compiler deduces the variant type from the initializer or explicitly specifies it.
2. Visiting with std::visit
The core of variant handling is std::visit, which applies a visitor (a functor or lambda) to the current active member.
void printResponse(const Response& r) {
std::visit([](auto&& value) {
std::cout << "Value: " << value << '\n';
}, r);
}
The generic lambda [](auto&& value) automatically deduces the type of the active member. You can also provide overloaded lambdas for more nuanced handling:
std::visit(overloaded{
[](int i) { std::cout << "int: " << i << '\n'; },
[](const std::string& s) { std::cout << "string: " << s << '\n'; },
[](double d) { std::cout << "double: " << d << '\n'; }
}, r);
overloaded is a helper to combine multiple lambdas:
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<class... Ts> overloaded(Ts...)->overloaded<Ts...>;
3. Accessing the Value Safely
You can use `std::get
()` to retrieve the value if you know the type, but it throws `std::bad_variant_access` if the active type mismatches. A safer approach is `std::get_if()`, which returns a pointer or `nullptr`. “`cpp if (auto p = std::get_if (&r)) { std::cout << "int is " << *p << '\n'; } “` This pattern avoids exceptions and lets you guard against wrong type accesses. — ### 4. Common Use Cases #### 4.1 HTTP Response Wrapper “`cpp using HttpResponse = std::variant< std::monostate, // no response yet std::pair, // status + body std::runtime_error // error >; HttpResponse fetch(const std::string& url) { try { // pretend we fetch something return std::make_pair(200, “Hello World”); } catch (…) { return std::runtime_error(“network failure”); } } “` #### 4.2 Visitor Pattern Replacement A traditional visitor often requires a base class and virtual functions. With `std::variant`, you can replace it with: “`cpp using Shape = std::variant; std::visit([](auto&& shape){ shape.draw(); }, shapeInstance); “` Each concrete shape type implements `draw()`, but you no longer need a virtual table. — ### 5. Performance Considerations – **Size**: `std::variant` stores the largest type among its alternatives plus a discriminator. If you mix small and large types, consider using `std::optional<std::variant>` to reduce space. – **Cache locality**: Because the value is stored inline, accessing the active member is usually faster than dynamic allocation. – **Exception safety**: `std::visit` guarantees that if the visitor throws, the variant remains unchanged. However, constructing the variant itself can throw if any alternative’s constructor throws. — ### 6. Pitfalls and How to Avoid Them | Pitfall | How to Fix | |———|————| | Forgetting to include all possible types in overloads | Use a default case or `std::visit` with `std::get_if` | | Misusing `std::monostate` as an actual value | Keep `std::monostate` only for “empty” state | | Using `std::get ()` without checking | Prefer `std::get_if()` or guard with `std::holds_alternative()` | — ### 7. Summary `std::variant` is a versatile, type-safe, and efficient alternative to classic polymorphism for many modern C++ scenarios. By mastering construction, visitation, and safe access, you can write cleaner code with fewer runtime costs. Whether you’re building network responses, UI widgets, or a scripting engine, consider `std::variant` as a first-class citizen in your toolkit.</std::variant