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ácilVamos a construir un sistema de configuración que demuestre el poder de los tipos de vocabulario del C++ moderno. Crearás un gestor de configuraciones flexible que maneje valores que podrían estar ausentes, que pueden contener diferentes tipos o que necesiten flexibilidad de tipos en tiempo de ejecución.
Organizarás tu código en tres archivos:
ConfigTypes.h: Define tus tipos de valores de configuración y funciones auxiliares.Crea una función llamada
parseValueque tome un string e intente analizarlo como un entero. Si el string representa un entero válido, devuelve el entero envuelto en unstd::optional. Si el análisis falla (el string contiene caracteres no numéricos), devuelvestd::nullopt. Para simplificar, considera un string válido si solo contiene dígitos (y opcionalmente un signo menos al principio).También crea un alias de tipo llamado
Settingusandostd::variantque pueda contener unint, undoubleo unstd::string.Finalmente, crea una función llamada
describeSettingque tome unSettingy devuelva un string describiendo qué tipo contiene y su valor en este formato:- Para int:
Integer: [value] - Para double:
Double: [value] - Para string:
String: [value]
- Para int:
DynamicStore.h: Crea un almacén de clave-valor simple usandostd::any.Define una clase
DynamicStoreque pueda almacenar valores de cualquier tipo. Debe tener:- Un método
setque tome una clave string y un valorstd::any, almacenándolos internamente (usa unstd::map) - Un método
getque tome 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:- Un string 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ío, imprimeParse failed.Segundo, crea una variante
Settingbasada en 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 un string. Luego llama adescribeSettinge imprime el resultado.Tercero, crea un
DynamicStore, almacena el string"config_loaded"bajo la clave"status", luego recupéralo e imprime:Status: [value]. Usastd::any_cast<std::string>para extraer el valor.- Un string 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 las cabeceras apropiadas: <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 la conversión booleana directa para verificar tu opcional.
Hoja de referencia
El C++ moderno proporciona tres tipos de vocabulario para manejar valores flexibles:
std::optional<T>
Representa un valor que puede o no existir. Incluya <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
}
auto result = findIndex("hello", 'l');
if (result.has_value()) {
std::cout << *result; // Desreferenciar para obtener el valor
}std::variant<Types...>
Una unión con seguridad de tipos que contiene exactamente uno de los tipos especificados. Incluya <variant>:
std::variant<int, double, std::string> data;
data = 42;
std::cout << std::get<int>(data);
data = "hello";
if (std::holds_alternative<std::string>(data)) {
std::cout << std::get<std::string>(data);
}std::any
Puede contener un valor de cualquier tipo, determinado en tiempo de ejecución. Incluya <any>:
std::any value = 10;
std::cout << std::any_cast<int>(value);
value = std::string("text");
std::cout << std::any_cast<std::string>(value);Cuándo usar: std::optional para valores que pueden ser nulos, std::variant cuando los tipos posibles se conocen en tiempo de compilación, std::any para flexibilidad de tipos en tiempo de ejecución.
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, any