コンパイル時のループ展開の一般的なソリューションに興味があります (各関数呼び出しが特定の数のクロック サイクルを取り、複数の呼び出しを並行して実行できる 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...);
これまでのところ、私は 3 つの異なるテンプレート メタプログラム ソリューションを使用しており、特にコンパイラが関数呼び出しをインライン化する方法に関して、異なるアプローチの利点/欠点は何か疑問に思っています。
アプローチ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
また、無名ラムダを使用する場合は、関数の型を明示的に指定する必要がありますが、これは厄介です。
アプローチ 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);
構文に関しては、この 3 番目のアプローチが最もクリーンで明確に見えますが、コンパイラによる 3 つのアプローチの処理方法に違いがあるのではないかと考えています。