【问题标题】:C++ Unrolled loop at compile time编译时的 C++ 展开循环
【发布时间】:2021-10-04 15:56:02
【问题描述】:

我的 c++ 项目中有几段代码如下所示:

system<0>::global_instance.do_something();
system<1>::global_instance.do_something();
system<2>::global_instance.do_something();
system<3>::global_instance.do_something();
//...
system<29>::global_instance.do_something();
system<30>::global_instance.do_something();
system<31>::global_instance.do_something();

这似乎有点重复,对吧?如果我能做这样的事情就好了:

for(int i = 0; i < 32; i++)
{
  system<i>::global_instance.do_something();
}

很遗憾,这不起作用,因为i 的值在编译时是未知的,因此不能用作system 的模板参数。

我需要的是一种在编译时展开或展开循环的方法。

我见过一些使用模板来实现展开的实现,但它们不适用于我想要做的事情。理想情况下,循环展开将在预处理期间进行,并且可以将任意语句作为输入。

例如,这个:

#unroll NUM 0 5
foo<NUM>();
#endunroll

会被翻译成这样:

foo<0>();
foo<1>();
foo<2>();
foo<3>();
foo<4>();
foo<5>();

还有这个:

#unroll NUM 0 5
blah blah blah NUM
#endunroll

会被翻译成这样:

blah blah blah 0
blah blah blah 1
blah blah blah 2
blah blah blah 3
blah blah blah 4
blah blah blah 5

即使blah blah blah NUM 是一个语法错误的语句,它仍应被复制,并且令牌 NUM 的每个实例都应替换为适当的数值。

有没有办法在 vanilla c++ 中实现这一点?或者是否有任何特殊的 c++ 编译器可以添加此功能?

编辑:我在 cmets 中找到了来自 @HTNW 的答案。

#include <iostream>
using namespace std;

template<auto begin, auto end>
inline void unroll(auto f)
{
  if constexpr(begin < end)
  {
    f.template operator()<begin>();
    unroll<begin + 1, end>(f);
  }
}

template<int NUM>
struct thingy
{
  static void print(){cout << NUM << endl;}
};

int main()
{
  unroll<0,8>([]<int i>()
  {
    thingy<i>::print();
  });
}

输出:

0
1
2
3
4
5
6
7

完成这项工作的行是f.template operator()&lt;begin&gt;();。我不知道这条线是什么意思,而且我以前从未见过它。如果有人能给我解释一下,那就太好了。

【问题讨论】:

    标签: c++ metaprogramming template-meta-programming


    【解决方案1】:

    您可以使用std::index_sequence 和折叠表达式,例如:

    #include <iostream>
    #include <utility>
    
    template <size_t x>
    void foo ()
    {
        std::cout << x << " ";
    }
    
    template <size_t... indices>
    void unroll (std::index_sequence <indices...>)
    {
        (foo <indices> (), ...);
    }
    
    int main ()
    {
        unroll (std::make_index_sequence <32> {});
    }
    

    输出:

    0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
    

    更新:根据 HTNW 的评论,在 C++20 中,您可以将模板 lambda 作为附加参数传递给 unroll,以避免对 foo 的调用进行硬编码:

    #include <iostream>
    #include <utility>
    
    template <size_t x>
    void foo ()
    {
        std::cout << x << " ";
    }
    
    template <size_t... indices>
    void unroll (auto f, std::index_sequence <indices...>)
    {
        (f.template operator () <indices> (), ...);
    }
    
    int main ()
    {
        unroll ([] <size_t i> () { foo <i> (); }, std::make_index_sequence <32> {});
    }
    

    【讨论】:

      【解决方案2】:

      我不确定这是否对您有好处,但您可以在 c++17 中执行类似的操作:

      template <size_t curr, size_t max>
      void unroll(){
          doSomething<curr>();
          if constexpr (curr < max)
              unroll<curr+1,max>();
      }
      

      或者正如 HTNW 指出的那样,您可以这样做:

      template <size_t curr, size_t max>
      void unroll(auto f){
          f.template operator()<curr>(); // See below
          if constexpr (curr < max)
              unroll<curr + 1,max>(f);
      }
      

      我不得不调整建议,但这应该适用于 c++20。然后您可以将其称为:

      unroll<0,20>([]<size_t i> () { system<curr>::do_something(); });
      

      关于为什么需要f.template operator()&lt;curr&gt;();,请参阅this

      【讨论】:

      • 老实说,我有一半希望看到一个声称有一些我不知道的 for constexpr 表达式的答案,但是是的,这非常接近。
      • 在 C++20 中,您应该能够将函子 auto f 作为参数,然后将其称为 f&lt;curr&gt;();,而不是将自己固定为 doSomething。喜欢template&lt;std::size_t End, std::size_t Start = 0&gt; /* __attribute__((always_inline)) */ void repeat_for(auto f) { if constexpr(Start &lt; End) { f.template operator()&lt;Start&gt;(); repeat_for&lt;End, Start + 1&gt;(f); } }。然后就可以得到repeat_for&lt;32&gt;([]&lt;std::size_t i&gt;() { system&lt;i&gt;::global_instance.do_something(); });
      • @HTNW 这看起来不错,但出于某种原因g++ 不喜欢它。它在f&lt;Start&gt;(); 处引发错误。我可能缺少一些标志,但据我所知-std=c++20 应该足够了。
      • @HTNW 添加了您的建议。不幸的是,显式指定模板参数的语法比f&lt;i&gt;()复杂得多。
      • @Lala5th 我在评论works as-is 中的建议是我在您发表评论之前所做的编辑。您的编辑版本似乎不太正确,因为您没有将i 传递给system
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-08
      • 2011-12-03
      • 2011-09-02
      • 1970-01-01
      • 2016-08-03
      • 1970-01-01
      相关资源
      最近更新 更多