Menu
Coddy logo textTech

optional, variant, any

CoddyのC++ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 89/104。

モダンなC++は、<optional><variant>、および<any>ヘッダーにおいて、値が存在しない場合や、複数の型のうちの1つを保持する場合、あるいは任意の型を格納する場合の処理に役立つ3つのボキャブラリ型を提供しています。

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...> は、指定された型のうちのいずれか1つを常に保持する、型安全な共用体です:

#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++のボキャブラリー型(vocabulary types)の強力さを示す設定システムを構築しましょう。値が欠落している可能性があったり、異なる型を保持できたり、実行時の型の柔軟性が必要な設定を処理する、柔軟な設定マネージャーを作成します。

コードは以下の3つのファイルに分けて構成します:

  • ConfigTypes.h: 設定値の型とヘルパー関数を定義します。

    文字列を受け取り、それを整数としてパースしようとする parseValue という関数を作成してください。文字列が有効な整数を表している場合は、std::optional でラップした整数を返します。パースに失敗した場合(文字列に数字以外の文字が含まれている場合)は、std::nullopt を返します。簡単にするために、数字のみ(およびオプションで先頭のマイナス記号)が含まれている場合に文字列を有効とみなしてください。

    また、std::variant を使用して、intdouble、または std::string のいずれかを保持できる Setting という型エイリアスを作成してください。

    最後に、Setting を受け取り、それが保持している型とその値を以下の形式で説明する文字列を返す describeSetting という関数を作成してください:

    • intの場合: Integer: [value]
    • doubleの場合: Double: [value]
    • stringの場合: String: [value]
  • DynamicStore.h: std::any を使用したシンプルなキー・バリューストアを作成します。

    任意の型の値を保存できる DynamicStore クラスを定義してください。以下のものを含める必要があります:

    • 文字列のキーと std::any の値を受け取り、それらを内部に保存する set メソッド(std::map を使用してください)
    • キーを受け取り、std::any の値を返す get メソッド(キーが存在しない場合は空の std::any を返します)
    • キーが存在する場合に true を返す hasKey メソッド
  • main.cpp: 3つの入力を読み込みます:
    1. 数値である可能性のある文字列(std::optional のテスト用)
    2. 型インジケーター:intdouble、または string
    3. その型に対応する値

    3つのボキャブラリー型すべてを実演してください:

    まず、最初の入力に対して parseValue 関数を使用します。値が含まれている場合は、Parsed: [value] と出力します。空の場合は、Parse failed と出力します。

    次に、型インジケーターに基づいて Setting バリアントを作成します。型が int の場合は値を整数として保存し、double の場合は double として、string の場合は文字列として保存します。その後、describeSetting を呼び出して結果を出力します。

    3番目に、DynamicStore を作成し、キー "status" の下に文字列 "config_loaded" を保存します。その後、それを取り出して Status: [value] と出力します。値を取り出すには std::any_cast<std::string> を使用してください。

例えば、入力が 42int100 の場合:

Parsed: 42
Integer: 100
Status: config_loaded

入力が hellodouble3.14 の場合:

Parse failed
Double: 3.14
Status: config_loaded

入力が -15stringusername の場合:

Parsed: -15
String: username
Status: config_loaded

必要に応じて、適切なヘッダー <optional><variant><any><string>、および <map> を含めることを忘れないでください。バリアントを操作するには std::holds_alternativestd::get を使用し、オプショナルをチェックするには has_value() または直接のブール変換を使用してください。

チートシート

モダンなC++は、柔軟な値を扱うための3つのボキャブラリ型を提供しています:

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;  // 値なし
}

auto result = findIndex("hello", 'l');
if (result.has_value()) {
    std::cout << *result;  // デリファレンスして値を取得
}

std::variant<Types...>

指定された型のうち、正確に1つを保持する型安全な共用体です。<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、可能な型がコンパイル時にわかっている場合はstd::variant、実行時の型の柔軟性が必要な場合はstd::anyを使用します。

自分で試してみよう

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

int main() {
    // 3つの入力を読み込む
    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 - parseValue を使用して std::optional をテストする
    // input1 に対して parseValue 関数を使用する
    // 値が含まれている場合は、"Parsed: [value]" と出力する
    // 空の場合は、"Parse failed" と出力する
    
    
    // TODO: パート 2 - Setting を使用して std::variant をテストする
    // typeIndicator に基づいて Setting バリアントを作成する
    // 型が "int" の場合は、値を整数として格納する
    // 型が "double" の場合は、値を double として格納する
    // 型が "string" の場合は、値を文字列として格納する
    // その後、describeSetting を呼び出して結果を出力する
    
    
    // TODO: パート 3 - DynamicStore を使用して std::any をテストする
    // DynamicStore を作成する
    // キー "status" の下に文字列 "config_loaded" を格納する
    // それを取り出して、"Status: [value]" と出力する
    // 値を抽出するために std::any_cast<std::string> を使用する
    
    
    return 0;
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン