用于多维C数组的C ++迭代器

时间:2014-03-15 12:41:06

标签: c++ multidimensional-array iterator boost-iterators

我需要迭代大量的3到6维C数组。像boost :: multi_array这样的更多C ++表示不是一个选项,因为这些数组来自C框架PETSc(使用fortran排序,因此是向后索引)。直截了当的循环最终看起来像这样:

  for (int i=range.ibeg; i<=range.iend; ++i){
   for (int j=range.jbeg; j<=range.jend; ++j){
     for (int k=range.kbeg; k<=range.kend; ++k){
       (...)

甚至更糟:

  for (int i=range.ibeg-1; i<=range.iend+1; ++i){
    for (int j=range.jbeg-1; j<=range.jend+1; ++j){
      for (int k=range.kbeg-1; k<=range.kend+1; ++k){
       for (int ii=0; ii<Np1d; ++ii){
        for (int jj=0; jj<Np1d; ++jj){
         for (int kk=0; kk<Np1d; ++kk){
           data[k][j][i].member[kk][jj][ii] = 
            func(otherdata[k][j][i].member[kk][jj][ii],
                 otherdata[k][j][i].member[kk][jj][ii+1]);

有很多像这样的实例,循环索引的范围不同,而且它们都非常丑陋并且可能容易出错。如何为这样的多维数组构造迭代器?

2 个答案:

答案 0 :(得分:3)

在嵌套for循环的简单情况下,不需要完整的n维迭代器。由于只需要一次遍历,一个简单的计数器就足够了,可以像这样轻松定制:

template<typename T, size_t N>
class counter
{
    using A = std::array<T, N>;
    A b, i, e;

public:
    counter(const A& b, const A& e) : b(b), i(b), e(e) { }

    counter& operator++()
    {
        for (size_t n = 0; n < N; ++n)
        {
            if (++i[n] == e[n])
            {
                if (n < N - 1)
                    i[n] = b[n];
            }
            else
                break;
        }

        return *this;
    }

    operator bool() { return i[N - 1] != e[N - 1]; }

    T&       operator[](size_t n)       { return i[n]; }
    const T& operator[](size_t n) const { return i[n]; }
};

这样就可以很容易地使用这个计数器:

int main()
{
    constexpr size_t N = 3;
    using A = std::array<int, N>;

    A begin = {{0, -1,  0}};
    A end   = {{3,  1,  4}};

    for (counter<int, N> c(begin, end); c; ++c)
        cout << c << endl;
        // or, cout << c[0] << " " << c[1] << " " << c[3] << endl;
}

假设<<有一个运算符counter。有关完整代码,请参阅live example

最里面的条件if (n < N - 1)说明能够检查终止,并且不能总是检查。对我来说,如何将其分解出来并不是那么明显,但无论如何它只会在我们前进到计数器的下一个“数字”时发生,而不是在每次递增操作时发生。

如果c[0], c[1], c[2]派生std::get而不是使用成员counterstd::array,则使用i代替使用b,e等。 1}}保持成员)。这个想法可以扩展到operator++operator bool)的编译时递归实现,它将消除其中的for循环,以及上面讨论的有问题的检查。在这种情况下,operator[]将被丢弃。但所有这些都会使counter代码变得更加模糊,我只想突出这个想法。它还会使counter的使用更加冗长,但这是你需要为效率付出的代价。

当然,可以通过使用更多方法和特征扩展counter来构建完整的n维迭代器。但要使其足够通用可能是一项艰巨的任务。

答案 1 :(得分:1)

完全模板化的版本毕竟不是那么难,所以这里是一个单独的答案,同样是live example。如果我没有弄错,那么在自定义嵌套循环之上应该没有任何开销。你可以测量并让我知道。无论如何,我打算为了自己的目的实现这个,这就是为什么我把这个努力放在这里。

template<size_t N>
using size = std::integral_constant<size_t, N>;

template<typename T, size_t N>
class counter : std::array<T, N>
{
    using A = std::array<T, N>;
    A b, e;

    template<size_t I = 0>
    void inc(size<I> = size<I>())
    {
        if (++_<I>() != std::get<I>(e))
            return;

        _<I>() = std::get<I>(b);
        inc(size<I+1>());
    }

    void inc(size<N-1>) { ++_<N-1>(); }

public:
    counter(const A& b, const A& e) : A(b), b(b), e(e) { }

    counter& operator++() { return inc(), *this; }

    operator bool() const { return _<N-1>() != std::get<N-1>(e); }

    template<size_t I>
    T& _() { return std::get <I>(*this); }

    template<size_t I>
    constexpr const T& _() const { return std::get <I>(*this); }
};

而不是operator[]我现在有方法_(随意重命名),这只是std::get的快捷方式,所以使用方法并不比使用{{operator[]更简洁。 1}}:

    for (counter<int, N> c(begin, end); c; ++c)
        cout << c._<0>() << " " << c._<1>() << " " << c._<2>() << endl;

事实上,您可以尝试以前的版本

    for (counter<int, N> c(begin, end); c; ++c)
        cout << c[0] << " " << c[1] << " " << c[2] << endl;

并测量,因为它可能是等价的。要实现此目的,请将std::array继承切换为public或在using A::operator[];的{​​{1}}部分中声明counter

明确不同的是public,它现在基于递归模板函数operator++,并且有问题的条件inc()被没有开销的特化(实际上是重载)所取代

如果事实证明最终有开销,最终的尝试是将if (n < N - 1)替换为std::array。在这种情况下,std::tuple是唯一的方法;没有std::get替代方案。类型operator[]重复T次也很奇怪。但我希望不需要这样做。

可以进一步概括,例如指定每个维度的(编译时)增量步骤,甚至指定每个维度的任意间接数组,例如:模拟

N

以类似Matlab的语法。

但这需要更多的工作,如果您喜欢这种方法,我认为您可以从这里开始。