我需要迭代大量的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]);
有很多像这样的实例,循环索引的范围不同,而且它们都非常丑陋并且可能容易出错。如何为这样的多维数组构造迭代器?
答案 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
而不是使用成员counter
(std::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的语法。
但这需要更多的工作,如果您喜欢这种方法,我认为您可以从这里开始。