Menu
Coddy logo textTech

optional, variant, any

Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 89 из 104.

Современный C++ предоставляет три типа-словаря в заголовочных файлах <optional>, <variant> и <any>, которые помогают обрабатывать значения, которые могут отсутствовать, содержать один из нескольких типов или хранить любой тип вообще.

std::optional<T> представляет значение, которое может существовать или не существовать — идеально подходит для функций, которые могут завершиться неудачей без генерации исключения:

#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;  // Значение отсутствует
}

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

std::variant<Types...> — это типобезопасное объединение (union), которое в любой момент времени содержит ровно один из указанных типов:

#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 может хранить значение любого типа, определяемого во время выполнения. Используйте std::any_cast для извлечения значения:

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

Выбирайте std::optional для значений, которые могут отсутствовать, std::variant, когда вы знаете возможные типы на этапе компиляции, и std::any только тогда, когда вам действительно нужна гибкость типов во время выполнения.

challenge icon

Задание

Легко

Давайте создадим систему конфигурации, которая демонстрирует возможности современных словарных типов C++. Вы создадите гибкий менеджер настроек, который обрабатывает значения, которые могут отсутствовать, могут иметь разные типы или требуют гибкости типов во время выполнения.

Вы организуете свой код в трех файлах:

  • ConfigTypes.h: Определите типы значений конфигурации и вспомогательные функции.

    Создайте функцию с именем parseValue, которая принимает строку и пытается преобразовать ее в целое число. Если строка представляет собой допустимое целое число, верните это число, обернутое в std::optional. Если парсинг не удался (строка содержит нецифровые символы), верните std::nullopt. Для простоты считайте строку допустимой, если она содержит только цифры (и, возможно, ведущий знак минуса).

    Также создайте псевдоним типа с именем Setting, используя std::variant, который может хранить либо int, либо double, либо std::string.

    Наконец, создайте функцию с именем describeSetting, которая принимает Setting и возвращает строку, описывающую тип, который он хранит, и его значение в следующем формате:

    • Для int: Integer: [value]
    • Для double: Double: [value]
    • Для string: String: [value]
  • DynamicStore.h: Создайте простое хранилище «ключ-значение», используя std::any.

    Определите класс DynamicStore, который может хранить значения любого типа. Он должен иметь:

    • Метод set, который принимает строковый ключ и значение std::any, сохраняя их внутри (используйте std::map)
    • Метод get, который принимает ключ и возвращает значение std::any (возвращает пустой std::any, если ключ не существует)
    • Метод hasKey, который возвращает true, если ключ существует
  • main.cpp: Считайте три входных значения:
    1. Строка, которая может быть числом (для тестирования std::optional)
    2. Индикатор типа: int, double или string
    3. Значение, соответствующее этому типу

    Продемонстрируйте все три словарных типа:

    Во-первых, используйте функцию parseValue с первым входным значением. Если она содержит значение, выведите Parsed: [value]. Если она пуста, выведите Parse failed.

    Во-вторых, создайте вариант Setting на основе индикатора типа. Если тип — int, сохраните значение как целое число. Если double, сохраните его как double. Если string, сохраните его как строку. Затем вызовите describeSetting и выведите результат.

    В-третьих, создайте DynamicStore, сохраните строку "config_loaded" под ключом "status", затем извлеките ее и выведите: Status: [value]. Используйте std::any_cast<std::string> для извлечения значения.

Например, при входных данных 42, int и 100:

Parsed: 42
Integer: 100
Status: config_loaded

При входных данных hello, double и 3.14:

Parse failed
Double: 3.14
Status: config_loaded

При входных данных -15, string и username:

Parsed: -15
String: username
Status: config_loaded

Не забудьте включить соответствующие заголовки: <optional>, <variant>, <any>, <string> и <map> там, где это необходимо. Используйте std::holds_alternative и std::get для работы с вашим вариантом, а также has_value() или прямое логическое преобразование для проверки вашего optional.

Шпаргалка

Современный C++ предоставляет три словарных типа для работы с гибкими значениями:

std::optional<T>

Представляет значение, которое может существовать или отсутствовать. Подключите <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
}

auto result = findIndex("hello", 'l');
if (result.has_value()) {
    std::cout << *result;  // Dereference to get value
}

std::variant<Types...>

Типобезопасное объединение (union), которое содержит ровно один из указанных типов. Подключите <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

Может содержать значение любого типа, определяемого во время выполнения. Подключите <any>:

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

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

Когда использовать: std::optional для обнуляемых (nullable) значений, std::variant, когда возможные типы известны на этапе компиляции, std::any для гибкости типов во время выполнения.

Попробуйте сами

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

int main() {
    // Считать три входных значения
    std::string input1;  // Строка, которая может быть числом (для тестирования std::optional)
    std::string typeIndicator;  // Индикатор типа: int, double или string
    std::string value;  // Значение, соответствующее этому типу
    
    std::cin >> input1;
    std::cin >> typeIndicator;
    std::cin >> value;
    
    // TODO: Часть 1 — Тестирование std::optional с помощью parseValue
    // Используйте функцию parseValue с input1
    // Если она содержит значение, выведите "Parsed: [value]"
    // Если она пуста, выведите "Parse failed"
    
    
    // TODO: Часть 2 — Тестирование std::variant с помощью Setting
    // Создайте вариант Setting на основе typeIndicator
    // Если тип — "int", сохраните значение как целое число
    // Если тип — "double", сохраните значение как число с плавающей запятой
    // Если тип — "string", сохраните значение как строку
    // Затем вызовите describeSetting и выведите результат
    
    
    // TODO: Часть 3 — Тестирование std::any с помощью DynamicStore
    // Создайте DynamicStore
    // Сохраните строку "config_loaded" под ключом "status"
    // Извлеките её и выведите: "Status: [value]"
    // Используйте std::any_cast<std::string> для извлечения значения
    
    
    return 0;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование