考虑以下形式的编译时函数:
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倍。但实际上,此参数没有非常广泛的可能值(例如0
和32
之间):因此在运行时在正确的编译时版本上进行分支会更有效率
答案 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
调用,那么它应该可以正常工作。请注意,将为使用的每个static
和Function
以及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)>());
}