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++.













.jpeg)