6

私はいくつかのブールテンプレート引数を取る関数を持っています:

template<bool par1, bool par2, bool par2>
void function(int arg1, int arg2, int arg3);

コンパイル時に(必要に応じてC ++ 11を使用して、テンプレートマジックを使用して)、の値のすべての組み合わせへの関数ポインターのテーブル(またはC ++メタプログラミングの面白い構造のようなもの)を自動的に生成したいテンプレートパラメータpar*。これにより、これらのテンプレートパラメータを実行時引数として受け取り、適切なテンプレートインスタンス化に転送する関数を作成できます。

void runtime_function(bool par1, bool par2, bool par3, int arg1, int arg2, int arg3);

テンプレート関数の代わりに、テンプレートテンプレート引数のおかげで、クラスで同じことをしたい場合は、これを行うことができると思います。

template<template<bool> class T> class CombinationsOfTemplateParameters;
template<template<bool, bool> class T> class CombinationsOfTemplateParameters;
template<template<bool, bool, bool> class T> class CombinationsOfTemplateParameters;
//and so on, up to some implementation defined hard limit.

しかし、私が知る限り、テンプレートパラメータを指定せずに、汎用テンプレート関数を指す方法はありません。したがって、そもそも、テンプレートパラメータリストのヘルパークラスに渡す方法がわかりません。

この問題を解決する方法はありますか?

4

1 に答える 1

21

ステップ1、問題を理解するために、インスタンス化ごとに関数ポインターの配列を作成します。

template<bool, bool, bool> void function(int, int, int);

typedef void (*func_type)(int, int, int);

func_type funcs[] = {
    &function<false, false, false>,
    &function<false, false, true>,
    &function<false, true,  false>,
    &function<false, true,  true >,
    &function<true,  false, false>,
    &function<true,  false, true >,
    &function<true,  true,  false>,
    &function<true,  true,  true >
};

これが3ビットの2進数の表のように見えることに注意してください。

0 0 0  == 0
0 0 1  == 1
0 1 0  == 2
0 1 1  == 3
// etc...

したがって、ビット演算によって形成された整数を使用して配列にインデックスを付けることができます。

void runtime_function(bool par1, bool par2, bool par3, int arg1, int arg2, int arg3)
{
  func_type f = funcs[ int(par1)<<2 | int(par2)<<1 | int(par3) ];
  f(arg1, arg2, arg3);
};

ステップ2、配列を作成して使用する方法を理解したので、手動で書き出すのではなく、可変個引数テンプレートを使用して配列を自動的に生成します。

まず、整数のパラメーターパックを作成する型を使用します(Johannes Schaubseqテンプレートを使用)。

template<int ...>
struct seq { };

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };

template<int ...S>
struct gens<0, S...> {
  typedef seq<S...> type;
};

次に、それをパック拡張で使用して、可能な各インスタンス化を生成します。

template<bool, bool, bool> void function(int, int, int);

typedef void (*func_type)(int, int, int);

template<typename> struct make_table;

template<int... N>
  struct make_table<seq<N...>>
  {
    static const func_type funcs[sizeof...(N)];
  };

template<int... N>
  const func_type make_table<seq<N...>>::funcs[sizeof...(N)] = {
    &function< bool(N&4), bool(N&2), bool(N&1) >...
  };

今、あなたはそのようにそれを使うことができます:

void runtime_function(bool par1, bool par2, bool par3, int arg1, int arg2, int arg3)
{
  typedef gens<8>::type seq8;

  func_type f = make_table<seq8>::funcs[ (par1<<2) | (par2<<1) | par3 ];

  f(arg1, arg2, arg3);

}

マジックナンバー8は2の3乗(ブールパラメータの数)です。

ステップ3、それをテストします。すべてのタイプとパック拡張がコンパイラによってチェックされるため、コアロジックを間違えた場合でもコンパイルされなかったとかなり確信していますが、ビット演算が間違っていた可能性があります。

#include <iostream>

template<bool b1, bool b2, bool b3>
  void function(int i1, int i2, int i3){
    std::cout << std::boolalpha << "f<"
      << b1 << ", " << b2 << ", " << b2
      << ">("
      << i1 << ", " << i2 << ", " << i3
      << ")\n";
}

int main()
{
  runtime_function(false, true, true, 1, 2, 3);
  runtime_function(true, false, false, 4, 5, 6);
}

それは印刷します:

f<false, true, true>(1, 2, 3)
f<true, false, false>(4, 5, 6)

完全に一般的なバージョン

4つのboolテンプレートパラメーターを持つ関数テンプレートに対してこれを行うにはgens<16>、パック拡張を使用および変更する必要があります

template<int... N>
  const func_type make_table<seq<N...>>::funcs[] = {
    &function< bool(N&8), bool(N&4), bool(N&2), bool(N&1) >...
  };

これはあまり便利ではないので、intの別のパラメーターパックを導入し、次のseq<3,2,1,0>ように使用することで、任意の数のパラメーターを処理するように一般化できるはずです。

template<int... N, int... Bits>
  const func_type make_table<seq<N...>, seq<Bits...>>::funcs[] = {
    &function< /* some bitwise op using N & (1<<Bits) ... */ > ...
  };

ただし、これは機能しません。これは、を使用してパックを拡張したいが、同時にBits拡張したくないためですN(パックのサイズが異なるため、とにかく機能しません)。したがって、レベルを使用する必要があります。パックを個別に拡張できるようにするための間接参照。

以下の最終バージョンでは、関数を使用してgen_func<N>、インデックスNの関数ポインターを取得します。

template<unsigned N, int... Mask>
  static constexpr func_type gen_func(seq<Mask...>)
  { return &function<(N&(1<<Mask))...>; }

genrevsそして、整数の逆シーケンスを作成するために追加します。これは、パラメーターパックseq<2,1,0>として使用される関数に渡されます。Mask

gen_func<I>(typename genrevs<NParams>::type()) ...

この変更により、make_tableクラステンプレートは任意のアリティで関数を処理できるため、最後のステップは、関数の型でパラメーターを設定し(そして、パラメーターの数を推測し、そこから可能な関数の特殊化の数を推測して)、アクセサーを追加することです。適切な機能をmake_table取得するには:

void runtime_function(bool par1, bool par2, bool par3, int arg1, int arg2, int arg3)
{
  auto f = make_table<void(int, int, int)>::get(par1, par2, par3);

  f(arg1, arg2, arg3);
}

これが完全な最終バージョンです。昨夜このコードを書いた後、関数パラメーター(int, int, int)の数がテンプレートパラメーターの数と同じであると想定していることに気付きました。それが<bool, bool, bool>当てはまらない場合は、に型以外のテンプレートパラメーターを追加してmake_table、の数を指定する必要があります。テンプレートパラメータ(以下のコードでNParamsは、推定されます):

#include <type_traits>

template<int ...>
struct seq { };

template<int N, int ...S>
struct gens : gens<N-1, N-1, S...> { };

template<int ...S>
struct gens<0, S...> {
  typedef seq<S...> type;
};

template<int N, int ...S>
struct genrevs : genrevs<N-1, S..., N-1> { };

template<int ...S>
struct genrevs<0, S...> {
  typedef seq<S...> type;
};

template<bool, bool, bool> void function(int, int, int);

template<unsigned N>
  struct pow2
  {
    static constexpr unsigned value = 2*pow2<N-1>::value;
  };

template<> struct pow2<0> { static constexpr unsigned value = 1; };

template<typename Signature> struct make_table_seq;

template<typename Res, typename... Params>
  struct make_table_seq<Res(Params...)>
  : gens<pow2<sizeof...(Params)>::value>
  { };

template<typename Signature, typename = typename make_table_seq<Signature>::type>
struct make_table;

template<typename Res, typename... Params, int... I>
  class make_table<Res(Params...), seq<I...>>
  {
    static const unsigned NParams = sizeof...(Params);

  public:
    typedef Res (*func_type)(Params...);

    template<typename... Bool>
      static typename std::enable_if<sizeof...(Bool)==NParams, func_type>::type
      get(Bool... b)
      { return funcs[ shift_or(0, b...) ]; }

  private:
    template<unsigned N, int... Mask>
      static constexpr func_type gen_func(seq<Mask...>)
      { return &function<(bool(N&(1<<Mask)))...>; }

    template<typename... Bool>
      static int shift_or(int i, bool b0, Bool... b)
      {
        return shift_or((i<<1) | int(b0), b...);
      }

    static int shift_or(int i) { return i; }

    static const func_type funcs[sizeof...(I)];
  };

template<typename Res, typename... Params, int... I>
  const typename make_table<Res(Params...), seq<I...>>::func_type
  make_table<Res(Params...), seq<I...>>::funcs[] = {
    gen_func<I>(typename genrevs<NParams>::type()) ...
  };

// specialise for function pointer types as well as function types
template<typename Res, typename... Params>
  struct make_table_seq<Res(*)(Params...)>
  : make_table_seq<Res(Params...)>
  { };

template<typename Res, typename... Params, typename T>
  class make_table<Res(*)(Params...), T>
  : make_table<Res(Params...)>
  { };
于 2012-06-13T21:49:04.137 に答える