3

我有大量需要迭代的 3 到 6 维 C 数组。像 boost::multi_array 这样的更多 C++'y 表示不是一个选项,因为这些数组来自 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]);

有很多这样的例子,循环索引的范围不同,这一切都变得非常丑陋并且可能容易出错。应该如何为这样的多维数组构造迭代器?

4

2 回答 2

3

毕竟,完全模板化的版本并没有那么难,所以这里有一个单独的答案,再次带有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,所以用法并不比 with 更冗长operator[]

    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继承切换到publicusing A::operator[];counter'spublic部分中声明。

绝对不同的是operator++,它现在基于递归模板函数inc(),并且有问题的条件if (n < N - 1)被替换为没有开销的专门化(实际上是重载)。

如果事实证明最终存在开销,那么最终的尝试将是替换std::arraystd::tuple. 在这种情况下,std::get是唯一的方法;没有operator[]其他选择。T类型重复多次也会很奇怪N。但我希望这不是必需的。

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

a([3 5 0 -2 7], -4:2:20)

在类似 Matlab 的语法中。

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

于 2014-03-15T16:03:34.953 回答
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. 查看完整代码的实时示例

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

与使用c[0], c[1], c[2]等相比,使用std::getifcounter派生std::array而不是拥有成员i(而b,e保持成员)更有效。这个想法可以扩展到operator++operator bool以及)的编译时递归实现,这将消除其中的for循环,以及上面讨论的有问题的检查。operator[]在这种情况下将被丢弃。但这一切都会让counter代码变得更加晦涩难懂,我只是想强调一下这个想法。它也会使使用counter更加冗长,但这是您需要为效率付出的代价。

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

于 2014-03-15T13:22:15.870 回答