【问题标题】:Simplify a longer unrolled loop expression with constexpr or template metaprogramming使用 constexpr 或模板元编程简化更长的展开循环表达式
【发布时间】:2017-07-11 14:11:35
【问题描述】:

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

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

【问题讨论】:

  • 您可以从部分特化 poly 开始,因为在您的情况下,它的第一个参数始终是 2
  • @iehrlich 但您不能部分特化函数模板...
  • 我注意到如果可以专门化一个函数会更简单
  • @Quentin 但你可以写template&lt;int J&gt; float poly2(float in) { return poly&lt;2, J&gt;(); }
  • @iehrlich 超载了——足够接近 :)

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


【解决方案1】:
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)

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

或类似的。

【讨论】:

  • 看起来不错,但是有c++11的约束
  • @dgrat 添加了关于如何转换为 C++11 的注释。烦人的部分是自动 lambda 必须重写为模板仿函数。
  • 我想,我需要一些时间来理解这个解决方案。看起来一些编译器也有问题。但是,我对模板变得多么复杂感到印象深刻:D 不过,我认为这对大脑来说是一个很好的练习。
  • @dgrat 将 X 替换为 decltype(X)::value 在传递给某些 C++ 编译器时会有所帮助。这个解决方案在 C++11 中会非常庞大​​,这就是我在 C++14 中编写它的原因。
猜你喜欢
  • 1970-01-01
  • 2021-11-20
  • 2016-12-03
  • 2017-01-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多