运行时函数在编译时函数上分支



考虑表单的编译时间函数:

template <unsigned int Value>
constexpr unsigned int function() 
{
    // Just for the example, but it could be very complicated here
    return Value*Value;
}

如何使用模板元编程来编写将调用正确的编译时版本的运行时等效内容,因为知道value始终在[From, To[间隔中:

template <unsigned int From, unsigned int To, /* Something here */>
constexpr unsigned int function(const unsigned int value)
{
    // Something here
}

正确版本的分支应尽可能快。

例如function<0, 32>(6)(运行时版)应调用function<6>()(编译时版)。

编辑:说明:我为什么要这样做?此功能(实际用例)需要尽可能快(超级计算问题)。通过在编译时提供参数,我可以生成非常有效的代码。如果我简单地将值从模板参数移动到函数参数,则代码速度较慢10到100倍。但是实际上,此参数没有很大的可能值(例如032之间):因此,在正确的编译时版本上分支时分支会更有效。

最简单的方法是设置递归级联if/recurse链。

#define RETURNS(X) -> decltype(X) { return (X); }
template<unsigned From, unsigned To, typename Target>
struct CallIf {
  constexpr auto operator()( unsigned N )
    RETURNS( (N==From)?Target::template func<From>():CallIf<From+1, To, Target>()( N ) );
};
template<unsigned From, typename Target>
struct CallIf<From, From+1, Target> {
  constexpr auto operator()( unsigned N )
    RETURNS( Target::template func<From>() );
};
struct Func {
  template<unsigned V>
  constexpr unsigned func() const {
    return function<V>();
  }
};

或类似的东西,并依靠编译器将if s的链条崩溃至一个。(如果您知道返回类型,则可以消除烦人的RETURNS宏,或者如果您具有C 1Y功能,则可以执行相同的功能)。

现在,您可能需要使用类似的递归呼叫案例将其与在该范围内在value上进行二进制搜索的版本进行比较。同样,您可以通过在编译时值中检查和设置位来做到这一点。

template<unsigned From, unsigned To, typename Target>
struct CallIf {
  enum { Mid = From + (To-From)/2 }; // avoid overflow risk
  constexpr auto operator()( unsigned N )
    RETURNS( (N>=Mid)?CallIf<Mid, To, Target>()(N):CallIf<From,Mid,Target>()(N) );
};

具有相同专业的1宽度案例。

另一种方法是设置function<V>static数组,然后在运行时进行数组解除:

template<unsigned...> struct indexes {};
template<unsigned Min, unsigned Max, unsigned... Is> struct make_indexes:make_indexes<Min, Max-1, Max-1, Is...> {};
template<unsigned Min, unsigned... Is> struct make_indexes<Min, Min, Is...>:indexes<Is...> {};
template<unsigned From, unsigned To, typename Target>
struct CallIf {
  template<unsigned... Is>
  unsigned Invoke( indexes<Is...>, unsigned N ) const {
    typedef unsigned(*target)();
    static target ts[] = { &(function<Is>)... };
    return ts[N-From]();
  };
  unsigned operator()( unsigned N ) const {
    return Invoke( make_indexes<From, To>(), N );
  }
};

尽管我不确定如何至少在C 11中轻松制作上述constexpr,但我跳过了进行返回式扣除。

以上都没有进行测试或编译,因此很可能需要维修。但是核心概念将起作用。专业化可能需要一些工作:在实践中我尚未完成<From, From+1终止的事情:如果导致问题,您可以进行基于<From, Width的助手,并专门研究Width=1

我亲自称此技术(在上面的CallIf类型中体现)"魔术交换机",在该技术中,我们将运行时间有效,并且"魔法"使其成为编译时间值。我之所以提到这一点,是因为您可能会发现我通过搜索Yakk和" Magic Switch"(以及site:Stackoverflow.com)进行其他变体来谈论它,其中一些已编译并附有实时示例。p>最后,虽然最后一个版本(手动跳台表)可能是最快的,但如果您经常调用此调用的速度是关键在魔术开关中围绕它的算法:较早进行调度。但是,如果您只在最后一刻获得索引,并且您可以使用非constexpr呼叫,则可以工作。请注意,将为每个FunctionToFrom创建static阵列。

您可以创建一个结果(constexpr)阵列,类似于:

#if 1 // Not in C++11
template <std::size_t ...> struct index_sequence {};
template <std::size_t I, std::size_t ...Is>
struct make_index_sequence : make_index_sequence < I - 1, I - 1, Is... > {};
template <std::size_t ... Is>
struct make_index_sequence<0, Is...> : index_sequence<Is...> {};
#endif
template <unsigned int Value>
constexpr unsigned int function()
{
    // Just for the example, but it could be very complicated here
    return Value * Value;
}
namespace detail
{
    template <std::size_t From, std::size_t...Is>
    struct result_array
    {
        static constexpr std::array<unsigned int, sizeof...(Is)> values = {{::function<From + Is>()...}};
    };
    template <std::size_t From, std::size_t...Is>
    constexpr std::array<unsigned int, sizeof...(Is)> result_array<From, Is...>::values;
    template <std::size_t From, std::size_t...Is>
    constexpr unsigned int function(unsigned int value, index_sequence<Is...>)
    {
        return result_array<From, Is...>::values[value - From];
    }
} // namespace detail
template <unsigned int From, unsigned int To>
constexpr unsigned int function(const unsigned int value)
{
    static_assert(From < To, "Invalid template parameters");
    return detail::function<From>(value, make_index_sequence<std::size_t(To + 1 - From)>());
}

最新更新