通常、464
のオーバーロード パターンを使用します。 . 477
タイプセーフ共用体です。 A488
498
(C++17) は、その型の 1 つから 1 つの値を持ちます。507
ビジターを適用できます。まさにここで、非常に便利なオーバーロード パターンが登場します。
前回の投稿「パラメーター パックとフォールド式を使用したスマート トリック」で、ラムダを使用してオーバーロード セットを作成するためのスマートなトリックとして、オーバーロード パターンを紹介しました。通常、オーバーロード パターンは、512<が保持する値にアクセスするために使用されます。 /コード> .
ほとんどの開発者が 525
を知らないことは、私の C++ セミナーで知っています。 そして533
それでもユニオンを使用します。そこで、540
について簡単に思い出させてください。 および 557
.
567
std::variant は型安全な共用体です。 std::variant のインスタンスには、その型の 1 つからの値があります。値は、参照、C 配列、または void であってはなりません。 std::variant は、1 つの型を複数回持つことができます。デフォルトで初期化された std::variant は、最初の型で初期化されます。この場合、最初の型にはデフォルトのコンストラクターが必要です。 cppreference.com に基づいた例を次に示します。
// variant.cpp #include <variant> #include <string> int main(){ std::variant<int, float> v, w; v = 12; // (1) int i = std::get<int>(v); w = std::get<int>(v); // (2) w = std::get<0>(v); // (3) w = v; // (4) // std::get<double>(v); // (5) ERROR // std::get<3>(v); // (6) ERROR try{ std::get<float>(w); // (7) } catch (std::bad_variant_access&) {} std::variant<std::string> v("abc"); // (8) v = "def"; // (9) }
バリアント v と w の両方を定義します。これらは int 値と float 値を持つことができます。初期値は 0 です。v は 12 になります (1 行目)。 std::get
もちろん、571
についてはまだまだあります Bartlomiej Filipek による投稿「C++17 の std::variant について知っておくべきことすべて」をお読みください。
関数 586
のおかげで 、C++17 は 597
の要素を訪問する便利な方法を提供します .
600
古典的なデザイン パターンによるとビジター パターンのように聞こえるものは、実際にはバリアントのコンテナーの一種のビジターです。
std::visit を使用すると、ビジターをバリアントのコンテナーに適用できます。ビジターは呼び出し可能でなければなりません。 callable は、呼び出すことができるものです。典型的な callable は、関数、関数オブジェクト、またはラムダです。私の例ではラムダを使用しています。
// visitVariants.cpp #include <iostream> #include <vector> #include <typeinfo> #include <variant> int main(){ std::cout << '\n'; std::vector<std::variant<char, long, float, int, double, long long>> // 1 vecVariant = {5, '2', 5.4, 100ll, 2011l, 3.5f, 2017}; for (auto& v: vecVariant){ std::visit([](auto arg){std::cout << arg << " ";}, v); // 2 } std::cout << '\n'; for (auto& v: vecVariant){ std::visit([](auto arg){std::cout << typeid(arg).name() << " ";}, v); // 3 } std::cout << "\n\n"; }
(1) バリアントの std::vector を作成し、各バリアントを初期化します。各バリアントは、char、long、float、int、double、または long long 値を保持できます。バリアントのベクトルをトラバースしてラムダを適用するのは非常に簡単です (行 (2) と (3))。最初に現在の値を表示し (2)、次に typeid(arg).name( の呼び出しのおかげで) ) (3)、現在の値の型の文字列表現を取得します。
罰金?いいえ!。プログラム 612
で使用しました 一般的なラムダ。その結果、型の文字列表現は、gcc を使用してかなり判読できません:"626
". 正直なところ、特定のラムダをバリアントの各タイプに適用したいと思っています。今、オーバーロード パターンが私の助けになります。
過負荷パターン
オーバーロード パターンのおかげで、各型を読み取り可能な文字列で表示し、各値を適切な方法で表示できます。
// visitVariantsOverloadPattern.cpp #include <iostream> #include <vector> #include <typeinfo> #include <variant> #include <string> template<typename ... Ts> // (7) struct Overload : Ts ... { using Ts::operator() ...; }; template<class... Ts> Overload(Ts...) -> Overload<Ts...>; int main(){ std::cout << '\n'; std::vector<std::variant<char, long, float, int, double, long long>> // (1) vecVariant = {5, '2', 5.4, 100ll, 2011l, 3.5f, 2017}; auto TypeOfIntegral = Overload { // (2) [](char) { return "char"; }, [](int) { return "int"; }, [](unsigned int) { return "unsigned int"; }, [](long int) { return "long int"; }, [](long long int) { return "long long int"; }, [](auto) { return "unknown type"; }, }; for (auto v : vecVariant) { // (3) std::cout << std::visit(TypeOfIntegral, v) << '\n'; } std::cout << '\n'; std::vector<std::variant<std::vector<int>, double, std::string>> // (4) vecVariant2 = { 1.5, std::vector<int>{1, 2, 3, 4, 5}, "Hello "}; auto DisplayMe = Overload { // (5) [](std::vector<int>& myVec) { for (auto v: myVec) std::cout << v << " "; std::cout << '\n'; }, [](auto& arg) { std::cout << arg << '\n';}, }; for (auto v : vecVariant2) { // (6) std::visit(DisplayMe, v); } std::cout << '\n'; }
行 (1) は整数型を持つバリアントのベクトルを作成し、行 (4) は 639
を持つバリアントのベクトルを作成します。 ,646
、および 654
.
最初の変種 669
を続けましょう . TypeOfIntegral (2) は、いくつかの整数型に対して文字列表現を返すオーバーロード セットです。型がオーバーロード セットによって処理されない場合、文字列 "673
を返します。 ". 行 (3) では、各バリアントにオーバーロード セットを適用します683
698
を使用 .
2 番目のバリアント vecVariant2 (4) には、複合型があります。それらの値を表示するオーバーロード セット (5) を作成します。一般に、値を 705
にプッシュするだけです。 . 718
の場合 、範囲ベースの for ループを使用して値を 725
にプッシュします .
最後に、これがプログラムの出力です。
この例 (7) で使用されているオーバーロード パターンにいくつかの単語を追加したいと思います。前回の投稿「パラメーター パックとフォールド式を使用したスマート トリック`」で既に紹介しました。
template<typename ... Ts> // (1) struct Overload : Ts ... { using Ts::operator() ...; }; template<class... Ts> Overload(Ts...) -> Overload<Ts...>; // (2)
行 (1) は過負荷パターンで、行 (2) はその控除ガイドです。構造体 736
任意の数の基本クラスを持つことができます (741
)。各クラス 756
から派生します 呼び出し演算子 (768
.) 各基本クラスをそのスコープに。基本クラスには、オーバーロードされた呼び出し演算子 (Ts::operator()) が必要です。ラムダは、この呼び出し演算子を提供します。次の例は、可能な限り単純です。
#include <variant> template<typename ... Ts> struct Overload : Ts ... { using Ts::operator() ...; }; template<class... Ts> Overload(Ts...) -> Overload<Ts...>; int main(){ std::variant<char, int, float> var = 2017; auto TypeOfIntegral = Overload { // (1) [](char) { return "char"; }, [](int) { return "int"; }, [](auto) { return "unknown type"; }, }; }
この例を C++ Insights で使用すると、魔法が透過的になります。まず、(1) を呼び出すと、完全に特殊化されたクラス テンプレートが作成されます。
次に、777
などのオーバーロード パターンで使用されるラムダ 関数オブジェクトを作成します。この場合、コンパイラは関数オブジェクトに 781
という名前を付けます。 .
自動生成タイプを調べると、少なくとも 1 つの興味深い点が示されます。 __lambda_15_9 の呼び出し演算子は 790
に対してオーバーロードされています
控除ガイド (804
) (2 行目) は C++17 でのみ必要です。演繹ガイドは、コンストラクター外の引数テンプレート パラメーターを作成する方法をコンパイラーに指示します。 C++20 では、テンプレートを自動的に推測できます。
次は?
テンプレートの友情は特別です。次回の投稿では、その理由を説明します。