我对编译时循环展开的一般解决方案感兴趣(我在 SIMD 设置中使用它,其中每个函数调用需要特定数量的时钟周期,并且可以并行执行多个调用,所以我需要调整数字累加器以最大程度地减少浪费的周期——添加额外的累加器和手动展开会产生显着的改进,但很费力)。
理想情况下,我希望能够写出类似的东西
unroll<N>(f,args...); // with f a pre-defined function
unroll<N>([](...) { ... },args...); // using a lambda
并生成以下内容:
f(1,args...);
f(2,args...);
...
f(N,args...);
到目前为止,我有三种不同的模板元程序解决方案,我想知道不同方法的优缺点是什么,特别是关于编译器如何内联函数调用。
方法1(递归函数)
template <int N> struct _int{ };
template <int N, typename F, typename ...Args>
inline void unroll_f(_int<N>, F&& f, Args&&... args) {
unroll_f(_int<N-1>(),std::forward<F>(f),std::forward<Args>(args)...);
f(N,args...);
}
template <typename F, typename ...Args>
inline void unroll_f(_int<1>, F&& f, Args&&... args) {
f(1,args...);
}
调用语法示例:
int x = 2;
auto mult = [](int n,int x) { std::cout << n*x << " "; };
unroll_f(_int<10>(),mult,x); // also works with anonymous lambda
unroll_f(_int<10>(),mult,2); // same syntax when argument is temporary
方法 2(递归构造函数)
template <int N, typename F, typename ...Args>
struct unroll_c {
unroll_c(F&& f, Args&&... args) {
unroll_c<N-1,F,Args...>(std::forward<F>(f),std::forward<Args>(args)...);
f(N,args...);
};
};
template <typename F, typename ...Args>
struct unroll_c<1,F,Args...> {
unroll_c(F&& f, Args&&... args) {
f(1,args...);
};
};
调用语法非常难看:
unroll_c<10,decltype(mult)&,int&>(mult,x);
unroll_c<10,decltype(mult)&,int&>(mult,2); // doesn't compile
如果使用匿名 lambda,则必须显式指定函数的类型,这很尴尬。
方法3(递归静态成员函数)
template <int N>
struct unroll_s {
template <typename F, typename ...Args>
static inline void apply(F&& f, Args&&... args) {
unroll_s<N-1>::apply(std::forward<F>(f),std::forward<Args>(args)...);
f(N,args...);
}
// can't use static operator() instead of 'apply'
};
template <>
struct unroll_s<1> {
template <typename F, typename ...Args>
static inline void apply(F&& f, Args&&... args) {
f(1,std::forward<Args>(args)...);
}
};
调用语法示例:
unroll_s<10>::apply(mult,x);
unroll_s<10>::apply(mult,2);
就语法而言,这第三种方法似乎是最干净和最清晰的,但我想知道编译器如何处理这三种方法是否可能存在差异。