💡 Understanding union vs std::variant in C++ (from low-level to modern safety)
Let’s start with a classic C++ concept: union.
A union allows multiple members to share the same memory location. Instead of allocating space for each field, it uses only enough memory for the largest one.
union Value {
Node* p;
int i;
};
Here:
-
pandioccupy the same address - The union size =
max(sizeof(Node*), sizeof(int))
👉 This is efficient — but comes with a cost.
⚠️ The Problem
A union does not track which value is active.
Value v;
v.i = 42;
// Later...
std::cout << v.p; // ❌ Undefined behavior
You’re responsible for remembering what’s stored. That’s why we often add a manual tag:
enum class Type { ptr, num };
struct Entry {
Type t;
Value v;
};
And use it like:
if (entry.t == Type::num)
std::cout << entry.v.i;
This pattern is called a tagged union.
🧠 The Modern C++ Solution: std::variant
C++ now gives us a safer, cleaner alternative:
#include <variant>
using Value = std::variant<Node*, int>;
Now:
- The type knows what it currently holds
- No manual tag needed
- No undefined behavior (if used correctly)
✅ Accessing the value safely
Value v = 42;
if (std::holds_alternative<int>(v)) {
std::cout << std::get<int>(v);
}
Or even better:
std::visit([](auto&& val) {
std::cout << val;
}, v);
🔄 Comparison
| Feature | union |
std::variant |
|---|---|---|
| Memory usage | Minimal | Slightly larger |
| Safety | ❌ Manual | ✅ Automatic |
| Type tracking | ❌ External enum | ✅ Built-in |
| Ease of use | ⚠️ Error-prone | ✅ Clean & expressive |
🚀 Takeaway
- Use
unionwhen you need low-level control and maximum efficiency - Use
std::variantwhen you want type safety and clarity
In modern C++, the default choice should almost always be:
👉 std::variant over raw union
Top comments (0)