使用 constexpr 或模板元编程简化较长的展开循环表达式

Simplify a longer unrolled loop expression with constexpr or template metaprogramming

本文关键字:表达式 循环 constexpr 编程 使用      更新时间:2023-10-16

看起来我有一个更长的表达式(展开循环(,例如下面的代码在一段软件中多次出现,这使它膨胀了几千行。

由于poly采用模板参数来提高性能(第二个参数对应于循环 z 值(,我想知道是否可以通过模板元编程和通过递归构建类似循环的东西来简化下面的代码。表达式的语法似乎是针对每个x=bx (a + b + c * by * bz) + ..

我想,如果poly不是模板函数,而是采用函数参数,那会更容易。

void calc(float mat[3][3][3], float fS, float fT, float fU)
{
const float bs20_u = poly<2, 0>(fU);
const float bs21_u = poly<2, 1>(fU);
const float bs22_u = poly<2, 2>(fU);
const float bs20_s = poly<2, 0>(fS);
const float bs21_s = poly<2, 1>(fS);
const float bs22_s = poly<2, 2>(fS);
const float bs20_t = poly<2, 0>(fT);
const float bs21_t = poly<2, 1>(fT);
const float bs22_t = poly<2, 2>(fT);
float result = 
((mat[0][0][0] * bs20_u + mat[0][0][1] * bs21_u + mat[0][0][2] * bs22_u) * bs20_t
+ (mat[0][1][0] * bs20_u + mat[0][1][1] * bs21_u + mat[0][1][2] * bs22_u) * bs21_t
+ (mat[0][2][0] * bs20_u + mat[0][2][1] * bs21_u + mat[0][2][2] * bs22_u) * bs22_t)
* bs20_s
+
((mat[1][0][0] * bs20_u + mat[1][0][1] * bs21_u + mat[1][0][2] * bs22_u) * bs20_t
+ (mat[1][1][0] * bs20_u + mat[1][1][1] * bs21_u + mat[1][1][2] * bs22_u) * bs21_t
+ (mat[1][2][0] * bs20_u + mat[1][2][1] * bs21_u + mat[1][2][2] * bs22_u) * bs22_t)
* bs21_s
+
((mat[2][0][0] * bs20_u + mat[2][0][1] * bs21_u + mat[2][0][2] * bs22_u) * bs20_t
+ (mat[2][1][0] * bs20_u + mat[2][1][1] * bs21_u + mat[2][1][2] * bs22_u) * bs21_t
+ (mat[2][2][0] * bs20_u + mat[2][2][1] * bs21_u + mat[2][2][2] * bs22_u) * bs22_t)
* bs22_s;
}
template<std::size_t...Is>
auto index_over(std::index_sequence<Is...>) {
return [](auto&& f)->decltype(auto){
return decltype(f)(f)( std::integral_constant< std::size_t, Is >{}... );
};
}
template<std::size_t N>
auto index_upto(std::integral_constant<std::size_t, N> ={}) {
return index_over( std::make_index_sequence<N>{} );
}
inline float sum() { return 0.0f; }
template<class...Args>
float sum( float a, Args... args ) {
return a + sum(args...);
}

我认为那些人应该这样做。

auto Z_f = [&](auto X, auto Y)->float {
return index_upto<2>()( [&](auto...Zs)->float{
return sum((poly<2,Zs>(fU) * mat[X][Y][Zs])...);
});
};
auto Y_f = [&](auto X)->float {
return index_upto<2>()( [&](auto...Ys)->float{
return sum( (poly<2,Ys>(fT) * Z_f(X, Ys))... );
});
};
auto X_f = [&]()->float {
return index_upto<2>()( [&](auto...Xs)->float{
return sum( (poly<2,Xs>(fS) * Y_f(Xs))... );
});
};
float val = X_f();

不确定这是否更简短,但也许通过更多的工作,我们可以将X_fY_fZ_f重构为一个函数。

Clang能够通过将常量输入到常量值中来优化这一点。

这使用了一些C++14结构(index_sequencemake_index_sequence(,可以在C++11中轻松重新实现。

我使用auto参数来制作模板 lambda,再次C++14。 在 C++11 中执行此操作需要您手动编写所述 lambda,这很痛苦。

sum可以写成C++17中的(0.f + ... + args)

显示它运行的现场示例。

Godbolt 显示它编译为一个常量。

如果你不需要保留确切的操作顺序,并且愿意有更多的乘法,我们可以生成一个情况,我们得到所有 3 个 X、Y 和 Z,然后调用一个具有编译时常量的目标并将结果相加。

auto contribution = [&](auto X, auto Y, auto Z) {
return mat[X][Y][Z] * poly<2,X>(fS) * poly2<2,Y>(fT) * poly2<2,Z>(fU);
};

但是我在一行中执行此操作时遇到问题,因为您最终会得到 3 个要单独扩展的活动包。

auto summer_1d = [](auto...Vals)->decltype(auto){
return sum(Vals...);
};
template<std::size_t X_max, std::size_t Y_max, std::size_t Z_max, class Sum = decltype(summer_1d)>
auto sumup_3d(Sum sum = summer_1d) {
return [](auto&& f)->decltype(auto) {
auto Z_part = [&](auto X, auto Y)->decltype(auto) {
return index_upto<Z_max>()([&](auto...Zs)->decltype(auto){
return sum( f(X,Y,Zs)... );
});
};
auto Y_part = [&](auto X)->decltype(auto) {
return index_upto<Y_max>()([&](auto...Ys)->decltype(auto){
return sum( Z_part(X, Ys)... );
});
};
return index_upto<X_max>()([&](auto...Xs)->decltype(auto){
return sum( Y_part(Xs)... );
});
};
};
auto val = sumup_3d<3,3,3>()(contribution);

或诸如此类。