IT

열거 형을 어떻게 반복 할 수 있습니까?

lottoking 2020. 3. 24. 08:14
반응형

열거 형을 어떻게 반복 할 수 있습니까?


방금 ++ 또는 + =와 같은 열거 형에서 표준 수학 연산자를 사용할 수 없다는 것을 알았습니다.

그렇다면 C ++ 열거 형의 모든 값을 반복하는 가장 좋은 방법은 무엇입니까?


일반적인 방법은 다음과 같습니다.

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

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

열거 형 Last은 반복에 의해 건너 뛰기위한 것입니다. 이 "가짜" Last열거 형을 사용하면 새 열거 형을 추가 할 때마다 for 루프에서 종료 조건을 마지막 "실제"열거 형으로 업데이트 할 필요가 없습니다. 나중에 더 많은 열거 형을 추가하려면 마지막 전에 추가하십시오. 이 예제의 루프는 여전히 작동합니다.

물론 열거 형 값을 지정하면 다음과 같이 분류됩니다.

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

이것은 열거 형이 실제로 반복되지 않는다는 것을 보여줍니다. 열거 형을 처리하는 일반적인 방법은 스위치 명령문에서 열거 형을 사용하는 것입니다.

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

실제로 열거하려면 열거 형 값을 벡터에 채우고 반복하십시오. 지정된 열거 형 값도 올바르게 처리합니다.


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

열거 형이 0으로 시작하고 증가량이 항상 1 인 경우

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

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

그렇지 않다면 유일한 이유는

vector<enumType> vEnums;

항목을 추가하고 일반 반복자를 사용하십시오 ....


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 덕분)


이 솔루션이 너무 복잡합니다.

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

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

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

열거 형으로는 할 수 없습니다. 아마도 열거 형이 상황에 가장 적합하지 않을 수도 있습니다.

일반적인 규칙은 MAX와 같은 마지막 열거 형 값의 이름을 지정하고 int를 사용하여 루프를 제어하는 ​​데 사용하는 것입니다.


나는 종종 그런 식으로

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

다음 매크로를 시도하고 정의 할 수 있습니다.

#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)

}

열거 된 유형의 증분 / 감소 연산자를 오버로드 할 수도 있습니다.


스위치에서 열거 형을 사용하면 컴파일러에서 누락 된 사례 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);
}

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대신 GCC를 사용 하여이 작업을 수행 할 수는 decltype있지만 현재 컴파일 할 수있는 컴파일러는 없습니다.


연속 열거 형에만 작동하는 또 다른 솔루션이 있습니다. 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에 순차적으로 번호가 매겨 졌다고 가정하면 오류가 발생하기 쉽습니다. 또한 선택된 열거 자에 대해서만 반복 할 수 있습니다. 해당 하위 집합이 작은 경우 명시 적으로 반복하면 우아한 선택이 될 수 있습니다.

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

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

C ++에는 내부 검사가 없으므로 런타임에 이러한 종류의 것을 결정할 수 없습니다.


열거 형 값이 순차적이라는 것을 알고 있다면 (예 : 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 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);
}

A는 constexpr std::array어레이가 컴파일러에 의해 인스턴스화 없이도 비 순차 열거를 반복 할 수있다. 이것은 컴파일러의 최적화 휴리스틱 스와 배열의 주소를 취했는지 여부에 달려 있습니다.

내 실험에서, g++9.1 with -O3는 비 순차적 값이 2 개이거나 순차적 인 값이 상당히 많은 경우 위 배열을 최적화합니다 (최대 6 테스트). 그러나 if진술 이있는 경우에만이 작업을 수행 합니다. (순차 배열의 모든 요소보다 큰 정수 값을 비교 한 명령문을 시도했지만 제외되지 않았음에도 반복을 인라인했지만 if 문을 생략하면 값이 메모리에 저장되었습니다.) 하나의 비 순차 열거 형의 값 https://godbolt.org/z/XuGtoc] . 이 이상한 행동은 캐시 및 분기 예측과 관련된 깊은 휴리스틱으로 인한 것으로 생각됩니다.

다음은 배열이 항상 인스턴스화되지는 않음을 보여주는 godbolt의 간단한 테스트 반복에 대한 링크 입니다.

이 기술의 가격은 열거 형 요소를 두 번 쓰고 두 목록을 동기화 상태로 유지합니다.


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

정수 배열을 만들고 배열을 반복하지만 마지막 요소를 -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 검사가 물론 요소 중 하나와 충돌하지 않는 한 표현의 정수에 관계없이 분해되지 않습니다.

참고 URL : https://stackoverflow.com/questions/261963/how-can-i-iterate-over-an-enum

반응형