引数の可変番号テンプレートの番号付け、または控えめなペアが隠すもの









C ++ 11標準を習得することは、けいれん的に起こらないプロセスです。 新しい言語構造を学習するには、構文を覚えるだけでなく、その目的と典型的な適用方法を理解する必要があります。 学習における重要な助けは、非常に興味深く必要な機会の存在に目を開くことができる、より美しいSTLです。 そして、あることが可能であり、STLで実装されていることを知っていれば、実装方法の一番下にたどり着くのは簡単です。



更新および改善されたペアクラスに関連する好奇心の強い例の1つについては、記事で説明します。

新しい標準では、 ペア 、利便性、汎用性など、一見シンプルなものが追加されました。 以前は、ペアを構成するタイプにかなり厳しい要件が課されていましたが、今では何でもペアに成形できます。 特に、そのようなタイプの構築に関する制限は削除されました。 コピー操作や移動操作を使用する必要がなくなりました。非自明なコンストラクターを使用して、メンバーを直接構築することでペアを作成できます(このような操作はemplace、「配置」と呼ばれ、C ++ 11ではSTLコンテナーでサポートされます)。



...そして、ここで、彼らが言うように、より詳細に。 ペアコンストラクターを呼び出して2組の引数を渡すと、どのコンストラクターにどの引数を与えるかを理解できます。 メンバーまたはペア全体のコピーまたは移動に関連する通常のコンストラクターの中で、次のことがわかります。



template< class... Args1, class... Args2 > pair( std::piecewise_construct_t, std::tuple<Args1...> first_args, std::tuple<Args2...> second_args );
      
      







piecewise_construct_tは、空の型であり、1 1のコンストラクターに引数を渡して、ピースごとにペアを作成することを知らせるのに役立ちます。 piecewise_constructと呼ばれるこのタイプの定数は、これに役立ちます。 それでは、2組の引数を指定して、タプルにパックします( tuplemake_tuple関数はそれらの作成に役立ちます)。 忘れてしまった読者、またはそれが何であるかを知らない読者のために、思い出させてください。タプルは、任意の型の任意の数の値のコレクションです。 C ++では、厳密な型制御により、可変数の引数を持つテンプレート (可変長テンプレート)を使用してタプルが実装されます。



さて、問題は正常に解決されたようです。タプルは、 最初2番目のコンストラクターの引数の「パッケージ」として機能します。 この段階で、新しい標準に精通しているプログラマーは、「ところで、タプルからのデータはどのようにアンパックされますか?」という質問をするかもしれません。ドキュメントは、タプル内の要素のインデックスをテンプレートパラメーターとして示すget関数のみを提供します。



引数はどのようにしてコンストラクターに入るのですか? タプルから一度に1つずつデータを取得するのは簡単です。 それらを再帰的に取得するのは簡単です。 しかし、関数呼び出し(この場合はコンストラクター)にすべての値を収める方法は?

ここでは、可変長テンプレートの展開が便利です。 ただし、アンパックはタプル型のリストではなく、インデックスのリストです。 どちらを最初に作成する必要があります。



主なものから始めましょう:既存のリストに新しいインデックスを追加しましょう。 当然、番号付けが0から始まる場合、新しいインデックスは入力リストのサイズと等しくなります。 インデックスのリストでパラメーター化する構造が必要です。



 template<size_t ... Indices> struct PackIndices { //     Indices  ,  sizeof ... (Indices) };
      
      







結果は、コンパイル時の整数定数のリストであり、同時にテンプレート引数も格納できませんが、これらの引数によってパラメーター化された型を格納できます。 そして、このタイプの役割に適した候補者はすでにいます。



 template<size_t ... Indices> struct PackIndices { typedef PackIndices<Indices... , sizeof ... (Indices)> next; };
      
      







次に、長さNのインデックスのリストを作成する再帰ジェネレーターを作成しましょう これは、最後のインデックスを長さN-1のリストに追加するだけで実行されます。



 template<size_t N> struct CreatePackIndices { typedef typename CreatePackIndices<N-1>::type::next type; };
      
      







...そして再帰を停止します:



 template<> struct CreatePackIndices<0> { typedef PackIndices<> type; };
      
      







インデックスのリストを作成する方法を取得したら、タプルをコンストラクタパラメータにアンパックします。 簡単にするために、最初に1つのオブジェクトのみの構築を検討します。



argsタプルとインデックスインデックスリストを使用すると、アンボクシングは基本的に次のようになります。



 first(std::get<Indices>(args)...)
      
      







Indicesにアクセスするには、アンパックするコンテキスト(つまり、 ペアコンストラクター)がこのリストによってパラメーター化されている必要があります。 これは、必要なすべてのパラメーターを含む2番目のコンストラクターテンプレートを作成する必要があることを意味します。 ちなみに、別のC ++ 11ノベルティ、コンストラクターの委任があります。これにより、初期化リストで代替コンストラクターを呼び出すことができます。 また、関数を呼び出すため、引数の型の自動推論を使用します。匿名コンストラクターPackIndicesを補助コンストラクターに渡します。 その結果、1本足のペアが得られます。



 template<typename T> class pair { // ,   template<typename ... ArgTypes, size_t ... Indices> pair(std::tuple<ArgTypes...>& first_args, PackIndices<Indices...>): first(std::get<Indices>(first_args)...) {} public: // ,   template<typename ... ArgTypes> pair(std::piecewise_construct_t, const std::tuple<ArgTypes...>& first_args): pair(first_args, typename CreatePackIndices<sizeof ... (ArgTypes)>::type()) {} private: T first; };
      
      







ここで、 完全な転送 -型を変更せずにネストされた呼び出しに引数を正しく渡すために必要なメカニズムについて思い出してください。 更新されたSTLでは、 forward関数が提供されます。これは各引数に適用され、さらに引数の型でパラメーター化される必要があります。 幸いなことに、新しい標準の作成者は、いくつかの引数のセットを同時にアンパックするなど、トリッキーなことを提供しました。 ArgTypesIndicesは明らかに同じ長さなので、アンパックパターンに転送呼び出しを安全に追加できます。



 template<typename ... ArgTypes, size_t ... Indices> pair(std::tuple<ArgTypes...>& first_args, PackIndices<Indices...>): first(std::forward<ArgTypes>(std::get<Indices>(first_args))...) {}
      
      







make_tupleの値からコンストラクターのパラメーターに至るまで、両方のレッグにペアを配置します。

コード
 template<typename T1, typename T2> class pair { // ,   template<typename ... ArgTypes1, size_t ... Indices1, typename ... ArgTypes2, size_t ... Indices2> pair(std::tuple<ArgTypes1...>& first_args, std::tuple<ArgTypes2...>& second_args, PackIndices<Indices1...>, PackIndices<Indices2...>): first(std::forward<ArgTypes1>(std::get<Indices1>(first_args))...), second(std::forward<ArgTypes2>(std::get<Indices2>(second_args))...) {} public: // ,   template<typename ... ArgTypes1, typename ... ArgTypes2> pair(std::piecewise_construct_t, std::tuple<ArgTypes1...> first_args, std::tuple<ArgTypes2...> second_args): pair(first_args, second_args, typename CreatePackIndices<sizeof ... (ArgTypes1)>::type(), typename CreatePackIndices<sizeof ... (ArgTypes2)>::type()) {} private: T1 first; T2 second; };
      
      







もちろん、このテクニックは、間に合わせの自転車のペアを作成するだけでなく、便利です。 したがって、 スタックされた仮想マシンを扱うプログラマーは、おそらく関数のラッパーを思い浮かぶでしょう。 間違いなく、他の分野にも応用されるでしょう。



All Articles