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

时间:2014-01-26 02:36:58

标签: c++ templates c++11 runtime metaprogramming

考虑以下形式的编译时函数:

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之间):因此在运行时在正确的编译时版本上进行分支会更有效率

2 个答案:

答案 0 :(得分:4)

最简单的方法是设置递归级联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宏,或者如果您具有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宽度的情况具有相同的专业性。

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

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和“魔术开关”(以及网站:stackoverflow.com)找到我在堆栈溢出上讨论它的其他变体,其中一些已经编译并附有实例。

最后,虽然最后一个版本(手动跳转表)可能是最快的,但如果您经常调用它以使此调用的速度成为关键,那么您可能想要考虑不仅包装调用站点,而且在魔术开关中围绕它的算法:先做调度。但是,如果您只在最后一刻获得索引,并且您可以使用非constexpr调用,那么它应该可以正常工作。请注意,将为使用的每个staticFunction以及To创建From个数组。

答案 1 :(得分:0)

您可以创建一个(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)>());
}