我有一个Vec3类。 如何替换像这样的循环是最好的方法:
for (int x = 20; x < 25; x++)
for (int y = 40; y < 45; y++)
for (int z = 2; z < 4; z++) doStuff({x,y,z});
使用类似以下的方式:
for(Vec3 v: Vec3range({20,40,2}, {25,45,4}))
doStuff(v);
没有任何运行时成本吗?
我有一个Vec3类。 如何替换像这样的循环是最好的方法:
for (int x = 20; x < 25; x++)
for (int y = 40; y < 45; y++)
for (int z = 2; z < 4; z++) doStuff({x,y,z});
使用类似以下的方式:
for(Vec3 v: Vec3range({20,40,2}, {25,45,4}))
doStuff(v);
没有任何运行时成本吗?
为此,我在我的函数库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) : ...)
这样的“自动绑定”语法。)
seq()
begin()
和 end()
函数。它们返回一个自定义迭代器,该迭代器在 operator++
中增加当前值并在 operator*
中返回它。
combine()
combine
的范围。此适配器的迭代器在元组成员中保持对包装范围的迭代器三次引用:当前位置、开始和结束,您很快就会明白原因。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;
}
}
template<size_t N>
using indexes=std::array<size_t,N>;
template<size_t N>
void advance( indexes<N>& in, indexes<N-1> const& limit, size_t amt=1 );
该函数查找距离当前位置amt个索引的元素,当到达限制时会循环。
然后编写一个范围对象。它存储了限制和两个迭代器b和e。begin
函数返回 b
,end
函数返回 e
。
这些迭代器有指向它们所来自的范围的指针和值数组。它们通过上面的next
方法实现自增运算符++
。编写通常的前向迭代器样板代码。
您的函数可能应该是:
template<size_t N>
multi_range_t<N> multi_range( indexes<N> start, indexes<N> finish );
这需要您通过N
。
最后为Vec3
从std::array<3,T>
编写一个拷贝构造函数。
您可能可以通过将其变成3而不是N
使它变得更加容易,但只是一点点。
#include <iostream>
#include <tuple>
using namespace std;
using tuple_3d = tuple<int, int, int>;
struct range_3d;
struct range_3d_iterator
{
const range_3d& c;
tuple_3d i;
bool operator!=(const range_3d_iterator& other)
{ return get<0>(i) != get<0>(other.i) && get<1>(i) != get<1>(other.i) && get<2>(i) != get<2>(other.i); }
tuple_3d operator*() const
{ return make_tuple(get<0>(i), get<1>(i), get<2>(i)); }
const range_3d_iterator& operator++();
};
struct range_3d
{
tuple_3d s;
tuple_3d e;
range_3d_iterator begin() const
{ return { *this, s }; }
range_3d_iterator end() const
{ return { *this, e }; }
};
const range_3d_iterator& range_3d_iterator::operator++()
{
++get<2>(i);
if (get<2>(i) == get<2>(c.e))
{
get<2>(i) = get<2>(c.s);
++get<1>(i);
if (get<1>(i) == get<1>(c.e))
{
get<1>(i) = get<1>(c.s);
++get<0>(i);
}
}
return *this;
}
int main(void)
{
for (auto&& v : range_3d{ make_tuple(20, 40, 2), make_tuple(25, 45, 4) })
cout << get<0>(v) << ' ' << get<1>(v) << ' ' << get<2>(v) << endl;
}
range_3d
是一个简单的类,支持begin()
,end()
获取循环范围,然后“智能”的部分在于range_3d_iterator
,它将迭代元组。鉴于我在这里使用元组的方式,将其扩展到任意维度是微不足道的...