【问题标题】:Replacing Multi-Dimension for-loop with range-based for-loop用基于范围的 for 循环替换多维 for 循环
【发布时间】:2014-10-22 18:28:11
【问题描述】:

我有一个 Vec3 课程。 替换循环的最佳方法是什么

for (int x = 20; x < 25; x++)
    for (int y = 40; y < 45; y++)
        for (int z = 2; z < 4; z++) doStuff({x,y,z});

类似这样的:

for(Vec3 v: Vec3range({20,40,2}, {25,45,4}))
    doStuff(v);

没有任何运行时成本?

【问题讨论】:

  • Vec3range 需要是带有自定义迭代器的自定义容器类型(带有开始、结束等)。但是,因为这需要具有更改内部变量的中间类,这将阻止编译器展开循环,因此从长远来看它可能会变慢。老实说,恕我直言,它的可读性要差得多。
  • 我支持@MadScienceDreams - 通常不会以这种方式操纵矢量,因此这种方法(虽然更简洁)没有解释就没有多大意义。如果您不喜欢循环读取的方式和/或创建一个宏来制作这种循环,如果您经常需要它们并且不喜欢进行大量输入,那么 IMO 最好的选择是使用您的代码格式。
  • 我刚刚写了一个可行的解决方案,但是哇,它很难看。根据@MadScienceDreams,无论如何它肯定会更慢。慢+丑?听起来像是赢家!
  • @Conduit 用于在 3d 地图中创建元素,有很多这样的循环用于创建地板等。(github)

标签: c++ c++11 for-loop


【解决方案1】:

为此,我在functional library fn 中编写了一个迭代 和一个组合 适配器:

#include <fn.h>
#include <iostream>

int main() {
    using std; using fn;

    for (auto &&values : combine(seq(20,25), seq(40,45), seq(2,4))) {
        int x, y, z; 
        tie(x, y, z) = values;
        cout << x << ", " << y << ", " << z << "\n";
        // or in your case: doStuff({x, y, z});
    }
}

输出:

20, 40, 2
20, 40, 3
20, 41, 2
20, 41, 3
...
24, 43, 2
24, 43, 3
24, 44, 2
24, 44, 3

这里,seq(a, b) 返回一个隐式范围,该范围遍历值[a, b)(即第一个是包含的,第二个是排除的)。 (第三个参数可以指定步骤,并且存在更复杂的替代方案以更好地控制迭代。)

函数combine(ranges...) 返回一个隐式范围,该范围迭代所有组合给定范围(其中第一个被认为是“最重要的”一个,类似于您的“最外层”循环)。它的迭代器取消了对持有当前组合的std::tuple 的引用。

这个元组然后在循环体中绑定到一些变量。 (遗憾的是,像 for(tie(auto x, auto y, auto z) : ...) 这样的基于范围的 for 循环没有“自动绑定”。)


实施:

seq()

这很简单:它是一个返回具有begin()end() 函数的适配器对象的函数。它们返回一个自定义迭代器,该迭代器递增 operator++ 中的当前值并在 operator* 中返回它。

combine()

这更有趣:它返回一个适配器对象,该对象在元组成员中保存作为参数提供给combine 的范围。此适配器的迭代器将迭代器保存到元组成员中的包装范围,但有 3 次:当前位置、开始和结束,您很快就会明白为什么。

迭代器的operator++ 很可能是最有趣的一个:它是使用variadic_ops.h, va_next_combination() 中的可变参数模板递归实现的,并且给定了三组迭代器(对于每个范围,当前、开始和结束):

// base case
inline bool va_next_combination() {
    return true;
}

// recursive case
template<typename Head, typename ...Tail>
inline bool va_next_combination(std::tuple<Head&,Head&,Head&> && curr_and_begin_and_end, std::tuple<Tail&,Tail&,Tail&> &&...t) {
    // advance the "tail" to its next combination and check if it had an overflow
    if (va_next_combination(std::forward<std::tuple<Tail&,Tail&,Tail&>>(t)...)) {
        // advance the "head" iterator
        ++std::get<0>(curr_and_begin_and_end);
        // check if the "head" just overflow
        bool at_end = (std::get<0>(curr_and_begin_and_end) == std::get<2>(curr_and_begin_and_end));
        // if it did, put it back to the beginning and report the overflow
        if (at_end) std::get<0>(curr_and_begin_and_end) = std::get<1>(curr_and_begin_and_end);
        return at_end;
    } else {
        // "tail" didn't overflow, so we do nothing and no overflow should be reported
        return false;
    }
}

从集合中最右边的迭代器开始,它递增迭代器。如果它刚刚到达范围的末尾,它会将其报告为递归函数的返回值。下一个迭代器检查该值,如果它是真的,它本身需要前进(否则不)以及“重置”右边的迭代器(即“环绕”它的溢出),最后它向在左边。

这基本上就是机械计数器的工作方式,如果您从最深递归级别的“if”条件开始。

【讨论】:

    【解决方案2】:

    这是我能管理的最简单的实现:

    #include <iostream>
    #include <tuple>
    
    using namespace std;
    
    using tuple_3d = tuple<int, int, int>;
    
    struct range_3d;
    
    struct range_3d_iterator
    {
      const range_3d& c;
      tuple_3d i;
    
      bool operator!=(const range_3d_iterator& other)
      { return get<0>(i) != get<0>(other.i) && get<1>(i) != get<1>(other.i) && get<2>(i) != get<2>(other.i); }
    
      tuple_3d operator*() const
      { return make_tuple(get<0>(i), get<1>(i), get<2>(i)); }
    
      const range_3d_iterator& operator++();
    };
    
    struct range_3d
    {
      tuple_3d s;
      tuple_3d e;
    
      range_3d_iterator begin() const
      { return { *this, s }; }
    
      range_3d_iterator end() const
      { return { *this, e }; }
    };
    
    const range_3d_iterator& range_3d_iterator::operator++()
    {
      ++get<2>(i);
      if (get<2>(i) == get<2>(c.e))
      {
        get<2>(i) = get<2>(c.s);
        ++get<1>(i);
        if (get<1>(i) == get<1>(c.e))
        {
          get<1>(i) = get<1>(c.s);
          ++get<0>(i);
        }
      }
      return *this;
    }
    
    int main(void)
    {
      for (auto&& v : range_3d{ make_tuple(20, 40, 2), make_tuple(25, 45, 4) })
        cout << get<0>(v) << ' ' << get<1>(v) << ' ' << get<2>(v) << endl;  
    }
    

    命名有点废话,但除此之外,概念很简单。 range_3d 是一个简单的类,它支持 begin()end() 以使循环工作的范围,然后“智能”在 range_3d_iterator 中,它将迭代元组。鉴于我在这里使用元组的方式,扩展到任意维度是微不足道的......

    TBH,原来的 for 循环很清楚……IMO!

    【讨论】:

    • 谢谢!这很好用。不知道我是要使用它还是只是保持正常循环,或者写一个宏。
    【解决方案3】:
    template<size_t N>
    using indexes=std::array<size_t,N>;
    
    template<size_t N>
    void advance( indexes<N>& in, indexes<N-1> const& limit, size_t amt=1 );
    

    找到索引 amt 更远,在限制处环绕。

    然后写一个范围对象。它存储限制和两个迭代器,b 和 e。 begin 返回 bend e

    迭代器有一个指向它们来自的范围的指针和一个值数组。他们++ 通过next 上面。编写通常的前向迭代器样板。

    你的功能可能应该是:

    template<size_t N>
    multi_range_t<N> multi_range( indexes<N> start, indexes<N> finish );
    

    这需要你通过N

    最后从std::array&lt;3,T&gt;Vec3写一个copy ctor。

    您可以通过将其设为 3 而不是 N 来使其更容易一点,但只需一点点。

    【讨论】:

      猜你喜欢
      • 2011-06-23
      • 2016-10-31
      • 1970-01-01
      • 1970-01-01
      • 2014-12-06
      • 1970-01-01
      • 2014-01-12
      • 2013-01-04
      • 1970-01-01
      相关资源
      最近更新 更多