Desde C++17, std::variant forma parte de la STL como una alternativa segura y tipada a las uniones clásicas (union) y a jerarquías con herencia cuando solo necesitamos representar uno de varios tipos posibles.
std::variant<Ts...> es un tipo discriminado que puede contener exactamente uno de los tipos listados en Ts....
#include <variant>
std::variant<int, double, std::string> v;
En todo momento, v contiene un solo valor, y el compilador sabe qué tipos son válidos.
¿Por qué no usar union? Los union tradicionales:
- No son type-safe
- No manejan bien tipos no triviales
- Requieren bookkeeping manual
std::variant soluciona esto:
- Seguridad de tipos
- Manejo correcto de constructores/destructores
- Integración con la STL
std::variant<int, std::string> v1 = 10;
v1 = std::string("hola");
Si intentás asignar un tipo que no está en el variant, el código no compila.
Veamos como accedemos al valor:
int x = std::get<int>(v1);
Lanza std::bad_variant_access si el tipo activo no coincide.
std::get_if<T> (forma segura)
if (auto p = std::get_if<std::string>(&v1)) {
std::cout << *p;
}
Devuelve nullptr si el tipo no coincide.
Cómo saber qué tipo está activo
if (v1.index() == 0) {
// es int
}
Usar index() suele ser menos expresivo que std::visit.
std::visit permite aplicar una función al valor contenido, sin saber su tipo concreto.
std::visit([](auto&& value) {
std::cout << value;
}, v1);
El compilador genera una implementación segura para cada alternativa.
Overload pattern (muy usado)
template<class... Ts>
struct overloaded : Ts... {
using Ts::operator()...;
};
template<class... Ts>
overloaded(Ts...) -> overloaded<Ts...>;
std::visit(overloaded{
[](int i) { std::cout << "int: " << i; },
[](const std::string& s) { std::cout << "string: " << s; }
}, v1);
Si una excepción ocurre durante una asignación:
if (v1.valueless_by_exception()) {
// estado inválido
}
Es raro, pero importante en código robusto.
- variant evita jerarquías artificiales
- No necesita `virtual`
- Más eficiente en muchos casos
Veamos el caso de uso típico:
using Result = std::variant<int, std::string>;
Result parse(const std::string& s) {
if (s.empty()) return "error";
return std::stoi(s);
}
En C++26 o 29 contaremos con pattern matching, hoy tenemos que hacer :
std::visit(overloaded{
[](int i) { /* ... */ },
[](std::string s) { /* ... */ }
}, v);
Futuro (propuesto):
v match {
int i => /* ... */,
std::string s => /* ... */
};
std::variant es la base natural para el pattern matching moderno.
- std::variant es la unión segura del C++ moderno
- Reduce errores de diseño
- Elimina jerarquías innecesarias
- Es clave para código expresivo y funcional
Si usás C++17 o superior, debería ser tu primera opción cuando necesitás representar una de varias alternativas.
