transpose template function boolean arguments to r

2019-01-25 16:04发布

I have a function which takes several boolean template arguments:

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

I want to generate automatically at compile-time (with whatever template magic, with C++11 if needed) a table (or something equivalent as in the funny structures of C++ metaprogramming) of the function pointers to all the combination of the values of the template parameters par*, so that I can construct a function which takes these template parameters as runtime arguments and forward to the right template instantiation:

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

I think that this can be done if instead of a template function one wanted to do the same with classes, thanks to template template arguments:

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.

But as far as I know there's no way to point to a generic template function, leaving its template parameters unspecified. Hence I don't know how to pass it to some helper class in its template parameter list, in the first place.

Is there a way to solve this problem?

1条回答
小情绪 Triste *
2楼-- · 2019-01-25 16:17

Step one, just to understand the problem, I would construct an array of function pointers with each instantiation:

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 >
};

Notice how that looks like a table of 3-bit binary numbers:

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

So you can index into the array with an integer formed by bitwise operations:

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);
};

Step two, now that I've understood how to construct the array and use it, I would generate the array automatically with a variadic template, instead of writing it out by hand.

First use a type that creates a parameter pack of integers (using Johannes Schaub's seq template):

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;
};

Then use that in a pack expansion to generate each possible instantiation:

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) >...
  };

Now you can use that like so:

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);

}

The magic number 8 is two to the power of three (the number of bool parameters.)

Step three, test it. I'm fairly confident it wouldn't even have compiled if I got the core logic wrong, as all the types and pack expansions will be checked by the compiler, but I could have got the bitwise operations wrong.

#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);
}

It prints:

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

Fully generic version

To do it for a function template with four bool template parameters you'd need to use gens<16> and change the pack expansion

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

This isn't very convenient, so it should be possible to generalise it to handle any number of parameters, by introducing another parameter pack of ints seq<3,2,1,0> and using it like:

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

But this won't work, because we want a pack expansion using Bits but we don't want it to expand N at the same time (and the packs have different sizes so it woulodn't work anyway,) so we need to use a level of indirection to allow the packs to be expanded separately.

The final version below uses a function gen_func<N> to get the function pointer at index N:

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

And adds genrevs to create a reverse sequence of integers, seq<2,1,0>, which is passed to that function to be used as the Mask parameter pack:

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

With that change the make_table class template can handle functions with any arity, so the final step is to parameterise it by the function type (and have it deduce the number of parameters, and from that the number of possible function specializations) and to add an accessor to make_table to get the right function:

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);
}

Here's the full final version. After writing this code last night I realised it assumes that the number of function parameters (int, int, int) is the same as the number of template parameters <bool, bool, bool>, if that's not true then you'd need to add an extra non-type template parameter to make_table, specifying the number of template parameters (in the code below that's NParams and is deduced):

#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...)>
  { };
查看更多
登录 后发表回答