Menu
CoddyTech

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...> — это типобезопасное объединение, которое в любой момент содержит ровно один из указанных типов:

#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 для допускающих значение null, 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 — сохраните его как число с плавающей точкой. Если 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.

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

#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]"
    // If it's empty, print "Parse failed"
    
    
    // TODO: Часть 2 - Тест std::variant с Setting
    // Создать вариант Setting на основе typeIndicator
    // Если тип — "int", сохранить value как целое число
    // Если тип — "double", сохранить value как double
    // Если тип — "string", сохранить value как string
    // Затем вызовите describeSetting и выведите результат
    
    
    // TODO: Часть 3 - Тест std::any с DynamicStore
    // Создайте DynamicStore
    // Сохраните строку "config_loaded" под ключом "status"
    // Извлеките его и выведите: "Status: [value]"
    // Используйте std::any_cast<std::string> для извлечения значения
    
    
    return 0;
}
quiz iconПроверьте себя

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

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

Потренируйтесь самостоятельно: Онлайн-компилятор C++