运行时函数在编译时分支

考虑表单的编译时函数:

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

如何编写将使用模板元编程调用正确的编译时版本的运行时等效value ,知道该value始终位于[From, To[ interval:

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链松散到一个。 (如果你知道返回类型,你RETURNS使用那个恼人的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基于<From, Width的助手,并专注于Width=1

我个人称这种技术(体现在上面的CallIf类型中)“魔法开关”,在那里我们使运行时间有效并且“神奇地”使其成为编译时间值。 我只提到这一点,因为你可能会发现我在堆栈溢出时使用谷歌搜索引擎和其他变体(“site”:site:stackoverflow.com)来讨论堆栈溢出问题,其中一些已经编译并附有实例。

最后,尽管最后一个版本(手动跳转表)可能是最快的,但如果您经常调用此调用的速度至关重要,那么您可能需要考虑不仅包含调用站点而且包含它的算法在一个魔术开关中:先进行调度。 但是,如果您只在最后一刻得到索引,并且您可以接受非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)>());
}
链接地址: http://www.djcxy.com/p/68363.html

上一篇: Runtime function branching on compile

下一篇: Forcing a constant expression to be evaluated during compile