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

时间:2017-07-11 14:11:35

标签: c++ c++11 templates template-meta-programming

看起来我有一个更长的表达式(展开的循环),例如下面的代码在一个软件中多次膨胀数千行。

由于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;
}

1 个答案:

答案 0 :(得分:2)

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参数来制作模板lambdas,再次使用C ++ 14。在C ++ 11中执行此操作需要您手动编写所述lambdas,这很痛苦。

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

Live example显示它正在运行。

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);

或某些。