¿Cómo puedo iterar sobre una enumeración?

304

Me acabo de dar cuenta de que no puedes usar operadores matemáticos estándar en una enumeración como ++ o + =

Entonces, ¿cuál es la mejor manera de iterar a través de todos los valores en una enumeración de C ++?

Adán
fuente
2
Uno de los muchos enfoques: Cuando la enumeración no es suficiente: Clases de enumeración para C ++ . Y, si quieres algo más encapsulado, prueba este enfoque de James Kanze.
Don Wakefield el
Los elementos vinculados tienen algunas respuestas interesantes.
Tony
¡Estas respuestas no parecen cubrir el problema que intpuede no ser lo suficientemente grande! ( [C++03: 7.2/5])
Carreras de ligereza en órbita
Curiosamente, puede definir operator++en enumeraciones; Sin embargo, para que pueda hacer for(Enum_E e = (Enum_E)0; e < ENUM_COUNT; e++). Tenga en cuenta que debe enviar 0a Enum_Eporque C ++ prohíbe los operadores de asignación en las enumeraciones.
weberc2
Si hubiera un operador de tiempo de compilación, similar a la forma en que funciona sizeof, que podría emitir un literal std :: initializer_list compuesto por los valores de la enumeración, tendríamos una solución y no implicaría ninguna sobrecarga de tiempo de ejecución.
jbruni

Respuestas:

263

La forma típica es la siguiente:

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

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

Tenga en cuenta que Lastla iteración debe omitir la enumeración . Al utilizar esta Lastenumeración "falsa" , no tiene que actualizar su condición de terminación en el bucle for hasta la última enumeración "real" cada vez que desee agregar una enumeración nueva. Si desea agregar más enumeraciones más tarde, solo agréguelas antes de Last. El bucle en este ejemplo seguirá funcionando.

Por supuesto, esto se rompe si se especifican los valores de enumeración:

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

Esto ilustra que una enumeración no está destinada a iterar. La forma típica de lidiar con una enumeración es usarla en una declaración de cambio.

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

Si realmente quieres enumerar, rellena los valores de enumeración en un vector e itera sobre eso. Esto también se ocupará adecuadamente de los valores de enumeración especificados.

andreas buykx
fuente
13
Tenga en cuenta que, en la primera parte del ejemplo, si desea usar 'i' como una enumeración de Foo y no un int, deberá emitir estática como: static_cast <Foo> (i)
Clayton
55
También estás saltando Last in the loop. Debería ser <= Último
Tony
20
@Tony Last está destinado a ser omitido. Si desea agregar más enumeraciones más tarde, agréguelas antes de Última ... el bucle en el primer ejemplo seguirá funcionando. Al utilizar una última enumeración "falsa", no tiene que actualizar su condición de terminación en el bucle for a la última enumeración "real" cada vez que desee agregar una nueva enumeración.
timidpueo
Excepto que ahora ha asignado memoria cuando una enumeración, siempre que esté indexada a cero y sea estrictamente continua, puede hacer esa tarea sin asignar memoria.
Nube
1
Tenga en cuenta que para que esta definición de enumeración sea segura para las actualizaciones, se debe definir un valor UNKNOWN = 0. Además, sugeriría simplemente descartar el defaultcaso al cambiar los valores de enumeración, ya que podría ocultar casos en los que se olvidó el manejo de valores hasta el tiempo de ejecución. En cambio, uno debe codificar todos los valores y usar el UNKNOWNcampo para detectar incompatibilidades.
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
fuente
¡Gracias! Tenga en cuenta que si está cruzando archivos / clases y si la compatibilidad con MS le está dando problemas con las constantes no enteras declaradas por el encabezado, ayuda en mi compilador colocar explícitamente el tamaño en el tipo en el encabezado: static const Type All[3];y luego puedo para inicializar en la fuente: const MyEnum::Type MyEnum::All[3] = { a, b, c }; Antes de hacer eso, recibía Error in range-based for...errores desagradables (porque la matriz tenía un tamaño desconocido). Lo descubrí gracias a una respuesta relacionada
sabio
1
La versión de matriz es muy amigable de copiar y pegar. La respuesta más satisfactoria además, "NO" o "solo para secuencial". Probablemente macro amigable incluso.
Paulo Neves
1
Esta podría ser una buena solución para enumeraciones con una pequeña cantidad de elementos, pero para enumeraciones con una gran cantidad de elementos no debe ajustarse bien.
kato2
20

Si su enumeración comienza con 0 y el incremento es siempre 1.

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

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

Si no, supongo que el único motivo es crear algo así como

vector<enumType> vEnums;

agregue los elementos y use iteradores normales ...

João Augusto
fuente
19

Con c ++ 11, en realidad hay una alternativa: escribir un iterador personalizado con plantilla simple.

supongamos que tu enumeración es

enum class foo {
  one,
  two,
  three
};

Este código genérico hará el truco, de manera bastante eficiente: colóquelo en un encabezado genérico, le servirá para cualquier enumeración que necesite 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; }
};

Tendrás que especializarlo

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

Y luego puedes iterar usando range-for

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

La suposición de que no tienes huecos en tu enumeración sigue siendo cierta; no se supone el número de bits realmente necesarios para almacenar el valor de enumeración (gracias a std :: subyacente_tipo)

Francesco Chemolli
fuente
1
@lepe? Simplemente realiza una definición de tipo diferente para una enumeración diferente.
Andrew Lazarus
2
@lepe Eso es como decir que std::vectorno es genérico porque std::vector<foo>está vinculado a foo.
Kyle Strand
1
typedef Iterator<color, color::green, color::red> colorIterator;Asegúrese de comprender cómo funcionan las instancias de plantilla.
Andrew Lazarus
2
Oh, veo el problema, foo operator*() { ...debería serlo C operator*() { ....
Kyle Strand
1
@KyleStrand: ¡Lo tienes! eso tiene mucho sentido ahora. ¿Se debe actualizar el código? Gracias a todos por sus explicaciones.
lepe
16

estas soluciones son demasiado complicadas, me gusta eso:

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

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

for (NodePosition pos : NodePositionVector) {
...
}
Enzojz
fuente
No sé por qué esto fue rechazado. Es una solución razonable.
Paul Brannan
11
Supongo que fue porque las entradas deben mantenerse en dos lugares.
Ant
¿C ++ permite la for (NodePosition pos : NodePositionVector)sintaxis? Hasta donde sé, esta es la sintaxis de Java, y necesitará iteradores en C ++ para hacer algo equivalente.
thegreatjedi
3
@thegreatjedi Desde C ++ 11 puedes, incluso más simple: for (auto pos: NodePositionVector) {..}
Enzojz
@thegreatjedi Hubiera sido más rápido buscar, o incluso compilar un programa de prueba, que hacer esa pregunta. Pero sí, desde C ++ 11 es una sintaxis C ++ perfectamente válida, que el compilador traduce al código equivalente (y mucho más detallado / menos abstracto), generalmente a través de iteradores; ver cppreference . Y, como dijo Enzojz, C ++ 11 también agregó auto, por lo que no tiene que declarar el tipo de elementos explícitamente, a menos que (A) necesite usar un operador de conversión o (B) no le guste autopor alguna razón . La mayoría de los forusuarios de autorango usan AFAICT
underscore_d
9

A menudo lo hago así

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

o si no es sucesivo, pero con un paso regular (por ejemplo, banderas de bits)

    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
fuente
pero, ¿de qué sirve imprimir los valores en bits?
Anu
1
Usar enumeraciones para crear máscaras de bits. por ejemplo, combine varias opciones en una sola variable, luego use FOR para probar cada opción. Se arregló mi publicación con un mejor ejemplo.
Niki
¡Todavía no puedo usarlo (y tu publicación aún muestra el viejo ejemplo)! Usar enum como máscaras de bits es realmente útil, ¡pero no pudo conectar los puntos! ¿podría explicar un poco más su ejemplo en los detalles? También puede poner el código adicional.
Anu
@anu Lo siento, no vi tu comentario. Se agregó la clase Frog como ejemplo de máscara de bits
Niki
8

No puedes con una enumeración. Quizás una enumeración no sea la más adecuada para su situación.

Una convención común es nombrar el último valor de enumeración como MAX y usarlo para controlar un bucle usando un int.

Corey Trager
fuente
Aquí hay varios ejemplos que demuestran lo contrario. En tu propia afirmación te estás contradiciendo (segunda línea).
Niki
6

Algo que no se ha cubierto en las otras respuestas = si está usando enumeraciones de C ++ 11 fuertemente tipadas, no puede usarlas ++ni + inten ellas. En ese caso, se requiere un poco de una solución más desordenada:

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)

}
Alboroto
fuente
3
... pero C ++ 11 introduce el rango basado en eso que se muestra en otras respuestas. :-)
sabio
5

Puede intentar definir la siguiente 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))

Ahora puedes usarlo:

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

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

Se puede usar para iterar hacia atrás y hacia adelante a través de enteros, enteros y caracteres sin signo:

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


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

A pesar de su definición incómoda, está optimizado muy bien. Miré al desensamblador en VC ++. El código es extremadamente eficiente. No se desanime, sino las tres declaraciones for: ¡el compilador producirá solo un ciclo después de la optimización! Incluso puede definir bucles cerrados:

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

Obviamente no puede iterar a través de tipos enumerados con huecos.

Mikhail Semenov
fuente
1
¡Ese es un truco maravilloso! Aunque es más apropiado para C que para C ++, se podría decir.
einpoklum
3
_A1no es un nombre permitido, es un guión bajo destacado con la siguiente letra mayúscula.
Martin Ueding
3

También puede sobrecargar los operadores de incremento / decremento para su tipo enumerado.

JohnMcG
fuente
1
No puede sobrecargar ningún operador en tipos enumerados C o C ++. A menos que creara una estructura / clase que emulara una enumeración de valores.
Trevor Hickey
2
C ++ permite sobrecargar operadores en enumeraciones. Ver stackoverflow.com/questions/2571456/… .
Arch D. Robison
La sobrecarga del incremento / decremento requiere tomar una decisión sobre qué hacer cuando hay un desbordamiento
Eponymous
3

Asumir que enum está numerado secuencialmente es propenso a errores. Además, es posible que desee iterar solo sobre los enumeradores seleccionados. Si ese subconjunto es pequeño, recorrerlo explícitamente podría ser una opción elegante:

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

for (auto item : {Wolf, Goat, Cabbage}) { // or Item::Wolf, ...
    // ...
}
marski
fuente
Esta es una buena opción, creo. ¿Debe ser parte de una nueva especificación de C ++ que estaba usando cuando hice la pregunta que supongo?
Adam
Si. Se itera sobre un std :: initializer_list <Item>. enlace .
marski
2

Si no le gusta contaminar su enumeración con un elemento COUNT final (porque tal vez si también utiliza la enumeración en un conmutador, entonces el compilador le avisará de la falta de un caso COUNT :), puede hacer esto:

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
fuente
2

Aquí hay otra solución que solo funciona para enumeraciones contiguas. Da la iteración esperada, excepto por la fealdad en el incremento, que es donde pertenece, ya que eso es lo que está roto en 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
fuente
1
El incremento se puede acortar a foo = Bar(foo + 1).
HolyBlackCat
Gracias, HolyBlackCat, ¡he incorporado tu excelente sugerencia! También noto que Riot tiene más o menos esta misma solución, pero conforme con un tipeo fuerte (y, por lo tanto, más detallado).
Ethan Bradford el
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);
}

UNA constexpr std::array puede iterar incluso enumeraciones no secuenciales sin que la matriz sea instanciada por el compilador. Esto depende de cosas como la heurística de optimización del compilador y de si toma la dirección de la matriz.

En mis experimentos, descubrí que g++9.1 -O3optimizará la matriz anterior si hay 2 valores no secuenciales o bastantes valores secuenciales (probé hasta 6). Pero solo hace esto si tiene una ifdeclaración. (Probé una declaración que comparó un valor entero mayor que todos los elementos en una matriz secuencial y alineó la iteración a pesar de que no se excluyó ninguno, pero cuando omití la declaración if, los valores se guardaron en la memoria). valores de una enumeración no secuencial en [un caso | https://godbolt.org/z/XuGtoc] . Sospecho que este comportamiento extraño se debe a la profunda heurística que tiene que ver con los cachés y la predicción de ramas.

Aquí hay un enlace a una iteración de prueba simple en godbolt que demuestra que la matriz no siempre se instancia.

El precio de esta técnica es escribir los elementos de enumeración dos veces y mantener las dos listas sincronizadas.

Epónimo
fuente
Me gusta la semántica de bucle for de rango simple y creo que evolucionará aún más, por eso me gusta esta solución.
rtischer8277
2

En el libro de lenguaje de programación C ++ de Bjarne Stroustrup, puede leer que está proponiendo sobrecargar el operator++para su específico enum. enumson tipos definidos por el usuario y el operador de sobrecarga existe en el idioma para estas situaciones específicas.

Podrás codificar lo siguiente:

#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 prueba: http://cpp.sh/357gb

Mente que estoy usando enum class. El código funciona bien con enumtambién. Pero prefiero enum classya que son de tipo fuerte y pueden evitar que cometamos errores en el momento de la compilación.

LAL
fuente
Se emitió un voto negativo en esta publicación. ¿Alguna razón por la que no respondería a la pregunta?
LAL
La razón es probablemente porque esta es una solución terrible desde el punto de vista arquitectónico: te obliga a escribir una lógica global destinada a un componente específico (tu enumeración), además, si tu enumeración cambia por cualquier razón, te ves obligado a editar tu + + operador también, como enfoque no es sostenible para ningún proyecto de mediana-gran escala, no es una sorpresa que provenga de una recomendación de Bjarne Stroustrup, en la época en que la arquitectura del software era como ciencia ficción
Manjia
La pregunta original es acerca de tener un operador para un enum. No era una pregunta arquitectónica. No creo que en 2013 C ++ fuera una ciencia ficción.
LAL
1

Para compiladores de 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: este es mucho menos código que la respuesta del iterador personalizado simple con plantilla.

Puede hacer que esto funcione con GCC usando en typeoflugar de decltype, pero no tengo ese compilador a mano en este momento para asegurarme de que se compila.

usuario2407277
fuente
Esto se escribió ~ 5 años después de decltypeconvertirse en C ++ estándar, por lo que no debe recomendar el anticuado typeofdel antiguo CCG. GCC vagamente reciente se maneja decltypemuy bien. Hay otros problemas: se desalientan los modelos de estilo C y las macros son peores. Las características adecuadas de C ++ pueden proporcionar la misma funcionalidad genérica. Esto sería mejor volver a escribir a utilizar static_casty una función de plantilla: template <typename T> auto inc_enum(T const t) { return static_cast<T>(static cast<int>(t) + 1); }. Y los moldes no son necesarios para no enum class. Alternativamente, los operadores pueden sobrecargarse 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
fuente
Esta solución va a crear una variables estáticas innecesariamente en la memoria mientras que el objetivo de enumeración es sólo para crear una 'máscara' a las constantes inline
TheArquitect
A menos que haya cambiado aconstexpr static const int enumItems[]
Mohammad Kanan
0

C ++ no tiene introspección, por lo que no puede determinar este tipo de cosas en tiempo de ejecución.

kͩeͣmͮpͥ ͩ
fuente
1
¿Podría explicarme por qué sería necesaria la "introspección" para iterar sobre una enumeración?
Jonathan Mee el
¿Quizás el término es Reflexión ?
kͩeͣmͮpͥ ͩ
2
Estoy tratando de decir 2 cosas: 1) Por muchas otras respuestas, C ++ puede lograr esto, por lo que si va a decir que no puede, se requiere un enlace o una aclaración adicional. 2) En su forma actual, este es, en el mejor de los casos, un comentario, ciertamente no una respuesta.
Jonathan Mee
Rechace mi respuesta entonces - Creo que lo ha justificado más que bien
kͩeͣmͮpͥ ͩ
1
Volveré a escribir 2 comentarios: 1) No voté en contra porque encuentro que recibir un voto en contra desmotiva la participación en el sitio, encuentro que es contraproducente 2) Todavía no entiendo lo que estás tratando de decir, pero parece que entiendes algo que no entiendo, en cuyo caso preferiría que elabores en lugar de eliminar una respuesta rechazada.
Jonathan Mee
0

Si supiera que los valores de enumeración son secuenciales, por ejemplo, Qt: Clave enumeración, podría:

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 se esperaba.

kcrossen
fuente
-1

Simplemente haga una matriz de entradas y repita sobre la matriz, pero haga que el último elemento diga -1 y úselo para la condición de salida.

Si enum es:

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

luego crea una matriz:

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

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

Esto no se descompone, independientemente de las entradas en la representación, siempre que la comprobación -1 no choque con uno de los elementos, por supuesto.

mathreadler
fuente