Menu
Coddy logo textTech

optional, variant, any

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 89 de 104.

O C++ moderno fornece três tipos de vocabulário nos cabeçalhos <optional>, <variant> e <any> que ajudam você a lidar com valores que podem estar ausentes, conter um de vários tipos ou armazenar qualquer tipo.

std::optional<T> representa um valor que pode ou não existir - perfeito para funções que podem falhar sem lançar uma exceção:

#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;  // No value
}

int main() {
    auto result = findIndex("hello", 'l');
    if (result.has_value()) {
        std::cout << "Found at: " << *result << "\n";  // 2
    }
}

std::variant<Types...> é uma união com segurança de tipos que contém exatamente um dos tipos especificados a qualquer 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 pode conter um valor de qualquer tipo, determinado em tempo de execução. Use std::any_cast para recuperar o 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";
}

Escolha std::optional para valores anuláveis, std::variant quando você conhece os tipos possíveis em tempo de compilação, e std::any apenas quando você realmente precisar de flexibilidade de tipo em tempo de execução.

challenge icon

Desafio

Fácil

Vamos criar um sistema de configuração que demonstre o poder dos modernos tipos de vocabulário do C++. Você criará um gerenciador de configurações flexível que lida com valores que podem estar ausentes, conter tipos diferentes ou precisar de flexibilidade de tipos em tempo de execução.

Você organizará seu código em três arquivos:

  • ConfigTypes.h: Defina seus tipos de valores de configuração e funções auxiliares.

    Crie uma função chamada parseValue que receba uma string e tente analisá-la como um inteiro. Se a string representar um inteiro válido, retorne o inteiro encapsulado em std::optional. Se a análise falhar (a string contiver caracteres não numéricos), retorne std::nullopt. Para simplificar, considere uma string válida se ela contiver apenas dígitos (e, opcionalmente, um sinal de menos inicial).

    Crie também um alias de tipo chamado Setting usando std::variant, que possa conter um int, um double ou uma std::string.

    Por fim, crie uma função chamada describeSetting que receba um Setting e retorne uma string descrevendo qual tipo ele contém e seu valor neste formato:

    • Para int: Integer: [value]
    • Para double: Double: [value]
    • Para string: String: [value]
  • DynamicStore.h: Crie um armazenamento simples de chave-valor usando std::any.

    Defina uma classe DynamicStore que possa armazenar valores de qualquer tipo. Ela deve ter:

    • Um método set que receba uma chave string e um valor std::any, armazenando-os internamente (use um std::map)
    • Um método get que receba uma chave e retorne o valor std::any (retorne um std::any vazio se a chave não existir)
    • Um método hasKey que retorne true se a chave existir
  • main.cpp: Leia três entradas:
    1. Uma string que pode ser um número (para testar std::optional)
    2. Um indicador de tipo: int, double ou string
    3. Um valor correspondente a esse tipo

    Demonstre os três tipos de vocabulário:

    Primeiro, use sua função parseValue com a primeira entrada. Se ela contiver um valor, imprima Parsed: [value]. Se estiver vazia, imprima Parse failed.

    Segundo, crie uma variante Setting com base no indicador de tipo. Se o tipo for int, armazene o valor como um inteiro. Se for double, armazene-o como um double. Se for string, armazene-o como uma string. Em seguida, chame describeSetting e imprima o resultado.

    Terceiro, crie um DynamicStore, armazene a string "config_loaded" sob a chave "status", depois recupere-a e imprima: Status: [value]. Use std::any_cast<std::string> para extrair o valor.

Por exemplo, com as entradas 42, int e 100:

Parsed: 42
Integer: 100
Status: config_loaded

Com as entradas hello, double e 3.14:

Parse failed
Double: 3.14
Status: config_loaded

Com as entradas -15, string e username:

Parsed: -15
String: username
Status: config_loaded

Lembre-se de incluir os cabeçalhos apropriados: <optional>, <variant>, <any>, <string> e <map> onde necessário. Use std::holds_alternative e std::get para trabalhar com sua variante, e has_value() ou uma conversão booleana direta para verificar seu optional.

Experimente você mesmo

#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"

int main() {
    // Ler três entradas
    std::string input1;  // String que pode ser um número (para testar std::optional)
    std::string typeIndicator;  // Indicador de tipo: int, double, ou string
    std::string value;  // Valor correspondente a esse tipo
    
    std::cin >> input1;
    std::cin >> typeIndicator;
    std::cin >> value;
    
    // TODO: Parte 1 - Testar std::optional com parseValue
    // Usar a função parseValue com input1
    // Se contiver um valor, imprimir "Parsed: [value]"
    // Se estiver vazio, imprimir "Parse failed"
    
    
    // TODO: Parte 2 - Testar std::variant com Setting
    // Criar uma variante Setting baseada em typeIndicator
    // Se o tipo for "int", armazenar o valor como inteiro
    // Se o tipo for "double", armazenar o valor como double
    // Se o tipo for "string", armazenar o valor como string
    // Então chamar describeSetting e imprimir o resultado
    
    
    // TODO: Parte 3 - Testar std::any com DynamicStore
    // Criar um DynamicStore
    // Armazenar a string "config_loaded" sob a chave "status"
    // Recuperá-la e imprimir: "Status: [value]"
    // Usar std::any_cast<std::string> para extrair o valor
    
    
    return 0;
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos

Pratique por conta própria: Compilador de C++ online