C ++でのタプルの実装の機能

尊敬されるFlexFerrumによるVariadic Templates



に関する素晴らしい記事を読んで感銘を受け、メタプログラミングを練習し、可変数の引数を持つテンプレートを使用して、 Tupleと呼ばれるデータ構造の独自の実装を書くことにしました。 慣れていない人にとっては、タプルはさまざまなタイプのデータを同時に保存するデータ構造です。 ただし、特定のケースでは、テンプレートパラメーターとして渡された型のデータを格納するテンプレートクラスになります(順序を考慮して)。



読者は既に上記の記事に精通していると想定されています。開発プロセスを説明するときは、それを基に構築します。



データ保存


私たちが直面している最初の問題は、データストレージの原則です。 FlexFerrumの記事では、クラスの実装が提案されました。これは、本質的に、テンプレートパラメーターとして渡される関数の重ね合わせです。 関数ポインターを保存するために、 DataHolder



クラスからの多重継承のモデルがDataHolder



ました。



 template<typename T> struct DataHolder { T m_data; }; template<typename Op, typename … F> class Composer : public DataHolder<F> … { // ... };
      
      







少し異なる方法で継承のチェーンを構築し、最後にこれから得られるメリットを見ていきます。



 template <class T, class ... REST> class Tuple : public Tuple<REST ...> { public: Tuple(); Tuple(const T & _p1, const REST & ... _rest); /// returns elements count inline constexpr static unsigned int Count() { return mIndex + 1; } // ... protected: private: T mMember = T(); static constexpr unsigned int mIndex = TupleSuper::Count(); } //   template <class T> class Tuple { public: Tuple(); Tuple(const T & _p1); /// returns elements count inline constexpr static unsigned int Count() { return mIndex + 1; } // ... protected: private: T mMember = T(); static constexpr unsigned int mIndex = 0; }
      
      







コンストラクターの実装は明らかなはずです。現在のクラスのフィールドを初期化し、残りの引数を使用して基本クラスのコンストラクターを呼び出します。



メソッドの設定/取得


次のステップは、クラスのデータフィールドにセッターとゲッターを実装することです。 すべてのフィールドを一度に受け取って書き込むメソッドは実装が簡単であり、ここでは説明しません。 もっと興味深い状況は、たとえば、フィールドの数値インデックスをテンプレート引数として受け入れるゲッターの場合です。 それを実装するには、次のように継承チェーンのTuple



型にインデックスをTuple



ことができる補助クラスが必要です:インデックス0を持つ型は最下位の子孫であり、インデックス1はその直属の祖先などです。 誘導によって。 大量に送信すると、コンパイル段階でエラーが発生します。 このようなクラスを作成するには、インデックス0の終了特殊化、およびインデックスN-1のインスタンス化によるインデックスNのより一般的な定義を定義します。 tuple



型自体を「インデクサー」内でusing



(またはtypedef



)として定義します クラスをプレーンなTupleIndexer



と呼びましょう。



 template <class T, class ... REST> class Tuple; template <unsigned int INDEX, class T, class ... REST> class TupleIndexer { public: using TupleIndexerDeeper = TupleIndexer<INDEX - 1, REST ...>; using TupleType = typename TupleIndexerDeeper::TupleType; }; template <class T, class ... REST> class TupleIndexer<0, T, REST ...> { public: using TupleType = Tuple<T, REST ...>; };
      
      







さらに、多重継承を使用せず、クラスのチェーンを構築したという事実を利用します。 static_cast



介してインデックスによって計算されたstatic_cast



クラスをstatic_cast



ます。 Get



メソッドからの戻り値を計算するには、左端のフィールドのTuple



シノニムを作成し、インデックスで取得したTuple



から取得します。



 template <class T, class ... REST> class Tuple : public Tuple<REST ...> { public: using LeftMemberType = T; template <unsigned int INDEX> using MemberTypeIndexed = typename tuple::TupleIndexer<INDEX, T, REST ...>::TupleType::LeftMemberType; template <unsigned int INDEX> using TupleTypeIndexed = typename tuple::TupleIndexer<INDEX, T, REST ...>::TupleType; template <unsigned int INDEX> inline const MemberTypeIndexed<INDEX> & Get() const; // ... }; template <class T, class ... REST> template <unsigned int INDEX> inline const typename Tuple<T, REST ...>::template MemberTypeIndexed<INDEX> & Tuple<T, REST ...>::Get() const { return static_cast<const TupleTypeIndexed<INDEX> *>(this)->mMember; }
      
      







セッターの実装は提供されません、なぜなら これはゲッターと同じです。 残念ながら、このタプルのインデックスに対する完全な反復は不可能です(必要ですか?)。 戻り値のタイプを知る必要があります。



サブタプル


次に説明するステップは、別のフィールドの組み合わせからタプルを作成する機能を追加することです。 最も単純なオプションは、どのフィールドが取得され、新しいTuple



コンストラクターに転送されるかに応じて、可変数のインデックスを受け入れるテンプレートメソッドです。



 template <class T, class ... REST> class Tuple : public Tuple<REST ...> { public: using LeftMemberType = T; template <unsigned int INDEX> using MemberTypeIndexed = typename tuple::TupleIndexer<INDEX, T, REST ...>::TupleType::LeftMemberType; template <unsigned int ... INDICES> using SubTupleTypeIndexed = Tuple<MemberTypeIndexed<INDICES> ...>; template <unsigned int ... INDICES> inline SubTupleTypeIndexed<INDICES ...> MakeTuple() const { return Tuple<MemberTypeIndexed<INDICES> ...>(Get<INDICES>() ...); } // ... };
      
      







しかし、これだけでは十分ではありません。 インデックスの範囲を受け入れ、指定された範囲のインデックスを持つ型で指定されたTuple



を返すテンプレートメソッドを実装します。 すなわち:



 template <unsigned int A, unsigned int B> inline SubTupleTypeRanged<A, B> MakeSubTuple() const;
      
      







ご想像のとおり、インデックスではなく範囲によって返されるTuple



のタイプを計算できる別のヘルパークラスが必要です。 これを行うには、FlexFerrumの記事の方法と同様の手法を使用します。 テンプレートパラメータ(AおよびB)として範囲を取り、AからBのインデックスで指定された特定のクラスのシノニムを格納するクラスを作成しましょう。そのクラスは、順番にTuple



タイプを計算します。 クラスの名前はIndices



それぞれRange



Indices



を選択します。



Tuple



テンプレートの対応するタイプを格納するIndices



クラスの実装:



 template <class T, class ... REST> class Tuple; template <unsigned int ... INDICES> class Indices { public: template <class T, class ... REST> using SubTupleType = Tuple<typename Tuple<T, REST ...>::template MemberTypeIndexed<INDICES> ...>; protected: private: };
      
      







実際の範囲の実装:



 template <unsigned int A, unsigned int B> class Range { public: using RangeLesser = Range<A, B + (A < B ? - 1 : 1)>; template <unsigned int ... INDICES> using IndicesExtendable = typename RangeLesser::template IndicesExtendable<B, INDICES ...>; using Indices = IndicesExtendable<>; protected: private: }; template <unsigned int INDEX> class Range<INDEX, INDEX> { public: template <unsigned int ... INDICES> using IndicesExtendable = tuple::Indices<INDEX, INDICES ...>; using Indices = IndicesExtendable<>; protected: private: };
      
      







この場合、終了ブランチは、開始と終了が等しい範囲です(長さ= 0)。 長さNの範囲は、長さN-1の範囲からIndicesExtendable



パターンを介してIndicesExtendable



パターンを定義します。簡単に言えば、範囲を単位のポイントまで徐々に引き出します。 逆方向の型推論のプロセスを観察する場合、範囲拡張の各反復で、 IndicesExtendable



テンプレートの新しい同義語は、新しいテンプレートパラメーターを追加することIndicesExtendable



ます-前の反復からのIndicesExtendableテンプレートの同義語に別のインデックス。 したがって、 Range<1, 4>



にはIndices = Indices<1, 2, 3, 4>



Range<1, 4>



が含まれます。 この範囲クラスは、反対の場合Range<4, 1>



ます。 これにより、タイプIndices<4, 3, 2, 1>



が得られます。



もう1つの詳細は、 MakeSubTuple



メソッドでは、引数として正しいフィールドを使用してコンストラクターを何らかの方法で呼び出す必要があるという事実に関連しています。 これを行うには、パラメータとしてIndices



を受け入れる別のMakeTuple



メソッドを追加します。 これは、テンプレートメソッドを指定する別の方法です。



 template <unsigned int ... INDICES> inline SubTupleTypeIndexed<INDICES ...> MakeTuple(const Indices<INDICES ...> &) const;
      
      







このメソッドの実装は最初のものと似ています。 メソッドの本体では、パラメーターは単に無視されます。 これで、テンプレートパラメータを渡さずに呼び出しを行うことができます。



 t.MakeTuple(Indices<1, 2, 3>) //  ,   t.MakeTuple<1, 2, 3>()
      
      







これですべての準備が整い、 MakeSubTuple



メソッドの実装に直接進むことができます。 必要なインデックスをRangeから取得し、スタック上または静的変数としてインスタンス化し、拡張MakeTuple







 template <class T, class ... REST> class Tuple : public Tuple<REST ...> { public: template <unsigned int A, unsigned int B> using SubTupleTypeRanged = typename Range<A, B>::Indices::template SubTupleType<T, REST ...>; template <unsigned int A, unsigned int B> inline SubTupleTypeRanged<A, B> MakeSubTuple() const { return MakeTuple(typename Range<A, B>::Indices()); } // ... };
      
      







さまざまな数のテンプレート型パラメーター( Indices



またはRange



で指定されている、そうでない場合はコンパイル段階でエラーになる)を受け入れるさらに別のMakeTuple



メソッドの実装についての考えがあります。 このようなメソッドの戻り値は、例を使用してよりよく理解できます。



Tuple<char, int, double, float>::MakeTuple<Indices<3, 3>, Range<0, 1>, Indices<2>>



Tuple<float, float, char, int, double>



を返しTuple<float, float, char, int, double>







2つのMakeTuple



メソッドを1つに結合して、パラメーターにデフォルト値を与えることができます。 空のクラスをスタックにインスタンス化すると、少なくとも大きなオーバーヘッドが生じる可能性は低いため、この場合のパフォーマンスについて心配する必要はありません。



ボーナス


さらに、もう1つの興味深い方法であるInvoke



について説明します。 Tuple



フィールドに対応するパラメーターを持つ関数呼び出し演算子を適用できるすべてのものをパラメーターとして受け取ります。 Tupleを操作するための装置は、追加のエンティティを導入せずにそのような方法を実装するためにすでに十分に開発されています。 1つのMakeSubTuple



関数/ functor / lambda /などに渡すためのフィールドインデックスを使用してメソッドを指定しないようにするには、 MakeSubTuple



の場合と同じトリックを再度使用する必要があります。



 template <class CALLABLE, unsigned int ... INDICES> inline void Invoke(CALLABLE & _function, const Indices<INDICES ...> &) { _function(Get<INDICES>()...); } template <class CALLABLE> inline void Invoke(CALLABLE & _function) { Invoke(_function, typename Range<0, mIndex>::Indices()); }
      
      







この方法の注目すべき点は何ですか? そして、それによって、 Tuple



クラスは、自己記述Bind



クラスの一種のコアになります。 完全性に関する彼の発表は次のとおりです。



 template <class ... ARGS> class Bind { public: using Function = std::function<void (ARGS ...)>; using Tuple = tuple::Tuple<ARGS ...>; Bind(Function _function, const ARGS & ... _args): mFunction(_function), mTuple(_args ...) { } void operator() () { mTuple.Invoke(mFunction); } private: Function mFunction; Tuple mTuple; };
      
      







もちろん、 CALLABLE



戻り型があるCALLABLE



性があることはここでは考慮されませんが、この問題の解決策はこの記事の範囲外です。



まとめ


要約すると、 Tuple



クラスのすべてのメソッドが非常に高速であることがTuple



た。 ランタイム計算は使用されません。



Variadic Templates



使用すると、非常に圧縮されたコード行数でタプルを実装できます。 コードの重複に関する唯一の問題は、 Tuple - Tuple. , , , . Get . Get



. .



. Get<2>



Tuple<int, char>



, , Tuple



. "" , , , static_assert



.



Tuple



, Tuple<char, double, member1, member2>



, Get , . , .. C



, . Tuple



. , compile time



, , Serialize



. Tuple



.



. Tuple<int, char, double>



Tuple<char, double>



Tuple. . Tuple



, , , , , - , , .



Tuple



github .



readme, .

- , , append



, etc.

.



main.cpp , .



:

en.wikipedia.org/wiki/Variadic_template

habrahabr.ru/post/101430
















クラスの終了する特殊化であるTuple - Tuple. , , , . Get . Get



. .



. Get<2>



Tuple<int, char>



, , Tuple



. "" , , , static_assert



.



Tuple



, Tuple<char, double, member1, member2>



, Get , . , .. C



, . Tuple



. , compile time



, , Serialize



. Tuple



.



. Tuple<int, char, double>



Tuple<char, double>



Tuple. . Tuple



, , , , , - , , .



Tuple



github .



readme, .

- , , append



, etc.

.



main.cpp , .



:

en.wikipedia.org/wiki/Variadic_template

habrahabr.ru/post/101430
















Tuple - Tuple. , , , . Get



. Get



. .



. Get<2>



Tuple<int, char>



, , Tuple



. "" , , , static_assert



.



Tuple



, Tuple<char, double, member1, member2>



, Get , . , .. C



, . Tuple



. , compile time



, , Serialize



. Tuple



.



. Tuple<int, char, double>



Tuple<char, double>



Tuple. . Tuple



, , , , , - , , .



Tuple



github .



readme, .

- , , append



, etc.

.



main.cpp , .



:

en.wikipedia.org/wiki/Variadic_template

habrahabr.ru/post/101430















All Articles