C ++可変長テンプレート。 カレーと部分使用

こんにちは、Habrasociety様。

最近、カリー化と部分適用に関する議論を観察する必要がありました。 この論争の本質は、プログラミング言語で、組み込みの部分アプリケーション(たとえば、 Nemerleなど )または組み込みのカリー化(たとえば、 Haskellなど )を使用する方が実用的であるということです。

ネメルル:

 def sum3(x: int, y: int, z: int): int //   { x + y + z; }; def sum3y = sum3(_, 5, _); //     def sum3yz = sum3y(_, 5); //    def sum3yzx = sum3yz(5); // … ,  15
      
      





ハスケル:

 sum3 xyz = x + y + z --   sum3x = sum3 5 --     sum3xy = sum3x 5 --   sum3xyz = sum3xy 5 -- … ,  15
      
      





個人的には、両方のエンティティを実装する必要があると思います。 さらに、来るべきC ++標準、つまりVariadicテンプレートからgccに機会が現れた瞬間からすでに十分な時間が経過しています。 ご理解のとおり、この記事では、Variadicテンプレートを使用したC ++のカリー化と部分的なアプリケーションの実装を提案しています。 作業中、MinGW gcc 4.6.1およびCode :: Blocks 10.05が使用されました。



カレー



特に直感的なので、カレーから始めましょう。 目標は、関数を受け取り、カリー化されたバージョンを返す高次関数です。 さらに、任意の数の引数をこの関数に渡すことができます。その結果、残りの引数を取り、最終結果を生成する別の関数が作成されます。

 std::function< int(int, int, int) > f = [] ( int x, int y, int z ) { return x + y + z; }; auto f1 = carry(f); auto f2 = f1(5, 5); auto v15 = f2(5);
      
      





ターゲット関数を保存するオブジェクトが必要です。オブジェクトは、固定数の引数を取る関数のように動作します。 そして、引数に対するこのオブジェクト関数のアクションの結果は、関数に加えて、渡された引数を保存する別のオブジェクトである必要があります。C++では、スタック上のデータは永久に存続しないため、コピーする必要があります。つまり、この場合、オブジェクトをコピーする必要があります。 もちろん、アドレスは禁止されていません。 非常に長い間、この方向で哲学を立てることができ、それぞれの場合に最適なソリューションを見つけることができると思います。 将来的には、1つまたは別の引数をコピーする必要があるかどうか、またはリンクで十分かどうかを何らかの方法で指定することもできます。

さらに、このオブジェクトは単純な関数として動作し、特定の数の引数、つまり残りの引数を取る必要があります。 そのため、目的関数のタイプに依存するテンプレートクラスが必要です。 さらに、転送された引数を保存するには、それらの数と型が必要であり、結果の関数を決定するには、残りの引数の数と型が必要です。 関数型と2セットのインデックスをテンプレートに渡すのが最善であることが実験的に決定されました:渡されたものと残りのものです。 実装はラッパーstd ::関数に焦点を合わせ、引数はstd :: tupleに格納されました。 コンパイル時に数字と型を操作するために、いくつかの補助テンプレートも使用されました-それら自体が別のライブラリへの主張を置くことができるため、それらの名前がその本質の良い説明になることを願っています。ここでそれらを説明することはできません

以下は、関数とデータを格納するクラスコード、および固定されていない数の引数を受け入れる関数のように動作するクラスコードです。 テンプレートのパック展開の豊富な使用に注意を払いたいです。

 template< class, class, class > class CarryHolder; template< class OUT_TYPE, class... IN_TYPES, uint32_t... CAP_INDEXES, uint32_t... RES_INDEXES > class CarryHolder< std::function< OUT_TYPE ( IN_TYPES... ) >, UintContainer< CAP_INDEXES... >, //    UintContainer< RES_INDEXES... > > //    { public: typedef std::function< OUT_TYPE ( IN_TYPES... ) > FuncType; //    typedef std::tuple< typename UnQualify< //  const  & typename GetNthType< CAP_INDEXES, TypeContainer<IN_TYPES...> >::Result >::Result... > CleanCapTupleType; //      typedef std::function< OUT_TYPE ( typename GetNthType< RES_INDEXES, TypeContainer<IN_TYPES...> >::Result... ) > ReturnFuncType; //    public: CarryHolder( FuncType const& f, typename UnQualify< typename GetNthType< CAP_INDEXES, TypeContainer<IN_TYPES...> >::Result >::Result const&... capturedValues ): _function(f), _capturedData( capturedValues... ) { }; CarryHolder( CarryHolder const& other ): _function(other._function), _capturedData( other._capturedData ) { }; //    inline OUT_TYPE operator()( typename GetNthType< RES_INDEXES, TypeContainer<IN_TYPES...> >::Result... other_values ) { //        return _function( std::get< CAP_INDEXES > (_capturedData)..., other_values ... ); }; private: CarryHolder(); FuncType _function; CleanCapTupleType _capturedData; }; template< class > class Carry; template< class OUT_TYPE, class... IN_TYPES > class Carry< std::function< OUT_TYPE (IN_TYPES...) > > { public: typedef typename std::function< OUT_TYPE (IN_TYPES...) > FuncType; constexpr static uint32_t ArgsCount = GetTypesLength< TypeContainer<IN_TYPES...> >::Result; Carry( Carry const& carry ): _function( carry._function ) { }; Carry( FuncType const& f ): _function( f ) { }; template< class... INNER_IN_TYPES > inline auto operator()( INNER_IN_TYPES const& ... values ) -> typename CarryHolder< FuncType, //      typename UintRange< GetTypesLength< TypeContainer<INNER_IN_TYPES...> >::Result >::Result, //      typename UintRangeFromTo< GetTypesLength< TypeContainer<INNER_IN_TYPES...> >::Result, ArgsCount >::Result >::ReturnFuncType //  CarryHolder   std::function { typedef CarryHolder< FuncType, typename UintRange< GetTypesLength< TypeContainer<INNER_IN_TYPES...> >::Result >::Result, typename UintRangeFromTo< GetTypesLength< TypeContainer<INNER_IN_TYPES...> >::Result, ArgsCount >::Result > CarryHolderSpec; return CarryHolderSpec( _function, values... ); }; private: Carry(); FuncType _function; }; template< class FUNC_TYPE > Carry<FUNC_TYPE> carry( FUNC_TYPE const& f ) //    { return Carry<FUNC_TYPE>(f); };
      
      







引数の交換



部分的なアプリケーションを実装するには、さらにカリー化する場所で引数を再配置するというアプローチを使用できます。 次のようになります。

 std::function< int(int, int, int) > f = [] ( int x, int y, int z ) { return x + y + z; }; auto f1 = permute<2, 1, 0>(f);
      
      





前と同じように、目的関数を格納するオブジェクトが必要になりますが、特定の数の引数を取る、または目的関数の引数を再配置した通常の関数のように動作します。 これを行うには、関数のタイプとインデックスのシーケンス(引数の順列)に依存するテンプレートクラスが必要です。 また、順列(以下の説明)を補完し、逆順列(逆インデックス)を検索するためのテンプレートが必要になります。 入力パラメーター型のシーケンスを形成するには直接置換が必要であり、関数呼び出し中に引数を挿入するには逆置換が使用されます。 内部クラスは、逆インデックスタイプのデプロイにも使用されます。 以下は、この機能を実装するクラスコードです。

 template< class, class > class Permutation; template< class OUT_TYPE, class... IN_TYPES, uint32_t... INDEXES > class Permutation< std::function< OUT_TYPE (IN_TYPES...) >, //    UintContainer< INDEXES... > > //   { public: typedef std::function< OUT_TYPE (IN_TYPES...) > FuncType; typedef std::function< OUT_TYPE (typename GetNthType< INDEXES, TypeContainer<IN_TYPES...> >::Result...) > NewFuncType; Permutation( Permutation const& perm ): _function( perm._function ) { }; Permutation( FuncType const& f ): _function( f ) { }; private: //        template< uint32_t... INVERSE > inline OUT_TYPE apply( UintContainer< INVERSE... >, //    , ..  typename GetNthType< INDEXES, TypeContainer<IN_TYPES...> >::Result... values ) { //    std::tuple    std::tuple< typename GetNthType< INDEXES, TypeContainer<IN_TYPES...> >::Result... > data( values... ); //    std::tuple    return _function( std::get< INVERSE > (data)... ); }; public: inline OUT_TYPE operator()( typename GetNthType< INDEXES, TypeContainer<IN_TYPES...> >::Result... values ) { //    typename InversePermutation< UintContainer<INDEXES...> >::Result inverse; return apply( inverse, values... ); }; private: Permutation(); FuncType _function; }; //   ;  Permutation  std::function template< uint32_t... INDEXES, class FUNC_TYPE > auto permute( FUNC_TYPE const& f ) -> typename Permutation<FUNC_TYPE, //  , ..      typename ComplementRange< GetArgumentsCount<FUNC_TYPE>::Result, UintContainer < INDEXES... > >::Result >::NewFuncType { typedef Permutation<FUNC_TYPE, typename ComplementRange< GetArgumentsCount<FUNC_TYPE>::Result, UintContainer < INDEXES... > >::Result > PermutationType; return PermutationType(f); };
      
      





これで、説明した機能を備えた部分的なアプリケーションが簡単になります。以下にコードを示します。

 template< uint32_t... INDEXES, class FUNC_TYPE > auto partApply( FUNC_TYPE const& f ) -> decltype(carry(permute<INDEXES...>(f))) { return carry(permute<INDEXES...>(f)); };
      
      





そして、インデックスを補完する可能性を考えると、これはすべてのインデックスではないことを示すことで使用できます。

 std::function< int(int, int, int) > f = [] ( int x, int y, int z ) { return x + y + z; }; auto f1 = permute<2>(f); //  <2, 0, 1> -  "   "
      
      





これらはVariadicテンプレートを開くオプションです。 後で、興味深い場合は、コードをレイアウトします。

実際、カリー化は非常に古典的なものにはなりませんでした。それは1ステップで「必須」、つまりカリー化された(実装された関数の意味で)関数にすべての引数を渡すためです。 また、非古典的な本質は、修飾子の操作中に顕著です。 しかし、これらはすべてC ++の機能です。



更新:

Carry :: operator()のCarryHolderSpecは、引数が再コピーされるため、std ::関数で不必要にラップする必要がないことがわかりました。 しかし、一時オブジェクトへのリンクはこれを回避するのに役立つと思います。



更新:

トピックを「異常なプログラミング」に移動しました。ここで彼にとってより快適になると思います。



All Articles