我如何迭代枚举?

时间:2008-11-04 13:55:30

标签: c++ enums

我刚注意到你不能在枚举上使用标准数学运算符,例如++或+ =

那么迭代C ++枚举中所有值的最佳方法是什么?

23 个答案:

答案 0 :(得分:233)

典型的方法如下:

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

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

当然,如果指定了枚举值,则会发生故障:

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;
}

如果你真的想要枚举,请将枚举值填入向量中并迭代它。这也将适当地处理指定的枚举值。

答案 1 :(得分:33)

#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;
}

答案 2 :(得分:18)

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

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

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

如果不是,我猜唯一的原因就是创造像

这样的东西
vector<enumType> vEnums;

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

答案 3 :(得分:15)

使用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)

答案 4 :(得分:13)

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

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

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

for (NodePosition pos : NodePositionVector) {
...
}

答案 5 :(得分:8)

你不能用枚举。也许enum不适合你的情况。

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

答案 6 :(得分:7)

我经常这样做

    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 EAnimal
    {
        E_First,
        E_None    = E_First,
        E_CanFly  = 0x1,
        E_CanWalk = 0x2
        E_CanSwim = 0x4,
        E_Last
    }

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

答案 7 :(得分:6)

您可以尝试定义以下宏:

#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;
    }

你显然无法通过间隙迭代枚举类型。

答案 8 :(得分:6)

其他答案中未涉及的内容=如果您使用的是强类型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)

}

答案 9 :(得分:4)

您还可以为枚举类型重载递增/递减运算符。

答案 10 :(得分:2)

如果您不喜欢使用最终COUNT项污染枚举(因为如果您也在开关中使用枚举,那么编译器会警告您丢失了COUNT个案例:),您可以这样做:< / p>

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);
}

答案 11 :(得分:1)

对于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,但我目前还没有那个编译器可以确保它编译。<​​/ p>

答案 12 :(得分: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))
{
    // ...
}

答案 13 :(得分:1)

假定枚举顺序编号是容易出错的。此外,您可能只想遍历选定的枚举数。如果该子集很小,则明确地对其进行循环可能是一个不错的选择:

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

for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
    // ...
}

答案 14 :(得分:1)

扩展@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>) { ... }

因此,即使您已键入:

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

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

我想不出什么更好的地方了,缺少所有人都希望在此页面上看到的实际语言功能。

未来的工作

  1. 您应该能够添加一个constexpr函数(以及一个元函数),该函数可以过滤Common::AllValues<E>来为带有重复数值(例如{{1})的枚举提供Common::AllDistinctValues<E> }}。
  2. 我敢打赌,有一种方法可以使用编译器的enum { foo = 0, bar = 0 };-covers-all-switch-值来编写enum,以便在枚举添加了值时出错。 li>

答案 15 :(得分:0)

如果您知道枚举值是连续的,例如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);
    }
}

它按预期工作。

答案 16 :(得分:0)

C ++没有内省,因此您无法在运行时确定此类内容。

答案 17 :(得分:0)

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甚至可以迭代非顺序枚举,而无需由编译器实例化数组。这取决于诸如编译器的优化试探法以及是否采用数组的地址之类的东西。

在我的实验中,我发现如果有2个非序列值或相当多的序列值(我测试了多达6个),则g++ 9.1和-O3可以优化上述数组。但这仅在您有if语句的情况下执行。 (我尝试了一个比较整数值的语句,该整数值比顺序数组中的所有元素都大,并且尽管没有排除任何变量,但是它内联了迭代,但是当我省略if语句时,这些值已放入内存中。)它也内联了5 [one case | https://godbolt.org/z/XuGtoc]中来自非顺序枚举的值。我怀疑这种奇怪的行为是由于深度启发式技术与缓存和分支预测有关。

这里是一个link to a simple test iteration on godbolt,它说明了数组并不总是被实例化。

此技术的代价是两次编写枚举元素并使两个列表保持同步。

答案 18 :(得分:0)

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]
}

答案 19 :(得分:0)

在Bjarne Stroustrup的C ++编程语言书中,您可以了解到他提议为您的特定mouseout重载function inn() { cursor1.classList.add("hover") } function out() { cursor1.classList.remove("hover") } var focus = document.querySelectorAll(".focus"); for (i = focus.length - 1; i >= 0; i--) { hover(focus[i]) } function hover() { // enter code here cursor1.addEventListener("mouseover", inn); cursor1.addEventListener("mouseout", out); } operator++是用户定义的类型,在这些特定情况下,语言中存在重载运算符。

您将能够编写以下代码:

enum

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

请注意,我正在使用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; } 一起正常工作。但是我更喜欢enum class,因为它们是强类型的,可以防止我们在编译时犯错误。

答案 20 :(得分:0)

优点:枚举可以按您喜欢的任何顺序具有任何喜欢的值,并且仍然很容易在它们上进行迭代。 名称和值在第一个#define中定义一次。

缺点:如果在工作中使用此功能,则需要整段内容向同事解释。而且,必须声明内存来给您的循环进行迭代很烦人,但是我不知道有一种解决方法不能将您限制为具有相邻值的枚举(并且如果枚举将始终具有相邻值,则枚举可能不会买那么多东西。)

//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

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

答案 21 :(得分:0)

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

我的建议是:

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

答案 22 :(得分:-1)

只需创建一个int数组并循环遍历数组,但是将最后一个元素设为-1并将其用于退出条件。

如果枚举是:

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检查不会与其中一个元素发生碰撞,无论表示中的内容如何,​​这都不会分解。