Menu
Coddy logo textTech

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.

challenge icon

Desafío

Fácil

Vamos 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 parseValue que tome un string e intente analizarlo como un entero. Si el string representa un entero válido, devuelve el entero envuelto en un std::optional. Si el análisis falla (el string contiene caracteres no numéricos), devuelve std::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 Setting usando std::variant que pueda contener un int, un double o un std::string.

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

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

    • Un método set que tome una clave string y un valor std::any, almacenándolos internamente (usa un std::map)
    • Un método get que tome 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. Un string 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ío, imprime Parse failed.

    Segundo, crea una variante Setting basada 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 un string. Luego llama a describeSetting e imprime el resultado.

    Tercero, crea un DynamicStore, almacena el string "config_loaded" bajo la clave "status", luego recupéralo 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 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;
}
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