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 を選択してください。
チャレンジ
簡単モダンなC++のボキャブラリー型(vocabulary types)の強力さを示す設定システムを構築しましょう。値が欠落している可能性があったり、異なる型を保持できたり、実行時の型の柔軟性が必要な設定を処理する、柔軟な設定マネージャーを作成します。
コードは以下の3つのファイルに分けて構成します:
ConfigTypes.h: 設定値の型とヘルパー関数を定義します。文字列を受け取り、それを整数としてパースしようとする
parseValueという関数を作成してください。文字列が有効な整数を表している場合は、std::optionalでラップした整数を返します。パースに失敗した場合(文字列に数字以外の文字が含まれている場合)は、std::nulloptを返します。簡単にするために、数字のみ(およびオプションで先頭のマイナス記号)が含まれている場合に文字列を有効とみなしてください。また、
std::variantを使用して、int、double、またはstd::stringのいずれかを保持できるSettingという型エイリアスを作成してください。最後に、
Settingを受け取り、それが保持している型とその値を以下の形式で説明する文字列を返すdescribeSettingという関数を作成してください:- intの場合:
Integer: [value] - doubleの場合:
Double: [value] - stringの場合:
String: [value]
- intの場合:
DynamicStore.h:std::anyを使用したシンプルなキー・バリューストアを作成します。任意の型の値を保存できる
DynamicStoreクラスを定義してください。以下のものを含める必要があります:- 文字列のキーと
std::anyの値を受け取り、それらを内部に保存するsetメソッド(std::mapを使用してください) - キーを受け取り、
std::anyの値を返すgetメソッド(キーが存在しない場合は空のstd::anyを返します) - キーが存在する場合に true を返す
hasKeyメソッド
- 文字列のキーと
main.cpp: 3つの入力を読み込みます:- 数値である可能性のある文字列(
std::optionalのテスト用) - 型インジケーター:
int、double、またはstring - その型に対応する値
3つのボキャブラリー型すべてを実演してください:
まず、最初の入力に対して
parseValue関数を使用します。値が含まれている場合は、Parsed: [value]と出力します。空の場合は、Parse failedと出力します。次に、型インジケーターに基づいて
Settingバリアントを作成します。型がintの場合は値を整数として保存し、doubleの場合は double として、stringの場合は文字列として保存します。その後、describeSettingを呼び出して結果を出力します。3番目に、
DynamicStoreを作成し、キー"status"の下に文字列"config_loaded"を保存します。その後、それを取り出して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() または直接のブール変換を使用してください。
チートシート
モダンな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;
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
3コンストラクタとデストラクタ
デフォルトコンストラクタ引数付きコンストラクタコピーコンストラクタムーブコンストラクタコンストラクタ初期化リスト委譲コンストラクタデストラクタ詳解Rule of Three / Five / Zeroまとめ - Stringクラス6演算子オーバーロード
演算子オーバーロード入門算術演算子のオーバーロード比較演算子のオーバーロードストリーム演算子代入演算子のオーバーロード[] と () 演算子のオーバーロード型変換演算子まとめ:Matrix クラス12モダンC++の機能
ムーブセマンティクスと右辺値完全転送ラムダ式の詳細std::functionとstd::bindconstexprとconsteval構造化束縛optional, variant, any