Menu
Coddy logo textTech

optional, variant, any

Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 89 sur 104.

Le C++ moderne fournit trois types de vocabulaire dans les en-têtes <optional>, <variant> et <any> qui vous aident à gérer des valeurs qui pourraient être absentes, contenir l'un de plusieurs types ou stocker n'importe quel type.

std::optional<T> représente une valeur qui peut ou non exister - parfait pour les fonctions qui pourraient échouer sans lever d'exception :

#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;  // Aucune valeur
}

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

std::variant<Types...> est une union de types sécurisée (type-safe union) qui contient exactement l'un des types spécifiés à tout moment :

#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 peut contenir une valeur de n'importe quel type, déterminée au moment de l'exécution. Utilisez std::any_cast pour récupérer la valeur :

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

Choisissez std::optional pour les valeurs nullables, std::variant lorsque vous connaissez les types possibles au moment de la compilation, et std::any uniquement lorsque vous avez réellement besoin de flexibilité de type à l'exécution.

challenge icon

Défi

Facile

Construisons un système de configuration qui démontre la puissance des types de vocabulaire du C++ moderne. Vous allez créer un gestionnaire de paramètres flexible qui gère des valeurs pouvant être absentes, pouvant contenir différents types, ou nécessitant une flexibilité de type à l'exécution.

Vous organiserez votre code sur trois fichiers :

  • ConfigTypes.h : Définissez vos types de valeurs de configuration et vos fonctions utilitaires.

    Créez une fonction nommée parseValue qui prend une chaîne de caractères et tente de l'analyser comme un entier. Si la chaîne représente un entier valide, retournez l'entier enveloppé dans un std::optional. Si l'analyse échoue (la chaîne contient des caractères non numériques), retournez std::nullopt. Pour simplifier, considérez une chaîne comme valide si elle ne contient que des chiffres (et éventuellement un signe moins au début).

    Créez également un alias de type nommé Setting utilisant std::variant pouvant contenir soit un int, un double, ou un std::string.

    Enfin, créez une fonction nommée describeSetting qui prend un Setting et retourne une chaîne décrivant le type qu'il contient et sa valeur dans ce format :

    • Pour int : Integer: [value]
    • Pour double : Double: [value]
    • Pour string : String: [value]
  • DynamicStore.h : Créez un magasin clé-valeur simple utilisant std::any.

    Définissez une classe DynamicStore qui peut stocker des valeurs de n'importe quel type. Elle doit avoir :

    • Une méthode set qui prend une clé de type chaîne et une valeur std::any, en les stockant en interne (utilisez un std::map)
    • Une méthode get qui prend une clé et retourne la valeur std::any (retournez un std::any vide si la clé n'existe pas)
    • Une méthode hasKey qui retourne true si la clé existe
  • main.cpp : Lisez trois entrées :
    1. Une chaîne qui pourrait être un nombre (pour tester std::optional)
    2. Un indicateur de type : int, double, ou string
    3. Une valeur correspondant à ce type

    Démontrez les trois types de vocabulaire :

    Premièrement, utilisez votre fonction parseValue avec la première entrée. Si elle contient une valeur, affichez Parsed: [value]. Si elle est vide, affichez Parse failed.

    Deuxièmement, créez un variant Setting basé sur l'indicateur de type. Si le type est int, stockez la valeur en tant qu'entier. Si c'est double, stockez-la en tant que double. Si c'est string, stockez-la en tant que chaîne. Appelez ensuite describeSetting et affichez le résultat.

    Troisièmement, créez un DynamicStore, stockez la chaîne "config_loaded" sous la clé "status", puis récupérez-la et affichez : Status: [value]. Utilisez std::any_cast<std::string> pour extraire la valeur.

Par exemple, avec les entrées 42, int, et 100 :

Parsed: 42
Integer: 100
Status: config_loaded

Avec les entrées hello, double, et 3.14 :

Parse failed
Double: 3.14
Status: config_loaded

Avec les entrées -15, string, et username :

Parsed: -15
String: username
Status: config_loaded

N'oubliez pas d'inclure les en-têtes appropriés : <optional>, <variant>, <any>, <string>, et <map> là où c'est nécessaire. Utilisez std::holds_alternative et std::get pour travailler avec votre variant, et has_value() ou une conversion booléenne directe pour vérifier votre optionnel.

Aide-mémoire

Le C++ moderne propose trois types de vocabulaire pour gérer les valeurs flexibles :

std::optional<T>

Représente une valeur qui peut exister ou non. Incluez <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;  // Aucune valeur
}

auto result = findIndex("hello", 'l');
if (result.has_value()) {
    std::cout << *result;  // Déréférencer pour obtenir la valeur
}

std::variant<Types...>

Une union de types sécurisée qui contient exactement l'un des types spécifiés. Incluez <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

Peut contenir une valeur de n'importe quel type, déterminée à l'exécution. Incluez <any> :

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

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

Quand les utiliser : std::optional pour les valeurs pouvant être nulles, std::variant lorsque les types possibles sont connus à la compilation, std::any pour une flexibilité de type à l'exécution.

Essayez vous-même

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

int main() {
    // Lire trois entrées
    std::string input1;  // Chaîne qui pourrait être un nombre (pour tester std::optional)
    std::string typeIndicator;  // Indicateur de type : int, double, ou string
    std::string value;  // Valeur correspondant à ce type
    
    std::cin >> input1;
    std::cin >> typeIndicator;
    std::cin >> value;
    
    // TODO: Partie 1 - Tester std::optional avec parseValue
    // Utiliser la fonction parseValue avec input1
    // S'il contient une valeur, afficher "Parsed: [value]"
    // S'il est vide, afficher "Parse failed"
    
    
    // TODO: Partie 2 - Tester std::variant avec Setting
    // Créer un variant Setting basé sur typeIndicator
    // Si le type est "int", stocker la valeur en tant qu'entier
    // Si le type est "double", stocker la valeur en tant que double
    // Si le type est "string", stocker la valeur en tant que chaîne
    // Ensuite, appeler describeSetting et afficher le résultat
    
    
    // TODO: Partie 3 - Tester std::any avec DynamicStore
    // Créer un DynamicStore
    // Stocker la chaîne "config_loaded" sous la clé "status"
    // La récupérer et afficher : "Status: [value]"
    // Utiliser std::any_cast<std::string> pour extraire la valeur
    
    
    return 0;
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet