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 segura quanto ao tipo (type-safe) 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 construir um sistema de configuração que demonstra o poder dos tipos de vocabulário do C++ moderno. Você criará um gerenciador de configurações flexível que lida com valores que podem estar ausentes, podem conter tipos diferentes ou precisam de flexibilidade de tipo 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 recebe uma string e tenta analisá-la como um número inteiro. Se a string representar um inteiro válido, retorne o inteiro envolvido 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 no início).

    Também crie um apelido de tipo (type alias) chamado Setting usando std::variant que pode conter um int, um double ou uma std::string.

    Por fim, crie uma função chamada describeSetting que recebe um Setting e retorna 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 de chave-valor simples usando std::any.

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

    • Um método set que recebe uma chave string e um valor std::any, armazenando-os internamente (use um std::map)
    • Um método get que recebe uma chave e retorna o valor std::any (retorne um std::any vazio se a chave não existir)
    • Um método hasKey que retorna 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 todos 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 baseada 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 conversão booleana direta para verificar seu opcional.

Folha de consulta

O C++ moderno fornece três tipos de vocabulário para lidar com valores flexíveis:

std::optional<T>

Representa um valor que pode ou não existir. Inclua <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;  // Nenhum valor
}

auto result = findIndex("hello", 'l');
if (result.has_value()) {
    std::cout << *result;  // Desreferenciar para obter o valor
}

std::variant<Types...>

Uma união segura quanto ao tipo (type-safe union) que contém exatamente um dos tipos especificados. Inclua <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

Pode conter um valor de qualquer tipo, determinado em tempo de execução. Inclua <any>:

std::any value = 10;
std::cout << std::any_cast<int>(value);

value = std::string("text");
std::cout << std::any_cast<std::string>(value);

Quando usar: std::optional para valores anuláveis, std::variant quando os tipos possíveis são conhecidos em tempo de compilação, std::any para flexibilidade de tipo em tempo de execução.

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