¿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)?
c++
c++11
cardinality
enum-class
bquenin
fuente
fuente
enum
yenum class
es son conceptos muy diferentes.enum class
valores, entonces, ¿cuál sería el beneficio de saber el número?Respuestas:
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::Count
como 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).fuente
enum class
es en lugar de simpleenum
s. Editaré en un elenco para que quede claro.Para C ++ 17 puede usar
magic_enum::enum_count
lib 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 .
fuente
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.
BEGIN
ySIZE
) ( la respuesta de Cameron también tiene este problema).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_SIZE
el cálculo.fuente
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;
fuente
short
podría aumentarse, porint
ejemplo, 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.)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 ...
fuente
#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;
.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 };
fuente
enum
s simples , esto no funcionará comoExampleCount
es del tipoExample
. Para obtener el número de elementos enExample
,ExampleCount
tendría que convertirse en un tipo entero.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_ENUM
macro 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:
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
Count
valor enumerado adicional empleado en la respuesta aceptada creará muchos dolores de cabeza de advertencia al compilador si se usa unaswitch
declaración. Encuentro launhandled case
advertencia del compilador bastante útil para un mantenimiento más seguro del código, por lo que no querría socavarlo.fuente
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; }
fuente
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 ENUMVAL
después de cada uso.fuente
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 class
el 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.fuente
No, tienes que escribirlo en el código.
fuente
También puede considerar
static_cast<int>(Example::E) + 1
cuál elimina el elemento extra.fuente
Example::E
como último valor en la enumeración. Incluso si este no es el caso,Example::E
el valor literal de 'puede cambiar.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
TransformationTrait
operación:[reflect.ops.objseq] del borrador cubre
ObjectSequence
operaciones, donde particularmente [reflect.ops.objseq] / 1 cubre elget_size
rasgo para extraer el número de elementos para un metaobjeto que satisfaceObjectSequence
: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_v
yget_type_v
simplifiquemos 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
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.
fuente