Menu
Coddy logo textTech

optional, variant, any

Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 89 di 104.

Il C++ moderno fornisce tre tipi del vocabolario negli header <optional>, <variant> e <any> che ti aiutano a gestire valori che potrebbero essere assenti, contenere uno di diversi tipi o memorizzare qualsiasi tipo.

std::optional<T> rappresenta un valore che potrebbe esistere oppure no: è perfetto per le funzioni che potrebbero non riuscire senza generare un'eccezione:

#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;  // Nessun valore
}
int main() {
    auto result = findIndex("hello", 'l');
    if (result.has_value()) {
        std::cout << "Found at: " << *result << "\n";  // 2
    }
}

std::variant<Types...> è un'unione con sicurezza dei tipi che contiene esattamente uno dei tipi specificati in qualsiasi 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 può contenere un valore di qualsiasi tipo, determinato in fase di esecuzione. Usa std::any_cast per recuperare il valore:

#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";
}

Scegli std::optional per i valori nullabili, std::variant quando conosci i possibili tipi in fase di compilazione e std::any solo quando hai davvero bisogno di flessibilità dei tipi a runtime.

challenge icon

Sfida

Facile

Creiamo un sistema di configurazione che dimostri la potenza dei moderni tipi vocabolo di C++. Creerai un gestore flessibile delle impostazioni, in grado di gestire valori che potrebbero essere assenti, contenere tipi diversi o richiedere flessibilità di tipo in fase di esecuzione.

Organizzerai il codice in tre file:

  • ConfigTypes.h: definisci i tipi dei valori di configurazione e le funzioni di supporto.

    Crea una funzione chiamata parseValue che accetta una stringa e prova a convertirla in un intero. Se la stringa rappresenta un intero valido, restituisci l'intero racchiuso in std::optional. Se la conversione non riesce (la stringa contiene caratteri non numerici), restituisci std::nullopt. Per semplicità, considera valida una stringa se contiene solo cifre (e, facoltativamente, un segno meno iniziale).

    Crea anche un alias di tipo chiamato Setting usando std::variant, che possa contenere un int, un double oppure una std::string.

    Infine, crea una funzione chiamata describeSetting che accetta un Setting e restituisce una stringa che descrive il tipo contenuto e il suo valore nel formato seguente:

    • Per int: Integer: [value]
    • Per double: Double: [value]
    • Per string: String: [value]
  • DynamicStore.h: crea un semplice archivio chiave-valore usando std::any.

    Definisci una classe DynamicStore che possa memorizzare valori di qualsiasi tipo. Dovrebbe avere:

    • Un metodo set che accetta una chiave stringa e un valore std::any, memorizzandoli internamente (usa una std::map)
    • Un metodo get che accetta una chiave e restituisce il valore std::any (restituisci una std::any vuota se la chiave non esiste)
    • Un metodo hasKey che restituisce true se la chiave esiste
  • main.cpp: leggi tre valori in input:
    1. Una stringa che potrebbe essere un numero (per provare std::optional)
    2. Un indicatore di tipo: int, double oppure string
    3. Un valore corrispondente a quel tipo

    Mostra tutti e tre i tipi vocabolo:

    Per prima cosa, usa la funzione parseValue con il primo valore in input. Se contiene un valore, stampa Parsed: [value]. Se è vuoto, stampa Parse failed.

    In secondo luogo, crea una variante Setting in base all'indicatore di tipo. Se il tipo è int, memorizza il valore come intero. Se è double, memorizza il valore come double. Se è string, memorizza il valore come stringa. Poi chiama describeSetting e stampa il risultato.

    Infine, crea un DynamicStore, memorizza la stringa "config_loaded" sotto la chiave "status", poi recuperala e stampa: Status: [value]. Usa std::any_cast<std::string> per estrarre il valore.

Per esempio, con i valori in input 42, int e 100:

Parsed: 42
Integer: 100
Status: config_loaded

Con i valori in input hello, double e 3.14:

Parse failed
Double: 3.14
Status: config_loaded

Con i valori in input -15, string e username:

Parsed: -15
String: username
Status: config_loaded

Ricordati di includere gli header appropriati: <optional>, <variant>, <any>, <string> e <map>, dove necessario. Usa std::holds_alternative e std::get per lavorare con la variante, e has_value() o la conversione booleana diretta per controllare l'optional.

Provalo tu

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

int main() {
    // Leggi tre input
    std::string input1;  // Stringa che potrebbe essere un numero (per testare std::optional)
    std::string typeIndicator;  // Indicatore del tipo: int, double o string
    std::string value;  // Valore corrispondente a quel tipo
    
    std::cin >> input1;
    std::cin >> typeIndicator;
    std::cin >> value;
    
    // TODO: Parte 1 - Testa std::optional con parseValue
    // Usa la funzione parseValue con input1
    // Se contiene un valore, stampa "Parsed: [value]"
    // Se è vuoto, stampa "Parse failed"
    
    
    // TODO: Parte 2 - Testa std::variant con Setting
    // Crea una variante Setting in base a typeIndicator
    // Se il tipo è "int", memorizza il valore come intero
    // Se il tipo è "double", memorizza il valore come double
    // Se il tipo è "string", memorizza il valore come stringa
    // Poi chiama describeSetting e stampa il risultato
    
    
    // TODO: Parte 3 - Testa std::any con DynamicStore
    // Crea un DynamicStore
    // Memorizza la stringa "config_loaded" con la chiave "status"
    // Recuperala e stampa: "Status: [value]"
    // Usa std::any_cast<std::string> per estrarre il valore
    
    
    return 0;
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C++ online