将多个for循环组合成单个迭代器

LKl*_*vin 17 c++ loops c++14

说我有一个像窝一样的循环

for (int x = xstart; x < xend; x++){
    for (int y = ystart; y < yend; y++){
        for (int z = zstart; z < zend; z++){
            function_doing_stuff(std::make_tuple(x, y, z));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

并希望将其转化为

MyRange range(xstart,xend,ystart,yend, zstart,zend);
for (auto point : range){
    function_doing_stuff(point);
}
Run Code Online (Sandbox Code Playgroud)

我如何编写MyRange类与嵌套for循环一样高效?这样做的动机是能够使用std算法(例如转换,累积等),并创建主要与维度无关的代码.

通过使用迭代器,可以轻松创建在1d,2d或3d点范围内运行的模板化函数.

代码库目前是C++ 14.

编辑:

写清楚的问题很难.我会试着澄清一下.我的问题不是写一个迭代器,我能做到.相反,问题是性能问题:是否有可能使迭代器与嵌套for循环一样快?

Jar*_*d42 6

使用range/v3,您可以这样做

auto xs = ranges::view::iota(xstart, xend);
auto ys = ranges::view::iota(ystart, yend);
auto zs = ranges::view::iota(zstart, zend);
for (const auto& point : ranges::view::cartesian_product(xs, ys, zs)){
    function_doing_stuff(point);
}
Run Code Online (Sandbox Code Playgroud)

  • 简洁明了的解决方案.可悲的是,与嵌套for循环相比,速度非常糟糕.迭代器方法为~3000ns,琐碎for循环为44ns. (5认同)

sch*_*312 5

您可以将自己的课程介绍为

class myClass {
  public:
    myClass (int x, int y, int z):m_x(x) , m_y(y), m_z(z){};
  private: 
    int m_x, m_y, m_z;

}
Run Code Online (Sandbox Code Playgroud)

然后std::vector<myClass>用你的三重循环初始化a

std::vector<myClass> myVec;
myVec.reserve((xend-xstart)*(yend-ystart)*(zend-zstart)); // alloc memory only once;
for (int x = ystart; x < xend; x++){
    for (int y = xstart; y < yend; y++){ // I assume you have a copy paste error here
        for (int z = zstart; z < zend; z++){
            myVec.push_back({x,y,z})
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,你可以使用所有漂亮的std算法std::vector<myClass> myVec.用语法糖

using MyRange = std::vector<MyClass>;
Run Code Online (Sandbox Code Playgroud)

MyRange makeMyRange(int xstart, int xend, int ystart, int yend, int zstart,int zend) {
    MyRange myVec;
    // loop from above
    return MyRange;
}
Run Code Online (Sandbox Code Playgroud)

你可以写

const MyRange range = makeMyRange(xstart, xend, ystart, yend, zstart, zend);
for (auto point : range){
    function_doing_stuff(point);
}
Run Code Online (Sandbox Code Playgroud)

使用新的移动语义,这不会创建不需要的副本.请注意,此功能的界面相当糟糕.也许宁愿使用3对int,表示x,y,z间隔.

也许您将名称更改为有意义的名称(例如,myClass可能是Point).


Cal*_*eth 5

另一个直接移植任何循环代码的选项是使用Coroutine.这是yield从Python或C#模拟的.

using point = std::tuple<int, int, int>;
using coro = boost::coroutines::asymmetric_coroutine<point>;

coro::pull_type points(
    [&](coro::push_type& yield){
        for (int x = xstart; x < xend; x++){
            for (int y = ystart; y < yend; y++){
                for (int z = zstart; z < zend; z++){
                    yield(std::make_tuple(x, y, z));
                }
            }
        }
    });

for(auto p : points)
    function_doing_stuff(p);
Run Code Online (Sandbox Code Playgroud)


Kev*_*inZ 3

由于您关心性能,因此在可预见的将来您应该忘记组合迭代器。核心问题是编译器还无法理清混乱并找出其中有 3 个自变量,更不用说执行任何循环交换或展开或融合。

如果必须使用范围,请使用编译器可以识别的简单范围:

for (int const x : boost::irange<int>(xstart,xend))
    for (int const y : boost::irange<int>(ystart,yend))
        for (int const z : boost::irange<int>(zstart,zend))
            function_doing_stuff(x, y, z);
Run Code Online (Sandbox Code Playgroud)

或者,您实际上可以将函子和提升范围传递给模板:

template <typename Func, typename Range0, typename Range1, typename Range2>
void apply_ranges (Func func, Range0 r0, Range1 r1, Range2 r2)
{
     for (auto const i0 : r0)
         for (auto const i1 : r1)
             for (auto const i2 : r2)
                 func (i0, i1, i2);
}
Run Code Online (Sandbox Code Playgroud)

如果您真正关心性能,那么您不应该使用复杂的范围来扭曲您的代码,因为当您想在 AVX 内在函数中重写它们时,它们会让您更难理清它们。