使用元编程展开嵌套循环
Nested loops unrolling using metaprogramming
我有许多嵌套循环 I, J, ... 在编译时已知,例如
for(int i = 0; i < I; ++i) {
for(int j = 0; j < J; ++j) {
// ...
// do sth with (i,j,...)
}
}
我需要使用大小 I、J、... 展开循环,这样我就可以在 编译时使用每个坐标组合。
为了澄清,请考虑以下结构并采用 2 个大小为 I = 2,J = 3.
的嵌套循环
template<int... I>
struct C {
static void f() {
// do sth
}
};
我不能使用索引 i, j (类似于上面)来索引结构 C 因为它们在编译时间。然而,我想要生成的正是我被允许使用索引时的情况,例如
C<0,0>::f();
C<0,1>::f();
C<0,2>::f();
C<1,0>::f();
C<1,1>::f();
C<1,2>::f();
我不是特别关心调用生成的顺序,只要生成所有组合即可。生成机制应该推广到任意数量的嵌套循环。
您可以通过以树状方式实例化模板,跟踪当前访问的节点来做到这一点。
namespace detail{
//This is used to store the visited nodes
template<int...> struct int_pack;
//Primary template
template<typename, int... I>
struct C;
//This is the leaf node
template<int... Is>
struct C<int_pack<Is...>> {
//The loop body goes here
static void f() {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
};
//This is the recursive case
template <int I, int... Is, int... PIs>
struct C<int_pack<PIs...>, I,Is...> {
template <std::size_t... Idx>
static void f_help (std::index_sequence<Idx...>) {
//Store the current node in the pack
//and call `C::f` for each loop iteration
(void)std::initializer_list<int> {
(C<int_pack<PIs...,Idx>,Is...>::f(), 0)...
};
}
//Use tag dispatching to generate the loop iterations
static void f() {
f_help(std::make_index_sequence<I>{});
}
};
}
//Helper alias
template<int... Is>
using C = detail::C<detail::int_pack<>, Is...>;
用法很简单:
C<2,3>::f();
在 Clang 上打印:
static void detail::C<detail::int_pack<0, 0>>::f() [I = <>]
static void detail::C<detail::int_pack<0, 1>>::f() [I = <>]
static void detail::C<detail::int_pack<0, 2>>::f() [I = <>]
static void detail::C<detail::int_pack<1, 0>>::f() [I = <>]
static void detail::C<detail::int_pack<1, 1>>::f() [I = <>]
static void detail::C<detail::int_pack<1, 2>>::f() [I = <>]
你可以让它更通用,这样你就可以通过 lambda 将循环体注入到 class 中,但是如果你只想这样做一次而不想这样做,上面的解决方案应该可以引入其他依赖项,例如 boost::hana
。这是更通用版本的可能实现(您可以通过完美转发等改进它):
namespace detail{
template<int...> struct int_pack;
template<typename, int... I>
struct C;
template<int... Is>
struct C<int_pack<Is...>> {
template <typename Func>
static void f(const Func& func) {
func(Is...);
}
};
template <int I, int... Is, int... PIs>
struct C<int_pack<PIs...>, I,Is...> {
template <std::size_t... Idx, typename Func>
static void f_help (std::index_sequence<Idx...>, const Func& func) {
(void)std::initializer_list<int>{ (C<int_pack<PIs...,Idx>,Is...>::f(func), 0)... };
}
template <typename Func>
static void f(const Func& func) {
f_help(std::make_index_sequence<I>{}, func);
}
};
}
你可以这样使用:
C<2,3>::f([](int i, int j){
std::cout << "i " << i << " j " << j << '\n';
});
这是我用 boost::hana
模拟的快速版本。可能有更好的方法来做到这一点,但这应该让您了解可以做什么。
template <typename Func>
void unroll (const Func& func) {
func();
}
template <std::size_t I1, std::size_t... Is, typename Func>
void unroll (const Func& func) {
hana::for_each(hana::range_c<std::size_t, 0, I1>,
[&](auto x) {
unroll<Is...>([x, &func] (auto... xs) { func(x,xs...); });
});
}
我有许多嵌套循环 I, J, ... 在编译时已知,例如
for(int i = 0; i < I; ++i) {
for(int j = 0; j < J; ++j) {
// ...
// do sth with (i,j,...)
}
}
我需要使用大小 I、J、... 展开循环,这样我就可以在 编译时使用每个坐标组合。
为了澄清,请考虑以下结构并采用 2 个大小为 I = 2,J = 3.
的嵌套循环template<int... I>
struct C {
static void f() {
// do sth
}
};
我不能使用索引 i, j (类似于上面)来索引结构 C 因为它们在编译时间。然而,我想要生成的正是我被允许使用索引时的情况,例如
C<0,0>::f();
C<0,1>::f();
C<0,2>::f();
C<1,0>::f();
C<1,1>::f();
C<1,2>::f();
我不是特别关心调用生成的顺序,只要生成所有组合即可。生成机制应该推广到任意数量的嵌套循环。
您可以通过以树状方式实例化模板,跟踪当前访问的节点来做到这一点。
namespace detail{
//This is used to store the visited nodes
template<int...> struct int_pack;
//Primary template
template<typename, int... I>
struct C;
//This is the leaf node
template<int... Is>
struct C<int_pack<Is...>> {
//The loop body goes here
static void f() {
std::cout << __PRETTY_FUNCTION__ << '\n';
}
};
//This is the recursive case
template <int I, int... Is, int... PIs>
struct C<int_pack<PIs...>, I,Is...> {
template <std::size_t... Idx>
static void f_help (std::index_sequence<Idx...>) {
//Store the current node in the pack
//and call `C::f` for each loop iteration
(void)std::initializer_list<int> {
(C<int_pack<PIs...,Idx>,Is...>::f(), 0)...
};
}
//Use tag dispatching to generate the loop iterations
static void f() {
f_help(std::make_index_sequence<I>{});
}
};
}
//Helper alias
template<int... Is>
using C = detail::C<detail::int_pack<>, Is...>;
用法很简单:
C<2,3>::f();
在 Clang 上打印:
static void detail::C<detail::int_pack<0, 0>>::f() [I = <>]
static void detail::C<detail::int_pack<0, 1>>::f() [I = <>]
static void detail::C<detail::int_pack<0, 2>>::f() [I = <>]
static void detail::C<detail::int_pack<1, 0>>::f() [I = <>]
static void detail::C<detail::int_pack<1, 1>>::f() [I = <>]
static void detail::C<detail::int_pack<1, 2>>::f() [I = <>]
你可以让它更通用,这样你就可以通过 lambda 将循环体注入到 class 中,但是如果你只想这样做一次而不想这样做,上面的解决方案应该可以引入其他依赖项,例如 boost::hana
。这是更通用版本的可能实现(您可以通过完美转发等改进它):
namespace detail{
template<int...> struct int_pack;
template<typename, int... I>
struct C;
template<int... Is>
struct C<int_pack<Is...>> {
template <typename Func>
static void f(const Func& func) {
func(Is...);
}
};
template <int I, int... Is, int... PIs>
struct C<int_pack<PIs...>, I,Is...> {
template <std::size_t... Idx, typename Func>
static void f_help (std::index_sequence<Idx...>, const Func& func) {
(void)std::initializer_list<int>{ (C<int_pack<PIs...,Idx>,Is...>::f(func), 0)... };
}
template <typename Func>
static void f(const Func& func) {
f_help(std::make_index_sequence<I>{}, func);
}
};
}
你可以这样使用:
C<2,3>::f([](int i, int j){
std::cout << "i " << i << " j " << j << '\n';
});
这是我用 boost::hana
模拟的快速版本。可能有更好的方法来做到这一点,但这应该让您了解可以做什么。
template <typename Func>
void unroll (const Func& func) {
func();
}
template <std::size_t I1, std::size_t... Is, typename Func>
void unroll (const Func& func) {
hana::for_each(hana::range_c<std::size_t, 0, I1>,
[&](auto x) {
unroll<Is...>([x, &func] (auto... xs) { func(x,xs...); });
});
}