Step 2 of 7
std::variant and std::visit
Sometimes a value can be one of several different kinds: a JSON value is a number, a string or a list; a shape is a circle or a rectangle; a message is a login, a chat line or a logout. In C you'd use a union plus a tag field and hope nobody reads the wrong member.
std::variant<A, B, C> (from <variant>) holds exactly one of its types at a time and always knows which one. Reading the wrong one is caught instead of silently producing garbage.
#include <iostream>
#include <string>
#include <variant>
#include <vector>
struct Circle { double r; };
struct Square { double side; };
using Shape = std::variant<Circle, Square>;
double area(const Shape& s) {
if (const Circle* c = std::get_if<Circle>(&s)) return 3.14 * c->r * c->r;
return std::get<Square>(s).side * std::get<Square>(s).side;
}
int main() {
std::vector<Shape> shapes = {Circle{1}, Square{3}};
for (const auto& s : shapes) {
std::cout << (std::holds_alternative<Circle>(s) ? "circle " : "square ") << area(s) << "\n";
}
std::variant<int, std::string> cell = 42;
cell = std::string("hello"); // now it holds a string
std::visit([](const auto& x) { std::cout << "cell: " << x << "\n"; }, cell);
std::cout << "index " << cell.index() << "\n";
}
circle 3.14
square 9
cell: hello
index 1
Ways to look inside
| Tool | Use |
|---|---|
std::holds_alternative<T>(v) |
true if it currently holds a T |
std::get<T>(v) |
the T inside (only when it holds one) |
std::get_if<T>(&v) |
a pointer to the T, or nullptr if it holds something else |
std::visit(f, v) |
calls f with whatever is inside |
v.index() |
which alternative, counting from 0 |
std::get_if combines the check and the access, which is why the area function uses it. In std::visit, a lambda with an auto parameter is compiled once per alternative, so it handles every type.
std::get with the wrong type throws std::bad_variant_access. When a wrong type is a normal possibility rather than a bug, check first with holds_alternative or use get_if.
Your turn: an Event is either a Kill{attacker, victim}, a Plant{site} or a RoundEnd{winner}. Write std::string describe(const Event& e) returning "A killed B", "bomb planted at X" or "round to W". Use std::holds_alternative/std::get, or std::get_if.