列挙型を反復するにはどうすればよいですか?


304

++や+ =などの列挙型では標準の数学演算子を使用できないことに気づきました

それでは、C ++列挙型のすべての値を反復処理する最良の方法は何ですか?


2
多くのアプローチの1つ:列挙型が十分でない場合:C ++の列挙型クラス。そして、よりカプセル化されたものが必要な場合、James Kanzeのこのアプローチを試してください。
Don Wakefield、

リンクされたアイテムには、興味深い応答があります。
トニー

これらの回答は、int十分に大きくない可能性がある問題をカバーしていないようです!([C++03: 7.2/5]
オービットのライトネスレース

興味深いことに、operator++列挙型で定義できます。ただし、そうすることができますfor(Enum_E e = (Enum_E)0; e < ENUM_COUNT; e++)。C ++は列挙型の代入演算子を禁止しているため、キャスト0する必要があることに注意してくださいEnum_E
weberc2 14年

列挙型の値で構成されるstd :: initializer_listリテラルを生成する可能性がある、sizeofの動作と同様のコンパイル時演算子があった場合、解決策があり、実行時のオーバーヘッドは発生しません。
jbruni 2017

回答:


263

一般的な方法は次のとおりです。

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

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

列挙型Lastは反復によってスキップされることを意図していることに注意してください。この「偽の」Last列挙型を利用すると、新しい列挙型を追加するたびに、forループの終了条件を最後の「実際の」列挙型に更新する必要はありません。列挙型を後で追加したい場合は、Lastの前に追加してください。この例のループは引き続き機能します。

もちろん、列挙値が指定されている場合、これは機能しません。

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

これは、列挙型が実際に反復することを意図していないことを示しています。enumを処理する一般的な方法は、それをswitchステートメントで使用することです。

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

本当に列挙したい場合は、列挙値をベクトルに入れ、それを繰り返します。これにより、指定された列挙値も適切に処理されます。


13
例の最初の部分で、「i」をintではなくFoo enumとして使用する場合は、次のように静的キャストする必要があることに注意してください:static_cast <Foo>(i)
Clayton

5
また、ループの最後をスキップしています。<= Last
Tony

20
@Tony Lastはスキップされます。enumを後で追加したい場合は、それらをLastの前に追加します...最初の例のループは引き続き機能します。「偽の」最後の列挙型を利用することで、新しい列挙型を追加するたびに、forループの終了条件を最後の「実際の」列挙型に更新する必要がなくなります。
timidpueo 2013

列挙型がゼロであり、厳密に連続的である場合、列挙型がメモリを割り当てずにそのタスクを実行できる場合に、メモリを実際に割り当てました。
クラウド

1
この列挙型の定義を更新しても安全であるためには、値を定義する必要がありますUNKNOWN = 0。さらに、default列挙型の値を切り替える場合は、実行時まで値の処理が忘れられていたケースを隠す可能性があるため、ケースを削除することをお勧めします。代わりに、すべての値をハードコードし、UNKNOWNフィールドを使用して非互換性を検出する必要があります。
ベンジャミンバニエ2017

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

ありがとう!ファイル/クラスをまたいでいて、MSの互換性によりヘッダーで宣言された非整数定数の問題が発生している場合、コンパイラーでヘッダーのタイプにサイズを明示的に指定するstatic const Type All[3];と、次のことが可能になります。ソースで初期化する:const MyEnum::Type MyEnum::All[3] = { a, b, c }; それを行う前に、不愉快なError in range-based for...エラーが発生していました(配列のサイズが不明だったため)。関連する回答の
セージ

1
アレイバージョンは、コピーペーストに非常に適しています。「NO」または「順次のみ」以外の最も満足のいく答え。おそらくマクロフレンドリーでもあります。
Paulo Neves

1
これは、アイテムの数が少ない列挙型には良い解決策かもしれませんが、アイテムの数が多い列挙型の場合、うまく適合してはなりません。
kato2

20

列挙型が0で始まり、増分が常に1の場合。

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

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

そうでない場合、唯一の理由は、

vector<enumType> vEnums;

アイテムを追加し、通常のイテレータを使用します。


19

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

enumにギャップがないという仮定は依然として当てはまります。enum値を格納するために実際に必要なビット数についての仮定はありません(std :: underlying_typeに感謝)


1
@lepe?別の列挙型に対して別のtypedefを作成するだけです。
Andrew Lazarus

2
@lepe はに関連付けられているstd::vectorため、一般的ではないと言っているようなものです。std::vector<foo>foo
Kyle Strand

1
typedef Iterator<color, color::green, color::red> colorIterator;テンプレートのインスタンス化がどのように機能するかを理解してください。
Andrew Lazarus

2
ああ、私は問題を見る- foo operator*() { ...はずですC operator*() { ...
Kyle Strand

1
@KyleStrand:わかった!それは今完全に理にかなっています。コードを更新する必要がありますか?説明してくれてありがとう。
2015

16

これらのソリューションが複雑すぎて、私はそれが好きです:

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

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

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

なぜこれが反対票だったのかはわかりません。それは合理的な解決策です。
ポールブランナン2014

11
これは、エントリを2か所で管理する必要があるためだと思います。
Ant

C ++はfor (NodePosition pos : NodePositionVector)構文を許可しますか?私の知る限り、これはJava構文であり、同等の処理を行うにはC ++の反復子が必要です。
thegreatjedi

3
@thegreatjedi C ++ 11以降では、シンピアでも可能です:for(auto pos:NodePositionVector){..}
Enzojz

@thegreatjediその質問をするよりも、テストプログラムを検索したり、コンパイルしたりする方が早いでしょう。しかし、はい。C++ 11以降、これは完全に有効なC ++構文であり、コンパイラは通常、イテレータを使用して、同等の(そして、はるかに冗長で抽象化されていない)コードに変換します。cppreferenceを参照してください。そして、EnzojzはC ++ 11にも追加、言ったようにautoあなたは(A)変換演算子または(B)を使用する必要が好きではない場合を除き、明示的要素の型を宣言する必要はありませんので、auto何らかの理由。ほとんどの範囲forauto
underscore_d

9

私はよくそうします

    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_First,
        E_None    = E_First,
        E_CanFly  = 0x1,
        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))
    {}

しかし、値をビット単位で出力する用途は何ですか?
Anu

1
列挙型を使用してビットマスクを作成します。たとえば、複数のオプションを1つの変数に結合し、FORを使用してすべてのオプションをテストします。より良い例で私の投稿を修正しました。
Niki

私はまだそれを使用することができません(そしてあなたの投稿はまだ古い例を示しています)!enumをビットマスクとして使用することは本当に役立ちますが、ドットを接続できませんでした!例の詳細を少し詳しく説明してもらえますか。追加のコードを追加することもできます。
Anu

@anu申し訳ありませんが、コメントはありませんでした。Frogクラスをビットマスクの例として追加
Niki

8

列挙型ではできません。おそらく列挙型はあなたの状況に最適ではありません。

一般的な規則は、最後の列挙値にMAXなどの名前を付け、それを使用してintを使用するループを制御することです。


ここには、反対を示すいくつかの例があります。私はあなた自身と矛盾しているあなた自身の声明(2行目)。
Niki、

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)

}

3
...しかし、C ++ 11は、他の回答で示されている範囲に基づいて導入しています。:-)
セージ

5

次のマクロを試して定義できます。

#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 ++で逆アセンブラを見ました。コードは非常に効率的です。延期しないでくださいが、3つのforステートメント:コンパイラーは最適化後にループを1つだけ生成します!囲まれたループを定義することもできます。

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

ギャップのある列挙型を繰り返し処理することはできません。


1
それは素晴らしいハックです!C ++よりもCの方が適切ですが、1つ言うかもしれません。
einpoklum 2013年

3
_A1は許可された名前ではありません。これは、次の大文字の先頭のアンダースコアです。
Martin Ueding 2017

3

列挙型のインクリメント/デクリメント演算子をオーバーロードすることもできます。


1
CまたはC ++列挙型の演算子をオーバーロードすることはできません。値の列挙をエミュレートする構造体/クラスを作成する場合を除きます。
Trevor Hickey

2
C ++では、列挙型で演算子をオーバーロードできます。stackoverflow.com/questions/2571456/…を参照してください。
アーチD.ロビソン2018年

インクリメント/デクリメントをオーバーロードすることは、オーバーフローがあるときに何をすべきかについて決定を下す必要が
同名の

3

enumに順番に番号が付けられていると仮定すると、エラーが発生しやすくなります。さらに、選択した列挙子のみを反復処理することもできます。そのサブセットが小さい場合は、明示的にループするのがエレガントな選択です。

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

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

これは良いオプションだと思います。私が推測している質問をしたときに使用していたよりも新しいC ++仕様の一部である必要がありますか?
アダム

はい。std :: initializer_list <Item>を反復処理します。リンク
marski

2

enumを最後のCOUNTアイテムで汚染したくない場合は(おそらく、enumをスイッチでも使用している場合、コンパイラーは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);
}

2

これは、隣接する列挙型に対してのみ機能する別のソリューションです。これは、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))
{
    // ...
}

1
増分はに短縮できますfoo = Bar(foo + 1)
HolyBlackCat

おかげで、HolyBlackCat、私はあなたの優れた提案を取り入れました!Riotにもほぼ同じ解決策があるが、厳密な型指定に準拠している(したがって、より詳細な)ことにも気づきました。
Ethan Bradford

2
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は、配列がコンパイラーによってインスタンス化されることなく、非順次列挙型でも反復できます。これは、コンパイラーの最適化ヒューリスティックのようなものや、配列のアドレスを取得するかどうかによって異なります。

私の実験では、2つの非連続値またはかなりの数の連続値がある場合(私は6つまでテストしました)、g++9.1を使用-O3すると上記の配列が最適化されます。ただし、これは、ifステートメントがある場合にのみ行われます。(シーケンシャル配列のすべての要素より大きい整数値を比較するステートメントを試してみましたが、何も除外されていないにもかかわらず反復がインライン化されましたが、ifステートメントを省略した場合、値はメモリに配置されました。)また、5をインライン化しました。 [1つのケース|での非順次列挙型の値 https://godbolt.org/z/XuGtoc]。この奇妙な動作は、キャッシュと分岐予測に関連する深いヒューリスティックが原因であると思います。

配列が常にインスタンス化されないことを示す、godboltでの簡単なテスト反復へリンクを次に示します。

この手法の代償は、enum要素を2回書き込み、2つのリストの同期を維持することです。


私は単純な範囲のようなforループのセマンティクスが好きで、さらに進化すると思うので、このソリューションが好きです。
rtischer8277

2

Bjarne StroustrupのC ++プログラミング言語の本で、彼がoperator++特定ののオーバーロードを提案していることがわかりますenumenumユーザー定義型であり、これらの特定の状況では、オーバーロード演算子が言語に存在します。

次のコードを記述できます。

#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、それらは強く型付けされており、コンパイル時にミスをするのを防ぐことができるので、私は好みます。


この投稿には反対票が投じられました。それが質問に答えない理由は何ですか?
LAL 2019

その理由はおそらく、これはアーキテクチャ的に言えばひどいソリューションであるためです。特定のコンポーネント(列挙)にバインドされたグローバル意味ロジックを作成する必要があり、さらに、編集を強制された何らかの理由で列挙が変更された場合+演算子も、中規模の大規模プロジェクトでは持続可能ではないアプローチであるため、Bjarne Stroustrupの推奨によるものであり、ソフトウェアアーキテクチャがサイエンスフィクションのような時代にさかのぼります
Manjia

元の質問は、オペレーターをに任せることenumです。それは建築上の問題ではありませんでした。2013年のC ++がSFだったとは思いません。
LAL

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、現時点では、コンパイラが確実にコンパイルできるようにするための便利なコンパイラはありません。


これはdecltype標準のC ++ になってから約5年後に書かれているので、古いtypeofGCCの古いものはお勧めしません。漠然と最近のGCCは問題decltypeなく処理します。他にも問題があります。Cスタイルのキャストはお勧めできません。適切なC ++機能は、同じ汎用機能を提供できます。これはstatic_cast&テンプレート関数を使用するように書き直されます:template <typename T> auto inc_enum(T const t) { return static_cast<T>(static cast<int>(t) + 1); }。そして、キャストは非-のために必要とされていませんenum class。または、演算子をenumタイプ(TIL)ごとにオーバーロードできます
underscore_d

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

このソリューションはメモリに不必要に静的な変数を作成しますが、列挙型の目的はインライン化された定数への「マスク」を作成することです
TheArquitect

変更しない限りconstexpr static const int enumItems[]
Mohammad Kanan

0

C ++にはイントロスペクションがないため、実行時にこの種のことを判別することはできません。


1
列挙型を反復するために「内省」が必要になる理由を教えていただけませんか。
Jonathan Mee 2016

たぶん用語はリフレクションですか?
ケンプͩ

2
私は2つのことを言おうとしています:1)他の多くの回答によれば、C ++はこれを達成できるため、できない場合はリンクまたは詳細な説明が必要です。2)現在の形式では、これはせいぜいコメントであり、確かに回答ではありません。
Jonathan Mee

その後、私の答えをDownvote -私はあなたがより多く、それを正当化したと思う
ケンプͩ

1
私は再び2つのコメントで詰め込みます。1)反対票を受け取るとサイトへの参加を刺激するため、反対票を投じません。あなたは私が理解していないことを理解しています。
ジョナサンミー2016

0

enumの値が連続していることがわかっている場合(Qt:Key enumなど)、次のことができます。

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

期待どおりに動作します。


-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のチェックが要素の1つと衝突しない限り、表現のintに関係なく分解されません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.