Translate

viernes, 19 de diciembre de 2025

std::variant en C++ — Unión segura y moderna


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.