为此,我在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 循环没有“自动绑定”。)
实施:
这很简单:它是一个返回具有begin() 和end() 函数的适配器对象的函数。它们返回一个自定义迭代器,该迭代器递增 operator++ 中的当前值并在 operator* 中返回它。
这更有趣:它返回一个适配器对象,该对象在元组成员中保存作为参数提供给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”条件开始。