Translate

miércoles, 20 de mayo de 2026

std::optional en C++


En muchos lenguajes modernos existe alguna forma de representar “un valor que puede no existir”:

  • Optional en Java
  • Option en Scala
  • Maybe en Haskell
  • Option en Rust (Some/None)


En C++, la respuesta oficial es std::optional, introducido en C++17 y mejorado muchísimo en C++23 con operaciones monádicas.

¿Qué problema resuelve?

Antes de optional, era común devolver:

  • nullptr
  • valores mágicos (-1)
  • flags adicionales
  • excepciones


Ejemplo clásico:


int findUserId(const std::string& name) {

    if(name == "emanuel")

        return 10;

    return -1;

}


Problema:

  • -1 no expresa claramente ausencia
  • alguien podría olvidarse de validarlo
  • el contrato del método no es explícito


Con std::optional:


#include <optional>


std::optional<int> findUserId(const std::string& name) {

    if(name == "emanuel")

        return 10;


    return std::nullopt;

}


Ahora el tipo expresa claramente: “puede devolver un entero… o no”.


Crear un optional

std::optional<int> number = 10;


Vacío:

std::optional<int> empty = std::nullopt;


Verificar si tiene valor

if(number.has_value()) {

    std::cout << "Tiene valor";

}


o más idiomático:

if(number) {

    std::cout << "Tiene valor";

}


Obtener el valor

std::cout << number.value();


Ojo! Si no tiene valor, lanza excepción.


Más seguro:

if(number) {

    std::cout << *number;

}


Valor por defecto

std::optional<int> x;

std::cout << x.value_or(0);


Resultado: 0


Muy parecido a:

  • orElse() en Java
  • getOrElse() en Scala


¿Por qué es mejor que punteros?

Muchas APIs antiguas usan punteros nullable:

User* findUser();


Problemas:

  • ownership ambiguo
  • riesgo de dangling pointers
  • semántica poco clara


Con optional:

std::optional<User> findUser();


El contrato queda explícito y seguro.

En programación funcional, una mónada permite:

  • encadenar operaciones
  • evitar checks manuales
  • propagar automáticamente ausencia/error


Antes de C++23 esto era incómodo.


Había que hacer:

if(result) {

    ...

}


todo el tiempo.


C++23 agregó operaciones monádicas oficiales.

transform

Equivalente a map.

Transforma el valor si existe.


std::optional<int> number = 10;


auto result =

    number.transform([](int x) {

        return x * 2;

    });


std::cout << *result;


Resultado: 20

Si el optional está vacío, no ejecuta nada.


and_then

Equivalente a flatMap.

Permite encadenar funciones que devuelven optional.


std::optional<int> parse(const std::string& s) {

    if(s == "42")

        return 42;


    return std::nullopt;

}


auto result =

    parse("42")

        .and_then([](int x) -> std::optional<int> {

            if(x > 0)

                return x * 2;


            return std::nullopt;

        });


Diferencia entre transform y and_then

transform convierte:

optional<T> -> optional<U>

cuando la función devuelve un valor normal


and_then convierte:

optional<T> -> optional<U>


pero la función YA devuelve optional.

Evita nested optionals: optional<optional<int>>


or_else


Permite ejecutar lógica si está vacío.


std::optional<int> value;


value.or_else([] {

    std::cout << "No había valor";

    return std::optional<int>{0};

});


Ahora podemos escribir código mucho más declarativo:


auto result =

    parse("42")

        .transform([](int x) {

            return x * 2;

        })

        .and_then([](int x) -> std::optional<int> {

            if(x < 100)

                return x;


            return std::nullopt;

        })

        .or_else([] {

            return std::optional<int>{0};

        });


Esto ya se parece muchísimo a:

  • Scala
  • Haskell
  • Rust
  • Kotlin

¿Cuándo usar optional?


Ideal para:

  • búsquedas
  • parseos
  • resultados opcionales
  • operaciones que pueden fallar naturalmente


¿Cuándo NO usarlo?

  • errores complejos
  • información detallada de fallos


En esos casos es mejor:

  • std::expected (C++23)
  • excepciones


std::optional empezó en C++17 como una forma segura de representar ausencia de valor.

Pero en C++23 evolucionó muchísimo:

  • transform
  • and_then
  • or_else


lo convierten en una herramienta claramente influenciada por programación funcional y mónadas como Maybe.

C++ sigue siendo multiparadigma, pero cada vez incorpora más ideas del mundo funcional… sin dejar de ser C++.

No hay comentarios.:

Publicar un comentario