我如何迭代一个枚举?

我只是注意到你不能在enum上使用标准数学运算符,如+++=

那么,在c++ enum中遍历所有值的最佳方法是什么?

444407 次浏览

枚举就不行。也许枚举不是最适合您的情况。

一个常见的约定是将最后一个枚举值命名为MAX,并使用它来控制一个int类型的循环。

典型的方式如下:

enum Foo {
One,
Two,
Three,
Last
};


for ( int fooInt = One; fooInt != Last; fooInt++ )
{
Foo foo = static_cast<Foo>(fooInt);
// ...
}

请注意,枚举Last将被迭代跳过。利用这个“假”Last枚举,你不必每次想要添加一个新枚举时都将for循环中的终止条件更新到最后一个“真实”枚举。 如果你以后想添加更多的枚举,只要在Last之前添加它们。本例中的循环仍然有效。< / p >

当然,如果指定了enum值,则会中断:

enum Foo {
One = 1,
Two = 9,
Three = 4,
Last
};

这说明枚举并不是真正用于遍历的。处理枚举的典型方法是在switch语句中使用它。

switch ( foo )
{
case One:
// ..
break;
case Two:  // intentional fall-through
case Three:
// ..
break;
case Four:
// ..
break;
default:
assert( ! "Invalid Foo enum value" );
break;
}

如果你真的想要枚举,把枚举值填充到一个向量中,然后遍历它。这也将正确地处理指定的enum值。

c++没有自省,所以你不能在运行时确定这类事情。

如果枚举以0开头,且增量始终为1。

enum enumType
{
A = 0,
B,
C,
enumTypeEnd
};


for(int i=0; i<enumTypeEnd; i++)
{
enumType eCurrent = (enumType) i;
}

如果没有,我想唯一的原因是创造一个像

vector<enumType> vEnums;

添加条目,并使用普通的迭代器....

还可以重载枚举类型的自增/自减操作符。

您可以尝试并定义以下宏:

#define for_range(_type, _param, _A1, _B1) for (bool _ok = true; _ok;)\
for (_type _start = _A1, _finish = _B1; _ok;)\
for (int _step = 2*(((int)_finish)>(int)_start)-1;_ok;)\
for (_type _param = _start; _ok ; \
(_param != _finish ? \
_param = static_cast<_type>(((int)_param)+_step) : _ok = false))

现在你可以使用它:

enum Count { zero, one, two, three };


for_range (Count, c, zero, three)
{
cout << "forward: " << c << endl;
}

它可以用来在无符号、整数、枚举和字符之间来回迭代:

for_range (unsigned, i, 10,0)
{
cout << "backwards i: " << i << endl;
}




for_range (char, c, 'z','a')
{
cout << c << endl;
}

尽管它的定义很尴尬,但它优化得很好。我看了一下vc++中的反汇编程序。 代码非常高效。不要推迟,但是三个for语句:编译器在优化后只会产生一个循环!你甚至可以定义封闭循环:

unsigned p[4][5];


for_range (Count, i, zero,three)
for_range(unsigned int, j, 4, 0)
{
p[i][j] = static_cast<unsigned>(i)+j;
}

显然,不能在有间隙的枚举类型中进行迭代。

其他答案中没有涉及到的东西=如果你使用强类型c++ 11枚举,你不能对它们使用+++ int。在这种情况下,需要一个有点混乱的解决方案:

enum class myenumtype {
MYENUM_FIRST,
MYENUM_OTHER,
MYENUM_LAST
}


for(myenumtype myenum = myenumtype::MYENUM_FIRST;
myenum != myenumtype::MYENUM_LAST;
myenum = static_cast<myenumtype>(static_cast<int>(myenum) + 1)) {


do_whatever(myenum)


}

这些解决方案太复杂了,我喜欢这样:

enum NodePosition { Primary = 0, Secondary = 1, Tertiary = 2, Quaternary = 3};


const NodePosition NodePositionVector[] = { Primary, Secondary, Tertiary, Quaternary };


for (NodePosition pos : NodePositionVector) {
...
}
#include <iostream>
#include <algorithm>


namespace MyEnum
{
enum Type
{
a = 100,
b = 220,
c = -1
};


static const Type All[] = { a, b, c };
}


void fun( const MyEnum::Type e )
{
std::cout << e << std::endl;
}


int main()
{
// all
for ( const auto e : MyEnum::All )
fun( e );


// some
for ( const auto e : { MyEnum::a, MyEnum::b } )
fun( e );


// all
std::for_each( std::begin( MyEnum::All ), std::end( MyEnum::All ), fun );


return 0;
}

在c++11中,实际上有一个替代方案:编写一个模板化的自定义迭代器。

让我们假设枚举是

enum class foo {
one,
two,
three
};

这段泛型代码将会非常有效地达到目的——放置在泛型头文件中,它将为你提供任何你可能需要迭代的枚举:

#include <type_traits>
template < typename C, C beginVal, C endVal>
class Iterator {
typedef typename std::underlying_type<C>::type val_t;
int val;
public:
Iterator(const C & f) : val(static_cast<val_t>(f)) {}
Iterator() : val(static_cast<val_t>(beginVal)) {}
Iterator operator++() {
++val;
return *this;
}
C operator*() { return static_cast<C>(val); }
Iterator begin() { return *this; } //default ctor is good
Iterator end() {
static const Iterator endIter=++Iterator(endVal); // cache it
return endIter;
}
bool operator!=(const Iterator& i) { return val != i.val; }
};

你需要专门化它

typedef Iterator<foo, foo::one, foo::three> fooIterator;

然后你可以使用range-for进行迭代

for (foo i : fooIterator() ) { //notice the parentheses!
do_stuff(i);
}

枚举中没有空白的假设仍然成立;没有假设实际需要多少位来存储枚举值(感谢std::underlying_type)

对于MS编译器:

#define inc_enum(i) ((decltype(i)) ((int)i + 1))


enum enumtype { one, two, three, count};
for(enumtype i = one; i < count; i = inc_enum(i))
{
dostuff(i);
}

注意:这比简单的模板化自定义迭代器答案要少得多。

你可以通过使用typeof而不是decltype来让它与GCC一起工作,但我目前没有那个编译器来确保它能编译。

如果你不喜欢用最终的COUNT项污染你的枚举(因为如果你也在开关中使用枚举,那么编译器会警告你缺少大小写COUNT:),你可以这样做:

enum Colour {Red, Green, Blue};
const Colour LastColour = Blue;


Colour co(0);
while (true) {
// do stuff with co
// ...
if (co == LastColour) break;
co = Colour(co+1);
}

如果你知道枚举值是连续的,例如Qt:Key枚举,你可以:

Qt::Key shortcut_key = Qt::Key_0;
for (int idx = 0; etc...) {
....
if (shortcut_key <= Qt::Key_9) {
fileMenu->addAction("abc", this, SLOT(onNewTab()),
QKeySequence(Qt::CTRL + shortcut_key));
shortcut_key = (Qt::Key) (shortcut_key + 1);
}
}

它像预期的那样工作。

我经常这样做

    enum EMyEnum
{
E_First,
E_Orange = E_First,
E_Green,
E_White,
E_Blue,
E_Last
}


for (EMyEnum i = E_First; i < E_Last; i = EMyEnum(i + 1))
{}

或者如果不是连续的,但有规则步长(例如位标志)

    enum EAnimalCaps
{
E_None    = 0,
E_First   = 0x1,
E_CanFly  = E_First,
E_CanWalk = 0x2
E_CanSwim = 0x4,
E_Last
}
    

class MyAnimal
{
EAnimalCaps m_Caps;
}


class Frog
{
Frog() :
m_Caps(EAnimalCaps(E_CanWalk | E_CanSwim))
{}
}


for (EAnimalCaps= E_First; i < E_Last; i = EAnimalCaps(i << 1))
{}

只需创建一个int型数组并遍历该数组,但使最后一个元素为-1并使用它作为退出条件。

enum为:

enum MyEnumType{Hay=12,Grass=42,Beer=39};

然后创建数组:

int Array[] = {Hay,Grass,Beer,-1};


for (int h = 0; Array[h] != -1; h++){
doStuff( (MyEnumType) Array[h] );
}

当然,只要-1检查不与任何一个元素冲突,无论表示中的整数都不会被分解。

下面是另一种只适用于连续枚举的解决方案。它给出了期望的迭代,除了增量中的丑陋,这是它的归属,因为这是c++中破坏的地方。

enum Bar {
One = 1,
Two,
Three,
End_Bar // Marker for end of enum;
};


for (Bar foo = One; foo < End_Bar; foo = Bar(foo + 1))
{
// ...
}
enum class A {
a0=0, a3=3, a4=4
};
constexpr std::array<A, 3> ALL_A {A::a0, A::a3, A::a4}; // constexpr is important here


for(A a: ALL_A) {
if(a==A::a0 || a==A::a4) std::cout << static_cast<int>(a);
}

constexpr std::array甚至可以迭代非连续的枚举,而不需要编译器实例化数组。这取决于编译器的优化启发式以及是否取数组的地址。

在我的实验中,我发现带有-O3g++ 9.1将优化掉上面的数组,如果有2个非连续值或相当多的连续值(我测试到6个)。但只有当你有if语句时才会这样做。(我尝试了一个语句,比较一个大于顺序数组中所有元素的整数值,尽管没有排除,但它内联了迭代,但是当我省略if语句时,这些值被放入内存中。)它还内联了非顺序enum中的5个值[one case|https://godbolt.org/z/XuGtoc]。我怀疑这种奇怪的行为是由于与缓存和分支预测有关的深层启发式。

下面是一个链接到godbolt的简单测试迭代,它表明数组并不总是被实例化。

这种技术的代价是写入enum元素两次,并保持两个列表同步。

假设枚举是按顺序编号是容易出错的。此外,您可能只希望迭代选定的枚举数。如果这个子集很小,显式遍历它可能是一个优雅的选择:

enum Item { Man, Wolf, Goat, Cabbage }; // or enum class


for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
// ...
}
typedef enum{
first = 2,
second = 6,
third = 17
}MyEnum;


static const int enumItems[] = {
first,
second,
third
}


static const int EnumLength = sizeof(enumItems) / sizeof(int);


for(int i = 0; i < EnumLength; i++){
//Do something with enumItems[i]
}

在Bjarne Stroustrup的c++编程语言书中,你可以读到他建议为特定的enum重载operator++enum是用户定义的类型,语言中存在针对这些特定情况的重载操作符。

你将能够编写以下代码:

#include <iostream>
enum class Colors{red, green, blue};
Colors& operator++(Colors &c, int)
{
switch(c)
{
case Colors::red:
return c=Colors::green;
case Colors::green:
return c=Colors::blue;
case Colors::blue:
return c=Colors::red; // managing overflow
default:
throw std::exception(); // or do anything else to manage the error...
}
}


int main()
{
Colors c = Colors::red;
// casting in int just for convenience of output.
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
std::cout << (int)c++ << std::endl;
return 0;
}

测试代码:http://cpp.sh/357gb

注意,我使用的是enum class。代码在enum中也可以正常工作。但我更喜欢enum class,因为它们是强类型的,可以防止我们在编译时犯错误。

扩展@Eponymous的回答:它很棒,但没有提供通用语法。这是我想到的:

// Common/EnumTools.h
#pragma once


#include <array>


namespace Common {


// Here we forward-declare metafunction for mapping enums to their values.
// Since C++<23 doesn't have reflection, you have to populate it yourself :-(
// Usage: After declaring enum class E, add this overload in the namespace of E:
// inline constexpr auto allValuesArray(const E&, Commob::EnumAllValuesTag) { return std::array{E::foo, E::bar}; }
// Then `AllValues<NS::E>` will call `allValuesArray(NS::E{}, EnumAllValuesTag)` which will resolve
// by ADL.
// Just be sure to keep it sync'd with your enum!


// Here's what you want to use in, e.g., loops: "for (auto val : Common::AllValues<MyEnum>) {"


struct EnumAllValuesTag {}; // So your allValuesArray function is clearly associated with this header.


template <typename Enum>
static inline constexpr auto AllValues = allValuesArray(Enum{}, EnumAllValuesTag{});
// ^ Just "constexpr auto" or "constexpr std::array<Enum, allValuesArray(Enum{}, EnumAllValuesTag{}).size()>" didn't work on all compilers I'm using, but this did.


} // namespace Common

然后在你的命名空间:

#include "Common/EnumTools.h"


namespace MyNamespace {


enum class MyEnum {
foo,
bar = 4,
baz = 42,
};


// Making this not have to be in the `Common` namespace took some thinking,
// but is a critical feature since otherwise there's no hope in keeping it sync'd with the enum.
inline constexpr auto allValuesArray(const MyEnum&, Common::EnumAllValuesTag) {
return std::array{ MyEnum::foo, MyEnum::bar, MyEnum::baz };
}


} // namespace MyNamespace

然后在任何需要使用它的地方:

for (const auto& e : Common::AllValues<MyNamespace::MyEnum>) { ... }

所以即使你有typeded:

namespace YourNS {
using E = MyNamespace::MyEnum;
} // namespace YourNS


for (const auto& e : Common::AllValues<YourNS::E>) { ... }

我想不出比这更好的了,除了每个人都想要的实际语言功能。

未来工作:

  1. 你应该能够添加一个constexpr函数(以及一个元函数),它可以过滤Common::AllValues<E>,为枚举重复数值的情况提供一个Common::AllDistinctValues<E>,比如enum { foo = 0, bar = 0 };
  2. 我敢打赌,有一种方法可以使用编译器的__abc0 - coverage -all-enum-values来编写allValuesArray,这样如果枚举添加了一个值,它就会出错。
优点:枚举可以以你喜欢的任何顺序拥有你喜欢的任何值,并且仍然很容易遍历它们。 名称和值只定义一次,在第一个#define.

缺点:如果你在工作中使用这个短语,你需要用一整段话来向你的同事解释。而且,必须声明内存来给你的循环提供迭代的东西是很烦人的,但我不知道有什么解决办法不限制你使用相邻值的枚举(如果枚举总是有相邻值,enum可能不会给你买那么多)。

//create a, b, c, d as 0, 5, 6, 7
#define LIST x(a) x(b,=5) x(c) x(d)
#define x(n, ...) n __VA_ARGS__,
enum MyEnum {LIST}; //define the enum
#undef x //needed
#define x(n,...) n ,
MyEnum myWalkableEnum[] {LIST}; //define an iterable list of enum values
#undef x //neatness


int main()
{
std::cout << d;
for (auto z : myWalkableEnum)
std::cout << z;
}
//outputs 70567

使用未定义的宏包装器声明一个列表,然后在各种情况下以不同的方式定义包装器的技巧,除了这个应用程序之外,还有许多应用程序。

大多数解决方案基于(MIN, MAX)范围内的循环,但忽略了枚举中可能存在漏洞的事实。

我的建议是:

        for (int i = MYTYPE_MIN; i <= MYTYPE_MAX; i++) {
if (MYTYPE_IsValid(i)) {
MYTYPE value = (MYTYPE)i;
// DoStuff(value)
}
}
        

在评论中已经有关于std::initializer_list (c++ 11)的讨论。

或者std::initializer_list和一个更简单的语法:

enum E {
E1 = 4,
E2 = 8,
// ..
En
};


constexpr std::initializer_list<E> all_E = {E1, E2, /*..*/ En};

然后

for (auto e : all_E) {
// Do job with e
}

参考链接

这里有一些非常易读且易于理解的方法,适用于弱类型的 C和c++常规__abc0, 而且 强类型 c++ enum classes。

我建议使用-Wall -Wextra -Werror编译下面的所有示例。这为你提供了额外的安全性,如果你忘记在switch情况下覆盖任何枚举值,你的编译器将抛出编译时错误!这迫使您保持枚举定义和开关用例同步,这是代码的额外安全措施。只要你:

  1. 在你的switch case中覆盖所有 enum值
  2. 没有default开关箱。
  3. 使用-Wall -Wextra -Werror标志进行构建。

我建议您遵循所有这3点,因为这是一个很好的实践,可以创建更好的代码。

1. 对于标准,弱类型的 C或c++ enum:

C定义(这也适用于c++):

typedef enum my_error_type_e
{
MY_ERROR_TYPE_SOMETHING_1 = 0,
MY_ERROR_TYPE_SOMETHING_2,
MY_ERROR_TYPE_SOMETHING_3,
MY_ERROR_TYPE_SOMETHING_4,
MY_ERROR_TYPE_SOMETHING_5,
/// Not a valid value; this is the number of members in this enum
MY_ERROR_TYPE_count,
// helpers for iterating over the enum
MY_ERROR_TYPE_begin = 0,
MY_ERROR_TYPE_end = MY_ERROR_TYPE_count,
} my_error_type_t;

c++定义:

enum my_error_type_t
{
MY_ERROR_TYPE_SOMETHING_1 = 0,
MY_ERROR_TYPE_SOMETHING_2,
MY_ERROR_TYPE_SOMETHING_3,
MY_ERROR_TYPE_SOMETHING_4,
MY_ERROR_TYPE_SOMETHING_5,
/// Not a valid value; this is the number of members in this enum
MY_ERROR_TYPE_count,
// helpers for iterating over the enum
MY_ERROR_TYPE_begin = 0,
MY_ERROR_TYPE_end = MY_ERROR_TYPE_count,
};

C或c++迭代弱类型 enum:

注意:通过my_error_type++递增一个枚举是允许的——甚至在c风格的枚举上也不允许,所以我们必须这样做:my_error_type = (my_error_type_t)(my_error_type + 1)。注意,my_error_type + 1 是允许的,然而,因为这个弱enum在这里被自动隐式转换为int,从而使此添加成为可能,而不必手动转换为这样的int类型:my_error_type = (my_error_type_t)((int)my_error_type + 1)

for (my_error_type_t my_error_type = MY_ERROR_TYPE_begin;
my_error_type < MY_ERROR_TYPE_end;
my_error_type = (my_error_type_t)(my_error_type + 1))
{
switch (my_error_type)
{
case MY_ERROR_TYPE_SOMETHING_1:
break;
case MY_ERROR_TYPE_SOMETHING_2:
break;
case MY_ERROR_TYPE_SOMETHING_3:
break;
case MY_ERROR_TYPE_SOMETHING_4:
break;
case MY_ERROR_TYPE_SOMETHING_5:
break;
case MY_ERROR_TYPE_count:
// This case will never be reached.
break;
}
}

2. 对于作用域强类型 c++ enum class:

c++定义:

enum class my_error_type_t
{
SOMETHING_1 = 0,
SOMETHING_2,
SOMETHING_3,
SOMETHING_4,
SOMETHING_5,
/// Not a valid value; this is the number of members in this enum
count,
// helpers for iterating over the enum
begin = 0,
end = count,
};

c++迭代强类型 enum:

注意,强制增加enum class变量需要额外的(size_t)强制转换(或(int)也可以接受)!这里我还选择使用c++风格的static_cast<my_error_type_t>类型强制转换,但C风格的(my_error_type_t)类型强制转换,就像上面所做的那样,也可以。

for (my_error_type_t my_error_type = my_error_type_t::begin;
my_error_type < my_error_type_t::end;
my_error_type = static_cast<my_error_type_t>((size_t)my_error_type + 1))
{
switch (my_error_type)
{
case my_error_type_t::SOMETHING_1:
break;
case my_error_type_t::SOMETHING_2:
break;
case my_error_type_t::SOMETHING_3:
break;
case my_error_type_t::SOMETHING_4:
break;
case my_error_type_t::SOMETHING_5:
break;
case my_error_type_t::count:
// This case will never be reached.
break;
}
}

还要注意范围。在c++ my_error_type_t::0 enum class中,我使用my_error_type_t::来访问每个作用域的enum class成员。但是,在c风格的my_error_type_t::1常规enum中,可以实现非常类似的作用域,正如我所演示的,只需在每个enum成员名前加上MY_ERROR_TYPE_。因此,c++ my_error_type_t::0 enum class增加作用域的事实并没有真正增加太多价值——在这方面它实际上只是个人偏好。c++ my_error_type_t::0 enum class具有额外的类型安全性,这一事实也有优点和缺点。在某些情况下,它可能会帮助你,但它肯定会使枚举的增量和迭代成为一种痛苦,老实说,这意味着它正在完成它的工作。通过将其设置为my_error_type_t::4来增加作用域的enum class变量,就像它是一个整数一样,c++ my_error_type_t::0 enum class正在执行my_error_type_t::6。不管你是否my_error_type_t::7行为是由你决定的。就我个人而言,我经常做my_error_type_t::8想要这种行为,所以我更喜欢使用C风格的枚举,即使是在c++中。

参见:

  1. [我的回答]在c++11中是否有一种方法通过索引初始化一个向量?
  2. [我的q&a] 在c++中迭代枚举类的常用方法是什么?
  3. 关于c++中enum classes (强类型 enums)和常规enums (弱类型的 enums)之间的一些差异,我的回答是:如何自动转换强类型枚举为int?
  4. 关于-Wall -Wextra -Werror和其他构建选项的一些个人笔记,从我的eRCaGuy_hello_world回购。
使用lambda,我发现这是遍历枚举的最佳(现代)方式。 这大大提高了抽象性。 甚至可以使它成为模板,所以它适用于任何枚举。 这段代码不会给你clang(-tidy)的问题
#include <functional>


/// @brief Loop over all enum values where the last enum value is the invalid one
void forEachAction(std::function<void(Enum)> &&doThis) {
for (int value = 0; value = static_cast<int>(Enum::LastValue); ++value ) {
doThis(static_cast<Enum>(value ));
}
}


...


forEachAction([this](Enum value) {
...  // what you want to execute for every enum
});

将变量强制转换为int&可以在保持类型可读的同时进行递增。

#include <iostream>


enum MyEnum
{
ONE,
TWO,
THREE,
FOUR,
};


int main()
{
for (MyEnum v = MyEnum::ONE; v <= MyEnum::FOUR; ++(int&)v)
{
std::cout<<v<<std::endl;
}


return 0;
}
0
1
2
3

(把Marski的回答当作一个很大的暗示……)

由于枚举定义的主体与初始化列表相同,如果我们使用一个简单的宏来写出值,就可以在不重复项目列表的情况下做到这一点:

#define ITEM_LIST_MACRO Wolf, Goat, Cabbage


enum Item { ITEM_LIST_MACRO }; // Define the enum


// Now iterate through it
for (auto item : { ITEM_LIST_MACRO }) {
}

优点:简单,没有重复,不需要保持恼人的第一个/最后一个哨兵值。(事实上,我认为这可能是目前为止建议的唯一解决方案,不需要用户记得在新项目添加到列表时更新“结束”标记。)

缺点:不适用于范围限定的枚举(enum类),因为初始化列表需要范围(Item::Wolf等)。如果你想指定枚举成员的值而不是默认值,同样不起作用。