Como posso iterar sobre uma enumeração?

304

Acabei de notar que você não pode usar operadores matemáticos padrão em uma enumeração como ++ ou + =

Então, qual é a melhor maneira de iterar todos os valores em uma enumeração C ++?

Adão
fonte
2
Uma das muitas abordagens: Quando a enumeração não é suficiente: Classes de enumeração para C ++ . E, se você quiser algo mais encapsulado, tente esta abordagem de James Kanze.
Don Wakefield
Os itens vinculados têm algumas respostas interessantes.
Tony
Essas respostas parecem não cobrir o problema que intpode não ser grande o suficiente! ( [C++03: 7.2/5])
Lightness Races in Orbit
Curiosamente, você pode definir operator++enums; no entanto, para que você possa fazer for(Enum_E e = (Enum_E)0; e < ENUM_COUNT; e++). Observe que você precisa converter 0para Enum_Eporque o C ++ proíbe operadores de atribuição em enumerações.
weberc2
Se houvesse um operador de tempo de compilação, semelhante à maneira como sizeof funciona, que pudesse emitir um literal std :: initializer_list composto pelos valores da enumeração, teríamos uma solução e não envolveríamos nenhuma sobrecarga de tempo de execução.
jbruni

Respostas:

263

A maneira típica é a seguinte:

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

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

Observe que o enum Lastdeve ser ignorado pela iteração. Utilizando essa Lastenumeração "falsa" , você não precisa atualizar sua condição final no loop for para a última enumeração "real" toda vez que desejar adicionar uma nova enumeração. Se você quiser adicionar mais enumerações posteriormente, basta adicioná-las antes do Último. O loop neste exemplo ainda funcionará.

Obviamente, isso será interrompido se os valores de enumeração forem especificados:

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

Isso ilustra que um enum não é realmente destinado a iterar. A maneira típica de lidar com uma enumeração é usá-la em uma instrução switch.

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

Se você realmente deseja enumerar, encha os valores de enum em um vetor e itere sobre ele. Isso também lidará adequadamente com os valores de enum especificados.

andreas buykx
fonte
13
Observe que, na primeira parte do exemplo, se você quiser usar 'i' como um eno Foo e não como um int, precisará convertê-lo estático como: static_cast <Foo> (i)
Clayton
5
Além disso, você está pulando Last in the loop. Deve ser <= Último
Tony
20
@ Tony Last é para ser pulado. Se você quiser adicionar mais enums posteriormente, adicione-as antes de Last ... o loop no primeiro exemplo ainda funcionará. Ao utilizar uma última enumeração "falsa", você não precisa atualizar sua condição de encerramento no loop for para a última enumeração "real" toda vez que desejar adicionar uma nova enumeração.
precisa saber é o seguinte
Exceto agora que você realmente alocou memória quando um enum, desde que seja indexado a zero e estritamente contínuo, possa executar essa tarefa sem alocar memória.
Cloud
1
Observe que, para que essa definição de enum seja segura para atualizações, deve-se definir um valor UNKNOWN = 0. Além disso, eu sugeriria apenas descartar o defaultcaso ao alternar valores de enum, pois ele pode ocultar casos em que o tratamento de valores foi esquecido até o tempo de execução. Em vez disso, deve-se codificar todos os valores e usar o UNKNOWNcampo para detectar incompatibilidades.
22717 Benjamin Bannier
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;
}
zdf
fonte
Obrigado! Observe que, se você estiver cruzando arquivos / classes e se a compatibilidade do MS estiver fornecendo problemas com constantes não inteiras declaradas pelo cabeçalho, o meu compilador ajudará no explicitamente colocar o tamanho do tipo no cabeçalho: static const Type All[3];e então eu posso para inicializar na fonte: const MyEnum::Type MyEnum::All[3] = { a, b, c }; Antes de fazer isso, eu estava recebendo Error in range-based for...erros desagradáveis (porque a matriz tinha um tamanho desconhecido). Descobriram isso graças a uma resposta relacionada
sage
1
A versão do array é muito amigável para copiar e colar. A resposta mais satisfatória além disso, "NÃO" ou "apenas para sequencial". Provavelmente macro amigável mesmo.
Paulo Neves
1
essa pode ser uma boa solução para enumerações com pequeno número de itens, mas para enumerações com grande número de itens, ela não deve se encaixar bem.
kato2
20

Se o seu enum começa com 0 e o incremento é sempre 1.

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

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

Se não, acho que o único motivo é criar algo como um

vector<enumType> vEnums;

adicione os itens e use iteradores normais ....

João Augusto
fonte
19

Com o c ++ 11, na verdade existe uma alternativa: escrever um iterador personalizado simples e com modelo.

vamos assumir que seu enum é

enum class foo {
  one,
  two,
  three
};

Esse código genérico fará o truque, com bastante eficiência - coloque em um cabeçalho genérico, servirá para qualquer enumeração que você precise repetir:

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

Você precisará se especializar

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

E então você pode iterar usando o intervalo para

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

A suposição de que você não tem lacunas na enumeração ainda é verdadeira; não há suposição sobre o número de bits realmente necessário para armazenar o valor da enumeração (graças a std :: subjac_type)

Francesco Chemolli
fonte
1
@lepe? Você acabou de criar um typedef diferente para uma enumeração diferente.
Andrew Lazarus
2
@ Lepe É como dizer que std::vectornão é genérico porque std::vector<foo>está vinculado foo.
Kyle Strand
1
typedef Iterator<color, color::green, color::red> colorIterator;Certifique-se de entender como as instanciações de modelo funcionam.
Andrew Lazarus
2
Oh, eu vejo o problema - foo operator*() { ...deveria ser C operator*() { ....
Kyle Strand
1
@KyleStrand: Você conseguiu! isso faz totalmente sentido agora. O código deve ser atualizado? Obrigado a todos por suas explicações.
quer
16

muito complicado essas soluções, eu faço assim:

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

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

for (NodePosition pos : NodePositionVector) {
...
}
Enzojz
fonte
Não sei por que isso foi prejudicado. É uma solução razoável.
Paul Brannan
11
Espero que seja porque as entradas precisam ser mantidas em dois lugares.
Ant
O C ++ permite a for (NodePosition pos : NodePositionVector)sintaxe? Tanto quanto sei, essa é a sintaxe Java, e você precisará de iteradores em C ++ para fazer algo equivalente.
precisa
3
@thegreatjedi Desde C ++ 11 você pode, mesmo simpier: para (auto pos: NodePositionVector) {..}
Enzojz
@thegreatjedi Teria sido mais rápido pesquisar, ou até compilar um programa de teste, do que fazer essa pergunta. Mas sim, desde o C ++ 11, é perfeitamente válido a sintaxe do C ++, que o compilador converte no código equivalente (e muito mais detalhado / menos abstrato), geralmente por meio de iteradores; veja cppreference . E, como Enzojz disse, o C ++ 11 também adicionou auto, assim você não precisa declarar explicitamente o tipo dos elementos, a menos que você (A) precise usar um operador de conversão ou (B) não goste autopor algum motivo . A maioria gama- forusuários usam autoAFAICT
underscore_d
9

Costumo fazer assim

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

ou se não for sucessivo, mas com etapa regular (por exemplo, sinalizadores de bit)

    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))
    {}
Niki
fonte
mas, qual é a utilidade de imprimir os valores em bits?
Anu
1
Para usar enumerações para criar máscaras de bits. por exemplo, combine várias opções em uma única variável e use o FOR para testar todas as opções. Corrigido meu post com um exemplo melhor.
Niki
Ainda não consigo usá-lo (e sua postagem ainda mostra o exemplo antigo)! Usar enum como máscaras de bits é realmente útil, mas não conseguiu conectar os pontos! Se você puder elaborar um pouco mais o exemplo em detalhes, também poderá colocar o código adicional.
Anu
@anu Desculpe não viu seu comentário. Adicionada classe Frog como exemplo de máscara de bits
Niki
8

Você não pode com um enum. Talvez um enum não seja o mais adequado para sua situação.

Uma convenção comum é nomear o último valor da enumeração como MAX e usá-lo para controlar um loop usando um int.

Corey Trager
fonte
Existem vários exemplos aqui que demonstram o contrário. Na sua própria declaração, você está se contradizendo (segunda linha).
Niki
6

Algo que não foi abordado nas outras respostas = se você estiver usando enumerações C ++ 11 fortemente tipadas, não poderá usá -las ++ou + intnelas. Nesse caso, é necessária uma solução um pouco mais bagunçada:

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)

}
Tumulto
fonte
3
... mas o C ++ 11 apresenta o intervalo com base no que é mostrado em outras respostas. :-)
sage
5

Você pode tentar definir a seguinte macro:

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

Agora você pode usá-lo:

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

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

Pode ser usado para iterar para frente e para trás através de números inteiros, enumerados e enumerados não assinados:

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


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

Apesar de sua definição incômoda, ele é otimizado muito bem. Eu olhei desmontador em VC ++. O código é extremamente eficiente. Não desanime, mas as três instruções for: o compilador produzirá apenas um loop após a otimização! Você pode até definir loops fechados:

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

Você obviamente não pode iterar por tipos enumerados com lacunas.

Mikhail Semenov
fonte
1
Isso é um truque maravilhoso! Embora seja mais apropriado para C do que para C ++, pode-se dizer.
einpoklum
3
_A1não é um nome permitido, é um sublinhado à esquerda com uma letra maiúscula a seguir.
Martin Ueding 10/02
3

Você também pode sobrecarregar os operadores de incremento / decremento para seu tipo enumerado.

JohnMcG
fonte
1
Você não pode sobrecarregar nenhum operador nos tipos enumerados C ou C ++. A menos que você crie uma estrutura / classe que emule uma enumeração de valores.
Trevor Hickey
2
C ++ permite sobrecarregar operadores em enumerações. Consulte stackoverflow.com/questions/2571456/… .
Arch D. Robison
Sobrecarregando o incremento / decréscimo requer tomar uma decisão sobre o que fazer quando há um excesso
epónimo
3

Assumir que o enum é numerado sequencialmente é propenso a erros. Além disso, convém iterar apenas sobre enumeradores selecionados. Se esse subconjunto for pequeno, fazer um loop sobre ele explicitamente pode ser uma escolha elegante:

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

for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
    // ...
}
Marski
fonte
Esta é uma boa opção, eu acho. Deve fazer parte de uma especificação C ++ mais recente do que eu estava usando quando fiz a pergunta que estou adivinhando?
Adam
Sim. Ele itera sobre um std :: initializer_list <Item>. link .
22719 marski
2

Se você não gosta de poluir sua enumeração com um item COUNT final (porque talvez se você também usar a enumeração em um comutador, o compilador avisará sobre um caso ausente COUNT :), você pode fazer o seguinte:

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);
}
Niels Holst
fonte
2

Aqui está outra solução que funciona apenas para enumerações contíguas. Ele fornece a iteração esperada, exceto a feiura no incremento, que é o lugar a que pertence, pois é isso que está quebrado no 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))
{
    // ...
}
Ethan Bradford
fonte
1
Incrementar pode ser reduzido para foo = Bar(foo + 1).
precisa saber é o seguinte
Obrigado, HolyBlackCat, incorporei sua excelente sugestão! Percebo também que o Riot tem praticamente essa mesma solução, mas está em conformidade com a digitação forte (e, portanto, mais detalhada).
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::arraypode iterar até enumerações não sequenciais sem que a matriz seja instanciada pelo compilador. Isso depende de coisas como as heurísticas de otimização do compilador e se você usa o endereço da matriz.

Nas minhas experiências, descobri que o g++9.1 com -O3otimizará a matriz acima se houver 2 valores não sequenciais ou alguns valores sequenciais (testei até 6). Mas isso só acontece se você tiver uma ifdeclaração. (Tentei uma instrução que comparava um valor inteiro maior que todos os elementos em uma matriz seqüencial e ela delineava a iteração apesar de nenhuma ser excluída, mas quando deixei de fora a instrução if, os valores foram colocados na memória.) valores de uma enumeração não sequencial em [um caso | https://godbolt.org/z/XuGtoc] . Suspeito que esse comportamento estranho se deva a heurísticas profundas relacionadas a caches e previsão de ramificação.

Aqui está um link para uma iteração de teste simples no godbolt que demonstra que o array nem sempre é instanciado.

O preço dessa técnica é escrever os elementos da enumeração duas vezes e manter as duas listas sincronizadas.

Epônimo
fonte
Eu gosto de semânticas simples de loop for de alcance e acho que evoluirá ainda mais, e é por isso que gosto dessa solução.
rtischer8277 24/03
2

No livro de linguagem de programação C ++ de Bjarne Stroustrup, você pode ler que ele está propondo sobrecarregar o operator++específico enum. enumsão tipos definidos pelo usuário e o operador de sobrecarga existe no idioma para essas situações específicas.

Você poderá codificar o seguinte:

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

código de teste: http://cpp.sh/357gb

Mente que estou usando enum class. Código funciona bem com enumtambém. Mas eu prefiro, enum classpois eles são de tipo forte e podem nos impedir de cometer erros no momento da compilação.

LAL
fonte
Um voto negativo foi feito nesta postagem. Alguma razão para não responder à pergunta?
LAL
O motivo é provavelmente porque esta é uma solução terrível em termos de arquitetura: força você a escrever uma lógica global destinada a um componente específico (sua enumeração); além disso, se sua enumeração mudar por qualquer motivo, você será forçado a editar seu + + operador também, como uma abordagem que não é sustentável para nenhum projeto de médio e grande porte, não é uma surpresa que venha da recomendação de Bjarne Stroustrup, na época em que a arquitetura de software era como ficção científica
Manjia
A pergunta original é sobre ter operador para um enum. Não era uma questão arquitetônica. Não acredito que em 2013 o C ++ fosse uma ficção científica.
LAL
1

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

Nota: esse código é muito menos do que a resposta simples do iterador personalizado com modelo.

Você pode fazer isso funcionar com o GCC usando em typeofvez de decltype, mas não tenho esse compilador à mão no momento para garantir que ele seja compilado.

user2407277
fonte
Isso foi escrito ~ 5 anos depois de decltypese tornar C ++ padrão, portanto você não deve recomendar o desatualizado typeofdo antigo GCC. O GCC vagamente recente lida decltypemuito bem. Há outros problemas: as conversões no estilo C são desencorajadas e as macros piores. Recursos adequados do C ++ podem fornecer a mesma funcionalidade genérica. Isso seria melhor reescrito para usar static_caste uma função de modelo: template <typename T> auto inc_enum(T const t) { return static_cast<T>(static cast<int>(t) + 1); }. E elencos não são necessários para não enum class. Como alternativa, os operadores podem ser sobrecarregados por enumtipo (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]
}
Justin Moloney
fonte
Esta solução criará variáveis ​​estáticas desnecessariamente na memória, enquanto o objetivo do enum é apenas criar uma 'máscara' para as constantes
alinhadas
A menos que seja alterado paraconstexpr static const int enumItems[]
Mohammad Kanan
0

Como o C ++ não possui introspecção, não é possível determinar esse tipo de coisa em tempo de execução.

kͩeͣmͮpͥ ͩ
fonte
1
Você poderia me explicar por que a "introspecção" seria necessária para iterar sobre um enum?
Jonathan Mee
Talvez o termo seja Reflexão ?
kͩeͣmͮpͥ # 8/16
2
Estou tentando dizer duas coisas: 1) Por muitas outras respostas, o C ++ pode fazer isso; portanto, se você disser que não pode, é necessário um link ou esclarecimento adicional. 2) Na sua forma atual, este é, na melhor das hipóteses, um comentário, certamente não uma resposta.
Jonathan Mee
Voto negativo a
1
Vou me concentrar novamente em 2 comentários: 1) Não voto negativo porque acho que receber uma participação no site desmotiva desmotiva, acho isso contraproducente 2) Ainda não entendo o que você está tentando dizer, mas parece você entende algo que eu não entendo; nesse caso, eu prefiro que você elabore em vez de excluir uma resposta com voto negativo.
Jonathan Mee
0

Se você soubesse que os valores da enumeração eram seqüenciais, por exemplo, a enumeração Qt: Key, você poderia:

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

Funciona como esperado.

kcrossen
fonte
-1

Apenas faça uma matriz de entradas e faça um loop sobre a matriz, mas faça o último elemento dizer -1 e use-o para a condição de saída.

Se enum for:

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

então crie uma matriz:

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

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

Isso não se decompõe, independentemente das entradas na representação, desde que a verificação -1 não colide com um dos elementos, é claro.

mathreadler
fonte