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