Menu
CoddyTech

optional, variant, any

Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 89 de 104.

El C++ moderno proporciona tres tipos de vocabulario en los encabezados <optional>, <variant> y <any> que te ayudan a gestionar valores que podrían estar ausentes, contener uno de varios tipos o almacenar cualquier tipo.

std::optional<T> representa un valor que puede existir o no; 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 cada 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. Usa 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";
}

Elige std::optional para valores que pueden ser nulos, std::variant cuando conozcas los tipos posibles en tiempo de compilación y std::any solo cuando realmente necesites flexibilidad de tipos en tiempo de ejecución.

challenge icon

Desafío

Fácil

Construyamos un sistema de configuración que demuestre el poder de los modernos tipos de vocabulario de C++. Crearás un gestor de configuraciones flexible que maneje 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 parseValue que reciba una cadena e intente analizarla como un entero. Si la cadena representa un entero válido, devuelve el entero incluido en std::optional. Si el análisis falla (la cadena contiene caracteres no numéricos), devuelve std::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 Setting usando std::variant que pueda contener un int, un double o un std::string.

    Por último, crea una función llamada describeSetting que reciba un Setting y 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]
  • DynamicStore.h: Crea un almacén sencillo de clave-valor usando std::any.

    Define una clase DynamicStore que pueda almacenar valores de cualquier tipo. Debe tener:

    • Un método set que reciba una clave de tipo cadena y un valor std::any, y los almacene internamente (usa un std::map)
    • Un método get que reciba una clave y devuelva el valor std::any (devuelve un std::any vacío si la clave no existe)
    • Un método hasKey que devuelva true si la clave existe
  • main.cpp: Lee tres entradas:
    1. Una cadena que podría ser un número (para probar std::optional)
    2. Un indicador de tipo: int, double o string
    3. Un valor correspondiente a ese tipo

    Demuestra los tres tipos de vocabulario:

    Primero, usa tu función parseValue con la primera entrada. Si contiene un valor, imprime Parsed: [value]. Si está vacía, imprime Parse failed.

    Después, crea una variante Setting basándote en el indicador de tipo. Si el tipo es int, almacena el valor como un entero. Si es double, almacénalo como un double. Si es string, almacénalo como una cadena. Después, llama a describeSetting e imprime el resultado.

    Por último, crea un DynamicStore, almacena la cadena "config_loaded" bajo la clave "status", después recupérala e imprime: Status: [value]. Usa std::any_cast<std::string> para extraer el valor.

Por ejemplo, con las entradas 42, int y 100:

Parsed: 42
Integer: 100
Status: config_loaded

Con las entradas hello, double y 3.14:

Parse failed
Double: 3.14
Status: config_loaded

Con las entradas -15, string y username:

Parsed: -15
String: username
Status: config_loaded

Recuerda incluir los encabezados correspondientes: <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;  // Cadena 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]"
    // If it's empty, print "Parse failed"
    
    
    // TODO: Parte 2 - Probar std::variant con Setting
    // Crea una variante Setting basada en typeIndicator
    // Si el tipo es "int", almacena value como entero
    // Si el tipo es "double", almacena value como double
    // Si el tipo es "string", almacena value como string
    // Luego llama a describeSetting e imprime el resultado
    
    
    // TODO: Parte 3 - Prueba std::any con DynamicStore
    // Crea un DynamicStore
    // Almacena la cadena "config_loaded" bajo la clave "status"
    // Recupéralo e imprime: "Status: [value]"
    // Usa std::any_cast<std::string> para extraer el valor
    
    
    return 0;
}
quiz iconPonte a prueba

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

Practica por tu cuenta: Compilador de C++ online