【问题标题】:How to generate nested loops at compile time如何在编译时生成嵌套循环
【发布时间】:2016-11-18 12:30:47
【问题描述】:

我有一个整数N,我在编译时就知道了。我还有一个 std::array 保存描述 N 维数组形状的整数。我想在编译时使用元编程技术生成嵌套循环,如下所述。

constexpr int N {4};
constexpr std::array<int, N> shape {{1,3,5,2}};


auto f = [/* accept object which uses coords */] (auto... coords) { 
     // do sth with coords
}; 

// This is what I want to generate.
for(int i = 0; i < shape[0]; i++) {
     for(int j = 0; j < shape[1]; j++) {
          for(int k = 0; k < shape[2]; k++) {
                for(int l = 0; l < shape[3]; l++) {
                    f(i,j,k,l) // object is modified via the lambda function.
                }
          }
     }
}

注意参数 N 在编译时是已知的,但在编译之间可能会发生不可预测的变化,因此我不能像上面那样对循环进行硬编码。理想情况下,循环生成机制将提供一个接口,该接口接受 lambda 函数,生成循环并调用生成上述等效代码的函数。我知道可以在运行时用一个 while 循环和一组索引编写一个等效循环,并且已经有了这个问题的答案。但是,我对这个解决方案不感兴趣。我也对涉及预处理器魔法的解决方案不感兴趣。

【问题讨论】:

  • 你的编译器不是在优化开启时展开循环吗?
  • 他很可能会展开一些循环,但这不是重点。我问的是循环生成而不是循环展开。无论如何,数组可以保存比上面指定的整数大得多的值。一般来说,完全展开循环是不可能的。
  • 使用单循环并从单循环索引计算ijkl等。
  • @Cheersandhth.-Alf 在这种情况下,它没有展开。这里调用者创建了一个包装结构的实例,它在一个普通的运行时循环中调用下一个实例的成员函数,依此类推。因此,在实例化模板时,编译器不会展开循环。它相当递归地计算更深实例的模板参数。 Godboldt 的在线反汇编器证实了这一点:godbolt.org/g/4xGzoB。另一方面,如果您添加-O2(甚至没有-O3!),所有计算都将在编译时执行,代码归结为单个std::cout.operator&lt;&lt;()调用。
  • @Sergey:我想为上面的评论发布具体代码(使用单个循环),它更简单,但我制作的 i 变化最快,l 最慢,并且我没有时间修复它。或者我觉得我没有。也许你可以为我修好并发布它?在coliru.stacked-crooked.com/a/e073a4a40bc7a15e

标签: c++ templates c++11 metaprogramming c++14


【解决方案1】:

类似这样的东西(注意:我将“形状”作为可变参数模板参数集..)

#include <iostream>

template <int I, int ...N>
struct Looper{
    template <typename F, typename ...X>
    constexpr void operator()(F& f, X... x) {
        for (int i = 0; i < I; ++i) {
            Looper<N...>()(f, x..., i);
        }
    }
};

template <int I>
struct Looper<I>{
    template <typename F, typename ...X>
    constexpr void operator()(F& f, X... x) {
        for (int i = 0; i < I; ++i) {
            f(x..., i);
        }
    }
};

int main()
{
    int v = 0;
    auto f = [&](int i, int j, int k, int l) {
        v += i + j + k + l;
    };

    Looper<1, 3, 5, 2>()(f);

    auto g = [&](int i) {
        v += i;
    };

    Looper<5>()(g);

    std::cout << v << std::endl;
}

【讨论】:

  • 这不要求fconstexpr吗?
  • @Cheersandhth.-Alf - 呃,不这么认为.. 在 lambda 中粘贴 cout,应该仍然可以正常工作...
  • @Nim - 不错的解决方案; +1;我看到的唯一缺陷是您必须为每个 N 开发一个 lambda 函数(f()g() 等);我现在一般不怎么解决,但是如果你可以使用 C++14 并且 lamda 必须做一个简单的操作,比如参数的总和,你可以使用像 auto f = [](auto i, auto ... is) { auto ret = i; char unused[] { ((ret += is), '0')... }; return ret; }; 这样的可变参数 lambda
  • @Nim:我的意思是,为了让它生成调用在编译时。否则,这种增加的复杂性是不必要的。
  • @Cheersandhth.-Alf,我的理解(当然可能会被打破)是操作想要生成一个任意维度循环,其中维度的数量是已知的,但在编译时是可变的,范围是在编译时也知道,但是需要调用的函数是任意的。所以 constexpr 没有实际意义,它是生成任意维度循环的构造,而无需为 OP 所追求的所有维度显式手动编码各个循环?
【解决方案2】:

假设您不想完全展开循环,只需生成ijkf 的参数元组:

#include <stdio.h>
#include <utility>      // std::integer_sequence

template< int dim >
constexpr auto item_size_at()
    -> int
{ return ::shape[dim + 1]*item_size_at<dim + 1>(); }

template<> constexpr auto item_size_at<::N-1>() -> int { return 1; }

template< size_t... dim >
void call_f( int i, std::index_sequence<dim...> )
{
    f( (i/item_size_at<dim>() % ::shape[dim])... );
}

auto main()
    -> int
{
    int const n_items = ::shape[0]*item_size_at<0>();
    for( int i = 0; i < n_items; ++i )
    {
        call_f( i, std::make_index_sequence<::N>() );
    }
}

【讨论】:

    【解决方案3】:

    我想这正是你所要求的:

    #include <array>
    #include <iostream>
    
    constexpr int N{4};
    constexpr std::array<int, N> shape {{1,3,5,2}};
    
    // Diagnositcs
    
    template<typename V, typename ...Vals>
    struct TPrintf {
            constexpr static void call(V v, Vals ...vals) {
                    std::cout << v << " ";
                    TPrintf<Vals...>::call(vals...);
            }
    };
    
    template<typename V>
    struct TPrintf<V> {
            constexpr static void call(V v) {
                    std::cout << v << std::endl;
            }
    };
    
    
    template<typename ...Vals>
    constexpr void t_printf(Vals ...vals) {
            TPrintf<Vals...>::call(vals...);
    }
    
    // Unroll
    
    template<int CtIdx, typename F>
    struct NestedLoops {
            template<typename ...RtIdx>
            constexpr static void call(const F& f, RtIdx ...idx) {
                    for(int i = 0; i < shape[CtIdx]; ++i) {
                            NestedLoops<CtIdx + 1, F>::call(f, idx..., i);
                    }
            }
    };
    
    template<typename F>
    struct NestedLoops<N-1, F> {
            template<typename ...RtIdx>
            constexpr static void call(const F& f, RtIdx ...idx) {
                    for(int i = 0; i < shape[N-1]; ++i) {
                            f(idx..., i);
                    }
            }
    };
    
    template<typename F>
    void nested_loops(const F& f) {
            NestedLoops<0, F>::call(f);
    }
    
    int main()
    {
            auto lf = [](int i, int j, int k, int l) {
                    t_printf(i,j,k,l);
            };
    
            nested_loops(lf);
            return 0;
    }
    

    【讨论】:

      【解决方案4】:

      同一事物的另一个变体:

      template <size_t shape_index, size_t shape_size>
      struct Looper
      {
          template <typename Functor>
          void operator()(const std::array<int, shape_size>& shape, Functor functor)
          {
              for (int index = 0; index < shape[shape_index]; ++index)
              {
                  Looper<shape_index + 1, shape_size>()
                      (
                          shape,
                          [index, &functor](auto... tail){ functor(index, tail...); }
                      );
              }
          }
      };
      
      template <size_t shape_size>
      struct Looper<shape_size, shape_size>
      {
          template <typename Functor>
          void operator()(const std::array<int, shape_size>&, Functor functor)
          {
              functor();
          }
      };
      
      template <size_t shape_size, typename Functor>
      void loop(const std::array<int, shape_size>& shape, Functor functor)
      {
          Looper<0, shape_size>()(shape, functor);
      }
      

      使用示例:

      constexpr size_t N {4};
      
      constexpr std::array<int, N> shape {{1,3,5,2}};
      
      void f(int i, int j, int k, int l)
      {
          std::cout
              << std::setw(5) << i
              << std::setw(5) << j
              << std::setw(5) << k
              << std::setw(5) << l
              << std::endl;
      }
      
      // ...
      
      loop(shape, f);
      

      Live demo

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-09-30
        • 2021-10-21
        • 2017-10-05
        • 1970-01-01
        • 1970-01-01
        • 2020-06-27
        相关资源
        最近更新 更多