【问题标题】:Runtime function branching on compile-time function?运行时函数在编译时函数上分支?
【发布时间】:2014-01-26 02:36:58
【问题描述】:

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

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&lt;0, 32&gt;(6)(运行时版本)应该调用function&lt;6&gt;()(编译时版本)。

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

【问题讨论】:

  • 我不明白你的问题。可以举个例子吗?
  • 提供的示例是否阐明了问题?
  • 如果x 在编译时未知,您将不知道function&lt;0, 255&gt;(x) 在编译时的值。你的目标是什么?
  • @PaulDraper:我认为他(有效地)在询问如何生成跳转表,所以function&lt;0, 255&gt;(x) 将相当于switch (x) { case 0: return function&lt;0&gt;(); case 1: return function&lt;1&gt;(); /* etc*/ default: throw "nope"; }。我过去曾问过类似的问题。您可以使用递归来做到这一点,然后依靠编译器来展开它。 (重点是您的程序可以在编译时生成function&lt;N&gt; 的结果,您可以查看这些结果。)
  • @PaulDraper 我已经添加了解释。

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


【解决方案1】:

最简单的方法是设置递归级联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>();
  }
};

或类似的东西,并依靠编译器将ifs 链折叠成一个。 (如果你知道返回类型,你可以去掉那个烦人的 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&lt;V&gt;,然后在运行时执行数组取消引用:

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,但我跳过了返回类型推导。

以上都没有经过测试或编译,因此很可能需要进行一些修复。但核心概念将起作用。专业化可能需要一些工作:执行&lt;From, From+1 来终止是我在实践中没有做过的事情:如果这会导致问题,您可以执行基于&lt;From, Width 的助手,并专注于Width=1

我个人将这种技术(体现在上面的 CallIf 类型中)称为“魔术开关”,我们将运行时设为有效并“神奇地”将其设为编译时值。我之所以提到这一点,是因为您可能会发现我通过谷歌搜索 Yakk 和“magic switch”(以及网站:stackoverflow.com)来讨论堆栈溢出的其他变体,其中一些已经编译并附有实时示例。

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

【讨论】:

    【解决方案2】:

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

    【讨论】:

      猜你喜欢
      • 2022-11-28
      • 1970-01-01
      • 2018-11-07
      • 2016-07-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-28
      相关资源
      最近更新 更多