[英]Iterate over direction enum
我有一个带有6个方向条目(N,NE,SE,S,SW,NW)的方向枚举,它们基本上是图形中节点的边缘。 我经常需要遍历所有邻居,这些邻居当前通过仅使用int从0-> 5迭代来完成。
有时候还需要从例如2-> 1的环绕中进行迭代,其中当前通过从2-> 2 + 5迭代并在使用时将其调整为6来完成。
有什么我可以做的更安全/更容易使用而不会失去性能? 具有固定整数范围的for循环可以展开,内联等。基于矢量的方法不能(在枚举内使用静态const向量)
我已经在以下方案中使用枚举:
struct Direction{
enum Type{
N, NE, ...
}
unsigned COUNT = ...;
Type t_;
operator Type(){return t_;}
Direction(Type t):t_(t){assert(N<=t<COUNT);}
explicit Direction(unsigned t):t_(t%COUNT){}
static Direction fromUInt(unsigned t){return Direction(Type(t));}
}
所以我想要的是拥有允许在整个集合上有效迭代的迭代器,并允许这个任意起始点,在这种情况下迭代器会包装。
怎么能写这个呢? 我无法想象任何事情,因为我会有例如start = end的整个循环,这将是无效的。 或者我应该只有start = givenStartType,end = start + COUNT并在每个迭代器deref上做一个模数?
不幸的是,没有C ++ 11允许
编辑以回应澄清
你想在每个取消引用上采用迭代器模数COUNT
是一个很好的想法。 请参阅下面的反向迭代器/可迭代组合。 我用clang -O3
编译后检查了程序集输出。 编译器展开循环。 输出为2 1 0 5 4 3
。 您可以实现前向迭代器,或将方向作为参数。 您还可以将其作为枚举类型的模板。
不幸的是,从使用语法的角度来看,我不认为这会在do
- while
循环中为你买得那么多,如另一个答案所示 - 至少在C ++ 11之前是这样。 它确实隐藏了各种索引变量,并帮助您避免错误,但它更加冗长。
#include <iostream>
struct Direction {
enum Type {N, NE, SE, S, SW, NW};
static const unsigned COUNT = 6;
Type t_;
operator Type() { return t_; }
Direction(Type t) : t_(t) { }
explicit Direction(unsigned t) : t_(Type(t % COUNT)) { }
};
struct ReverseIterable {
const unsigned start_;
struct iterator {
unsigned offset_;
explicit iterator(unsigned offset) : offset_(offset) { }
Direction operator *() { return Direction(offset_); }
iterator& operator++() { --offset_; return *this; }
bool operator ==(const iterator &other)
{ return offset_ == other.offset_; }
bool operator !=(const iterator &other) { return !(*this == other); }
};
explicit ReverseIterable(Direction start) : start_(start) { }
iterator begin() { return iterator(start_ + Direction::COUNT); }
iterator end() { return iterator(start_); }
};
int main()
{
ReverseIterable range = ReverseIterable(Direction::SE);
for (ReverseIterable::iterator iterator = range.begin();
iterator != range.end(); ++iterator) {
std::cout << (int)*iterator << " ";
}
std::cout << std::endl;
return 0;
}
在C ++ 11中,循环可以是:
for (Direction direction : ReverseIterable(Direction::SE))
std::cout << (int)direction << " ";
std::cout << std::endl;
您可以(ab?)使用宏来在C ++ 98中获得类似的东西。
我暂时保留了下面的原始答案,因为如果枚举定义可以改变,并且因为它允许稀疏范围,它可以简化可维护性。 可以在它上面实现一个非常相似的迭代器。
原始答案侧重于安全
这可能是完全矫枉过正的目的,而且可能不适合我将进一步描述的原因。 但是,您可以使用此库(免责声明:我是作者): https : //github.com/aantron/better-enums编写如下代码:
#include <iostream>
#include <enum.h>
ENUM(Direction, int, N, NE, SE, S, SW, NW)
int main()
{
size_t iterations = Direction::_size();
size_t index = 2;
for (size_t count = 0; count < iterations; ++count) {
std::cout << Direction::_values()[index] << " ";
index = (index + 1) % Direction::_size();
}
std::cout << std::endl;
return 0;
}
输出:
SE S SW NW N NE
(值为int
-sized枚举,但转换为字符串,仅输出到std::cout
)。
这显示了整个集合的迭代,具有任意的起始点。 你可以让它前进或后退,并在任何枚举上进行模板化。
我认为在你的问题中使用模数是一个好主意。 这段代码只是给你一些关于枚举中常量数量的反射信息,并将它们放在一个数组中。
这可能不合适的原因是,由于您没有使用C ++ 11,因此数组Direction::_values()
将在程序启动时初始化。 我认为循环展开仍然可能发生,但编译器将无法对数组的内容做任何事情。 该数组仍将静态分配,但在编译期间元素将不可用。
如果你以后可以选择使用C ++ 11,那么数组基本上与静态初始化的int[6]
(实际上, Direction[6]
,其中Direction
是文字struct
类型)。
(实际上,我想我可以暴露一个int
数组而不是Direction
s,即使在C ++ 98中也会静态初始化)。
如果你想避免使用自定义库,我通常看到的方法是这样的:
enum Direction
{
SE,
S,
SW,
NW,
N,
NE,
DIRECTION_FIRST = SE,
DIRECTION_LAST = NE,
}
然后,您可以使用DIRECTION_FIRST
和DIRECTION_LAST
来正确迭代范围。 如果有人在不更新迭代端点的情况下向枚举添加值,则仍然存在错误的空间,但是将其集中在枚举中应该会降低这种可能性。
现在,如果您假设一个任意的起始方向start
您可以像这样迭代:
Direction current = start;
do
{
// Do stuff with current...
current = (current + 1) % DIRECTION_LAST;
} while(current != start);
(如果你的枚举不是从0开始,那么逻辑会有点复杂,但仍然可能。这可能是你唯一需要使用DIRECTION_FIRST
。)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.