【问题标题】:transpose template function boolean arguments to runtime function arguments with template metaprogramming使用模板元编程将模板函数布尔参数转换为运行时函数参数
【发布时间】:2012-06-16 21:54:26
【问题描述】:

我有一个接受多个布尔模板参数的函数:

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.

但据我所知,没有办法指向通用模板函数,而未指定其模板参数。因此,我首先不知道如何将它传递给模板参数列表中的某个辅助类。

有没有办法解决这个问题?

【问题讨论】:

    标签: c++ templates c++11 metaprogramming template-meta-programming


    【解决方案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 位二进制数表:

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

    第二步,既然我已经了解了如何构造和使用数组,我将使用可变参数模板自动生成数组,而不是手动写出来。

    首先使用创建整数参数包的类型(使用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 是二的三的幂(布尔参数的数量。)

    第三步,测试。我相当有信心,如果我弄错了核心逻辑,它甚至都不会编译,因为编译器会检查所有类型和包扩展,但我可能会弄错按位运算。

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

    完全通用的版本

    要为具有四个布尔模板参数的函数模板执行此操作,您需要使用 gens&lt;16&gt; 并更改包扩展

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

    这不是很方便,因此应该可以通过引入另一个整数参数包seq&lt;3,2,1,0&gt; 并像这样使用它来泛化它以处理任意数量的参数:

    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&lt;N&gt;来获取索引N处的函数指针:

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

    并添加genrevs 以创建整数的反向序列seq&lt;2,1,0&gt;,将其传递给该函数以用作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)与模板参数的数量&lt;bool, bool, bool&gt;相同,如果不是这样,那么你需要添加一个额外的非类型模板参数到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...)>
      { };
    

    【讨论】:

    • 我特别想避免的是您的第 1 步,即手动创建实例化表。
    • 这只是一个正在进行的工作,以证明它是一个二进制数字表。该示例的其余部分不使用它。仔细看:)
    • 抱歉,来晚了:)我明天会仔细检查。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-12
    • 1970-01-01
    • 2013-08-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多