optional, variant, any
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 89 de 104.
C++ moderno proporciona tres tipos de vocabulario en las cabeceras <optional>, <variant> y <any> que le ayudan a manejar valores que podrían estar ausentes, contener uno de varios tipos o almacenar cualquier tipo en absoluto.
std::optional<T> representa un valor que puede o no existir: es perfecto para funciones que podrían fallar sin lanzar una excepción:
#include <iostream>
#include <optional>
std::optional<int> findIndex(const std::string& str, char c) {
for (size_t i = 0; i < str.size(); ++i) {
if (str[i] == c) return i;
}
return std::nullopt; // Sin valor
}
int main() {
auto result = findIndex("hello", 'l');
if (result.has_value()) {
std::cout << "Found at: " << *result << "\n"; // 2
}
}std::variant<Types...> es una unión segura en cuanto a tipos que contiene exactamente uno de los tipos especificados en cualquier momento:
#include <iostream>
#include <variant>
#include <string>
int main() {
std::variant<int, double, std::string> data;
data = 42;
std::cout << std::get<int>(data) << "\n";
data = "hello";
if (std::holds_alternative<std::string>(data)) {
std::cout << std::get<std::string>(data) << "\n";
}
}std::any puede contener un valor de cualquier tipo, determinado en tiempo de ejecución. Use std::any_cast para recuperar el valor:
#include <iostream>
#include <any>
int main() {
std::any value = 10;
std::cout << std::any_cast<int>(value) << "\n";
value = std::string("text");
std::cout << std::any_cast<std::string>(value) << "\n";
}Elija std::optional para valores que pueden ser nulos, std::variant cuando conozca los tipos posibles en tiempo de compilación, y std::any solo cuando realmente necesite flexibilidad de tipos en tiempo de ejecución.
Desafío
FácilConstruyamos un sistema de configuración que demuestre el poder de los tipos de vocabulario modernos de C++. Crearás un administrador de configuraciones flexible que gestione valores que podrían estar ausentes, contener distintos tipos o necesitar flexibilidad de tipos en tiempo de ejecución.
Organizarás tu código en tres archivos:
ConfigTypes.h: Define los tipos de valores de configuración y las funciones auxiliares.Crea una función llamada
parseValueque reciba una cadena e intente analizarla como un entero. Si la cadena representa un entero válido, devuelve el entero envuelto enstd::optional. Si el análisis falla (la cadena contiene caracteres no numéricos), devuelvestd::nullopt. Para simplificar, considera válida una cadena si solo contiene dígitos (y opcionalmente un signo menos inicial).Crea también un alias de tipo llamado
Settingusandostd::variantque pueda contener unint, undoubleo unstd::string.Por último, crea una función llamada
describeSettingque reciba unSettingy devuelva una cadena que describa qué tipo contiene y su valor con este formato:- Para int:
Integer: [value] - Para double:
Double: [value] - Para string:
String: [value]
- Para int:
DynamicStore.h: Crea un almacén sencillo de pares clave-valor usandostd::any.Define una clase
DynamicStoreque pueda almacenar valores de cualquier tipo. Debe tener:- Un método
setque reciba una clave de tipo cadena y un valorstd::any, y los almacene internamente (usa unstd::map) - Un método
getque reciba una clave y devuelva el valorstd::any(devuelve unstd::anyvacío si la clave no existe) - Un método
hasKeyque devuelva true si la clave existe
- Un método
main.cpp: Lee tres entradas:- Una cadena que podría ser un número (para probar
std::optional) - Un indicador de tipo:
int,doubleostring - Un valor correspondiente a ese tipo
Demuestra los tres tipos de vocabulario:
Primero, usa tu función
parseValuecon la primera entrada. Si contiene un valor, imprimeParsed: [value]. Si está vacía, imprimeParse failed.Segundo, crea una variante
Settingsegún el indicador de tipo. Si el tipo esint, almacena el valor como un entero. Si esdouble, almacénalo como un double. Si esstring, almacénalo como una cadena. Después, llama adescribeSettinge imprime el resultado.Tercero, crea un
DynamicStore, almacena la cadena"config_loaded"bajo la clave"status", después recupérala e imprime:Status: [value]. Usastd::any_cast<std::string>para extraer el valor.- Una cadena que podría ser un número (para probar
Por ejemplo, con las entradas 42, int y 100:
Parsed: 42
Integer: 100
Status: config_loadedCon las entradas hello, double y 3.14:
Parse failed
Double: 3.14
Status: config_loadedCon las entradas -15, string y username:
Parsed: -15
String: username
Status: config_loadedRecuerda incluir los encabezados adecuados: <optional>, <variant>, <any>, <string> y <map> donde sea necesario. Usa std::holds_alternative y std::get para trabajar con tu variante, y has_value() o una conversión booleana directa para comprobar tu optional.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"
int main() {
// Lee tres entradas
std::string input1; // String que podría ser un número (para probar std::optional)
std::string typeIndicator; // Indicador de tipo: int, double, o string
std::string value; // Valor correspondiente a ese tipo
std::cin >> input1;
std::cin >> typeIndicator;
std::cin >> value;
// TODO: Parte 1 - Probar std::optional con parseValue
// Usa la función parseValue con input1
// Si contiene un valor, imprime "Parsed: [value]"
// Si está vacío, imprime "Parse failed"
// TODO: Parte 2 - Probar std::variant con Setting
// Crea una variante Setting basada en typeIndicator
// Si el tipo es "int", almacena el valor como entero
// Si el tipo es "double", almacena el valor como double
// Si el tipo es "string", almacena el valor como string
// Luego llama a describeSetting e imprime el resultado
// TODO: Parte 3 - Probar std::any con DynamicStore
// Crea un DynamicStore
// Almacena el string "config_loaded" bajo la clave "status"
// Recupéralo e imprime: "Status: [value]"
// Usa std::any_cast<std::string> para extraer el valor
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico12Características de C++ moderno
Semántica de movimiento y RvaluesReenvío perfectoExpresiones Lambda en profundidadstd::function y std::bindconstexpr y constevalBindings estructuradosoptional, variant, anyPractica por tu cuenta: Compilador de C++ online