¿Es posible determinar el número de elementos de una clase de enumeración de c ++?

85

¿Es posible determinar la cardinalidad de un c ++ enum class:

enum class Example { A, B, C, D, E };

Intenté usar sizeof, sin embargo, devuelve el tamaño de un elemento de enumeración.

sizeof(Example); // Returns 4 (on my architecture)

¿Existe una forma estándar de obtener la cardinalidad (5 en mi ejemplo)?

bquenin
fuente
Pensé que podría haber habido un mecanismo específico de C ++ 11
bquenin
6
Esto no es un duplicado, por cierto. enumy enum classes son conceptos muy diferentes.
Zapato
@Zapato ... ¿realmente lo son?
Kyle Strand
1
Esto parece un problema XY, sé que fue hace mucho tiempo, pero ¿recuerdas por qué tenías que hacer esto? No puede iterar sobre enum classvalores, entonces, ¿cuál sería el beneficio de saber el número?
Fantastic Mr Fox

Respuestas:

71

No directamente, pero podrías usar el siguiente truco:

enum class Example { A, B, C, D, E, Count };

Entonces la cardinalidad está disponible como static_cast<int>(Example::Count).

Por supuesto, esto solo funciona bien si permite que los valores de la enumeración se asignen automáticamente, comenzando desde 0.Si ese no es el caso, puede asignar manualmente la cardinalidad correcta a Count, que en realidad no es diferente de tener que mantener una constante separada de todas formas:

enum class Example { A = 1, B = 2, C = 4, D = 8, E = 16, Count = 5 };

La única desventaja es que el compilador le permitirá usarlo Example::Countcomo argumento para un valor de enumeración, ¡así que tenga cuidado si usa esto! (Aunque personalmente considero que esto no es un problema en la práctica).

Cameron
fuente
1
Los valores de enumeración son seguros para los tipos en una clase de enumeración, por lo que 'Count' será de tipo Example aquí y no int, ¿verdad? Primero tendrías que lanzar 'Count' a un int para usarlo para el tamaño.
Man of One Way
@Man: Sí, este truco es un poco más complicado con enum classes en lugar de simple enums. Editaré en un elenco para que quede claro.
Cameron
11
Si usa una declaración de cambio con esta enumeración, cualquier compilador decente le advertirá que le falta un caso. Si se usa mucho, puede ser muy molesto. Podría ser mejor tener una variable separada en este caso específico.
Fantastic Mr Fox
@FantasticMrFox Estoy 100% de acuerdo, basado en la experiencia. Esa advertencia del compilador también es importante. He publicado un enfoque alternativo, más acorde con el espíritu de su recomendación.
arr_sea
26

Para C ++ 17 puede usar magic_enum::enum_countlib https://github.com/Neargye/magic_enum :

magic_enum::enum_count<Example>() -> 4.

¿Dónde está el inconveniente?

Esta biblioteca utiliza un truco específico del compilador (basado en __PRETTY_FUNCTION__/ __FUNCSIG__), que funciona en Clang> = 5, MSVC> = 15.3 y GCC> = 9.

Pasamos por el rango de intervalo dado y encontramos todas las enumeraciones con un nombre, este será su recuento. Leer más sobre limitaciones

Mucho más sobre este truco en esta publicación https://taylorconor.com/blog/enum-reflection .

Neargye
fuente
2
¡Esto es asombroso! No es necesario modificar el código existente para contar el número de miembros de enumeración. ¡También esto parece implementado de manera muy elegante (solo hojeado el código)!
andreee
En general, no se recomiendan las respuestas de solo enlace. ¿Podría ampliar esto con una descripción de la técnica que utiliza su biblioteca?
Adrian McCarthy
24
constexpr auto TEST_START_LINE = __LINE__;
enum class TEST { // Subtract extra lines from TEST_SIZE if an entry takes more than one 
    ONE = 7
  , TWO = 6
  , THREE = 9
};
constexpr auto TEST_SIZE = __LINE__ - TEST_START_LINE - 3;

Esto se deriva de la respuesta de UglyCoder, pero la mejora de tres maneras.

  • No hay elementos adicionales en la enumeración type_safe ( BEGINy SIZE) ( la respuesta de Cameron también tiene este problema).
    • El compilador no se quejará de que falten en una declaración de cambio (un problema importante)
    • No se pueden pasar inadvertidamente a funciones que esperan su enumeración. (no es un problema común)
  • No requiere fundición para su uso. ( La respuesta de Cameron también tiene este problema).
  • La resta no interfiere con el tamaño del tipo de clase enum.

Conserva la ventaja de UglyCoder sobre la respuesta de Cameron de que a los enumeradores se les pueden asignar valores arbitrarios.

Un problema (compartido con UglyCoder pero no con Cameron ) es que hace que las nuevas líneas y los comentarios sean significativos ... lo cual es inesperado. Entonces, alguien podría agregar una entrada con espacios en blanco o un comentario sin ajustar TEST_SIZEel cálculo.

Epónimo
fuente
7
enum class TEST
{
    BEGIN = __LINE__
    , ONE
    , TWO
    , NUMBER = __LINE__ - BEGIN - 1
};

auto const TEST_SIZE = TEST::NUMBER;

// or this might be better 
constexpr int COUNTER(int val, int )
{
  return val;
}

constexpr int E_START{__COUNTER__};
enum class E
{
    ONE = COUNTER(90, __COUNTER__)  , TWO = COUNTER(1990, __COUNTER__)
};
template<typename T>
constexpr T E_SIZE = __COUNTER__ - E_START - 1;
UglyCoder
fuente
¡Inteligente! No puede haber comentarios o espaciado inusual, por supuesto, y para archivos de origen realmente grandes, el tipo de valor subyacente podría ser mayor de lo que sería de otra manera.
Kyle Strand
@Kyle Strand: existe ese problema: usar char y también tiene más de 256 enumeradores. Pero el compilador tiene la buena educación de notificarle sobre truncamientos, etc. LINE es un literal entero y el uso de #line tiene un límite de [1, 2147483647]
UglyCoder
Ah bien. Aún así, incluso una enumeración que de otro modo sería una shortpodría aumentarse, por intejemplo, al hacer una construcción de unidad. (Yo diría que esto es más de un problema con la unidad que construye con su truco propuesto, sin embargo.)
Kyle Strand
¿Truco? :-) Lo uso, pero rara vez y con el debido criterio. Como todo en la codificación, necesitamos mejorar los pros y los contras y especialmente las implicaciones de mantenimiento a largo plazo. Lo he usado recientemente para crear una clase de enumeración a partir de una lista de C #defines (OpenGL wglExt.h).
UglyCoder
5

Hay un truco basado en X () - macros: image, tienes la siguiente enumeración:

enum MyEnum {BOX, RECT};

Vuelva a formatearlo para:

#define MyEnumDef \
    X(BOX), \
    X(RECT)

Entonces el siguiente código define el tipo de enumeración:

enum MyEnum
{
#define X(val) val
    MyEnumDef
#undef X
};

Y el siguiente código calcula el número de elementos de enumeración:

template <typename ... T> void null(T...) {}

template <typename ... T>
constexpr size_t countLength(T ... args)
{
    null(args...); //kill warnings
    return sizeof...(args);
}

constexpr size_t enumLength()
{
#define XValue(val) #val
    return countLength(MyEnumDef);
#undef XValue
}

...
std::array<int, enumLength()> some_arr; //enumLength() is compile-time
std::cout << enumLength() << std::endl; //result is: 2
...
Kirill Suetnov
fuente
Esto se puede hacer más fácil quitando la coma de #define MyEnumDef(y colocándola #define X(val) val), lo que le permite contar el número de elementos usando solo #define X(val) +1 constexpr std::size_t len = MyEnumDef;.
HolyBlackCat
4

Un truco que puede probar es agregar un valor de enumeración al final de su lista y usarlo como tamaño. En tu ejemplo

enum class Example { A, B, C, D, E, ExampleCount };
David Nehme
fuente
En comparación con el comportamiento con enums simples , esto no funcionará como ExampleCountes del tipo Example. Para obtener el número de elementos en Example, ExampleCounttendría que convertirse en un tipo entero.
sopa de manzana
3

Si utiliza las utilidades del preprocesador de boost, puede obtener el recuento usando BOOST_PP_SEQ_SIZE(...).

Por ejemplo, se podría definir la CREATE_ENUMmacro de la siguiente manera:

#include <boost/preprocessor.hpp>

#define ENUM_PRIMITIVE_TYPE std::int32_t

#define CREATE_ENUM(EnumType, enumValSeq)                                  \
enum class EnumType : ENUM_PRIMITIVE_TYPE                                  \
{                                                                          \
   BOOST_PP_SEQ_ENUM(enumValSeq)                                           \
};                                                                         \
static constexpr ENUM_PRIMITIVE_TYPE EnumType##Count =                     \
                 BOOST_PP_SEQ_SIZE(enumValSeq);                            \
// END MACRO   

Luego, llamando a la macro:

CREATE_ENUM(Example, (A)(B)(C)(D)(E));

generaría el siguiente código:

enum class Example : std::int32_t 
{
   A, B, C, D, E 
};
static constexpr std::int32_t ExampleCount = 5;

Esto es solo un rasguño de la superficie con respecto a las herramientas del preprocesador de impulso. Por ejemplo, su macro también podría definir a / desde utilidades de conversión de cadenas y operadores ostream para su enumeración fuertemente tipada.

Más sobre las herramientas de preprocesador boost aquí: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html


Aparte, estoy totalmente de acuerdo con @FantasticMrFox en que el Countvalor enumerado adicional empleado en la respuesta aceptada creará muchos dolores de cabeza de advertencia al compilador si se usa una switchdeclaración. Encuentro la unhandled caseadvertencia del compilador bastante útil para un mantenimiento más seguro del código, por lo que no querría socavarlo.

arr_sea
fuente
@FantasticMrFox Gracias por señalar un problema preocupante con la respuesta aceptada. He proporcionado un enfoque alternativo que está más en línea con el espíritu de su recomendación.
arr_sea
2

Se puede resolver con un truco con std :: initializer_list:

#define TypedEnum(Name, Type, ...)                                \
struct Name {                                                     \
    enum : Type{                                                  \
        __VA_ARGS__                                               \
    };                                                            \
    static inline const size_t count = []{                        \
        static Type __VA_ARGS__; return std::size({__VA_ARGS__}); \
    }();                                                          \
};

Uso:

#define Enum(Name, ...) TypedEnum(Name, int, _VA_ARGS_)
Enum(FakeEnum, A = 1, B = 0, C)

int main()
{
    std::cout << FakeEnum::A     << std::endl
              << FakeEnun::count << std::endl;
}
ixjxk
fuente
2

Hay otra forma que no depende de los recuentos de líneas o las plantillas. El único requisito es pegar los valores de enumeración en su propio archivo y hacer que el preprocesador / compilador haga el recuento así:

my_enum_inc.h

ENUMVAL(BANANA)
ENUMVAL(ORANGE=10)
ENUMVAL(KIWI)
...
#undef ENUMVAL

my_enum.h

typedef enum {
  #define ENUMVAL(TYPE) TYPE,
  #include "my_enum_inc.h"
} Fruits;

#define ENUMVAL(TYPE) +1
const size_t num_fruits =
  #include "my_enum_inc.h"
  ;

Esto le permite poner comentarios con los valores de enumeración, reasignar valores y no inyecta un valor de enumeración de 'recuento' no válido que debe ignorarse / contabilizarse en el código.

Si no le importan los comentarios, no necesita un archivo adicional y puede hacer lo mismo que alguien mencionado anteriormente, por ejemplo:

#define MY_ENUM_LIST \
    ENUMVAL(BANANA) \
    ENUMVAL(ORANGE = 7) \
    ENUMVAL(KIWI)

y reemplace las #include "my_enum_inc.h"directivas con MY_ENUM_LIST, pero deberá hacerlo #undef ENUMVALdespués de cada uso.

MetalTurnip
fuente
1

Otro tipo de solución "estúpida" para esto es:

enum class Example { A, B, C, D, E };

constexpr int ExampleCount = [] {
  Example e{};
  int count = 0;
  switch (e) {
    case Example::A:
      count++;
    case Example::B:
      count++;
    case Example::C:
      count++;
    case Example::D:
      count++;
    case Example::E:
      count++;
  }

  return count;
}();

Al compilar esto -Werror=switch, asegúrese de obtener una advertencia del compilador si omite o duplica cualquier caso de cambio. También es constexpr, por lo que se calcula en tiempo de compilación.

Pero tenga en cuenta que incluso para un en, enum classel valor inicializado predeterminado es 0 incluso si el primer valor de la enumeración no es 0. Por lo tanto, debe comenzar en 0 o usar explícitamente el primer valor.

Zitrax
fuente
0

No, tienes que escribirlo en el código.


fuente
0

También puede considerar static_cast<int>(Example::E) + 1cuál elimina el elemento extra.

Fabio A. Correa
fuente
8
Esta respuesta es correcta para este problema de programación específico, pero en general está lejos de ser elegante y propensa a errores. La enumeración se puede extender con nuevos valores en el futuro que se pueden reemplazar Example::Ecomo último valor en la enumeración. Incluso si este no es el caso, Example::Eel valor literal de 'puede cambiar.
Matthias
0

Reflexión TS: reflexión estática de enumeraciones (y otros tipos)

Reflection TS , particularmente [reflect.ops.enum] / 2 de la última versión del borrador de Reflection TS ofrece la get_enumerators TransformationTraitoperación:

[reflect.ops.enum] / 2

template <Enum T> struct get_enumerators

Todas las especializaciones de get_enumerators<T>deben cumplir los TransformationTraitrequisitos (20.10.1). El tipo anidado denominado typedesigna un tipo de metaobjeto satisfactorio ObjectSequence, que contiene elementos que satisfacen Enumeratory reflejan a los enumeradores del tipo de enumeración reflejado por T.

[reflect.ops.objseq] del borrador cubre ObjectSequenceoperaciones, donde particularmente [reflect.ops.objseq] / 1 cubre el get_sizerasgo para extraer el número de elementos para un metaobjeto que satisface ObjectSequence:

[reflect.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

Todas las especializaciones de get_size<T>deben cumplir los UnaryTypeTraitrequisitos (20.10.1) con una característica base de integral_constant<size_t, N>, donde Nes el número de elementos en la secuencia del objeto.

Por lo tanto, en Reflection TS debían aceptarse e implementarse en su forma actual, el número de elementos de una enumeración se puede calcular, en tiempo de compilación, de la siguiente manera:

enum class Example { A, B, C, D, E };

using ExampleEnumerators = get_enumerators<Example>::type;

static_assert(get_size<ExampleEnumerators>::value == 5U, "");

donde es probable que veamos plantillas de alias get_enumerators_vy get_type_vsimplifiquemos aún más la reflexión:

enum class Example { A, B, C, D, E };

using ExampleEnumerators = get_enumerators_t<Example>;

static_assert(get_size_v<ExampleEnumerators> == 5U, "");

Estado en Reflection TS

Como se indica en el informe de viaje de Herb Sutter : Reunión de estándares ISO C ++ de verano (Rapperswil) de la reunión de verano del comité ISO C ++ del 9 de junio de 2018, Reflection TS ha sido declarado como completo

Reflection TS tiene características completas : Reflection TS se declaró con características completas y se enviará para su boleta de comentarios principal durante el verano. Tenga en cuenta nuevamente que la sintaxis basada en metaprogramación de la plantilla actual de TS es solo un marcador de posición; la retroalimentación solicitada está en las “entrañas” centrales del diseño, y el comité ya sabe que tiene la intención de reemplazar la sintaxis superficial con un modelo de programación más simple que usa código de tiempo de compilación común y <>metaprogramación no estilo.

e inicialmente se planeó para C ++ 20 , pero no está claro si Reflection TS aún tendrá la oportunidad de llegar a la versión de C ++ 20.

dfrib
fuente