Czy można określić liczność c ++ enum class
:
enum class Example { A, B, C, D, E };
Próbowałem jednak użyć funkcji sizeof
zwracającej rozmiar elementu wyliczeniowego.
sizeof(Example); // Returns 4 (on my architecture)
Czy istnieje standardowy sposób uzyskania liczności (w moim przykładzie 5)?
c++
c++11
cardinality
enum-class
bquenin
źródło
źródło
enum
ienum class
es to bardzo różne pojęcia.enum class
wartościach, więc jaka będzie korzyść ze znajomości liczby?Odpowiedzi:
Nie bezpośrednio, ale możesz użyć następującej sztuczki:
enum class Example { A, B, C, D, E, Count };
Wtedy liczność jest dostępna jako
static_cast<int>(Example::Count)
.Oczywiście działa to tylko wtedy, gdy pozwolisz na automatyczne przypisanie wartości wyliczenia, zaczynając od 0. Jeśli tak nie jest, możesz ręcznie przypisać poprawną liczność do funkcji Count, co tak naprawdę nie różni się od konieczności utrzymywania oddzielnej stałej tak czy inaczej:
enum class Example { A = 1, B = 2, C = 4, D = 8, E = 16, Count = 5 };
Jedyną wadą jest to, że kompilator pozwoli ci użyć
Example::Count
jako argumentu wartości wyliczeniowej - więc bądź ostrożny, jeśli tego używasz! (Osobiście uważam, że nie stanowi to jednak problemu w praktyce).źródło
enum class
es zamiast zwykłychenum
s. Zmienię w obsadzie, żeby było jasne.W przypadku C ++ 17 możesz użyć
magic_enum::enum_count
z lib https://github.com/Neargye/magic_enum :magic_enum::enum_count<Example>()
-> 4.Gdzie jest wada?
Ta biblioteka używa hackowania specyficznego dla kompilatora (opartego na
__PRETTY_FUNCTION__
/__FUNCSIG__
), który działa na Clang> = 5, MSVC> = 15.3 i GCC> = 9.Przeszukujemy podany zakres interwałów i znajdujemy wszystkie wyliczenia z nazwą, to będzie ich liczba. Przeczytaj więcej o ograniczeniach
Wiele więcej na temat tego włamania w tym poście https://taylorconor.com/blog/enum-reflection .
źródło
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;
Wynika to z odpowiedzi UglyCodera, ale poprawia ją na trzy sposoby.
BEGIN
iSIZE
) nie ma żadnych dodatkowych elementów ( odpowiedź Camerona również zawiera ten problem).Zachowuje przewagę UglyCodera nad odpowiedzią Camerona, że modułom wyliczającym można przypisać dowolne wartości.
Problem (dzielony z UglyCoderem, ale nie z Cameronem ) polega na tym, że sprawia, że nowe linie i komentarze są znaczące ... co jest nieoczekiwane. Więc ktoś mógłby dodać wpis ze spacjami lub komentarz bez korygowania
TEST_SIZE
obliczeń.źródło
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;
źródło
short
może zostać podniesione,int
np. Podczas budowania jedności. (Powiedziałbym, że jest to bardziej problem z kompilacjami jedności niż z proponowaną sztuczką.)Jest jedna sztuczka oparta na X () - makrach: obraz, masz następujące wyliczenie:
enum MyEnum {BOX, RECT};
Sformatuj ponownie na:
#define MyEnumDef \ X(BOX), \ X(RECT)
Następnie poniższy kod definiuje typ wyliczenia:
enum MyEnum { #define X(val) val MyEnumDef #undef X };
Poniższy kod oblicza liczbę elementów wyliczeniowych:
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 ...
źródło
#define MyEnumDef
(i wstawiając go#define X(val) val
), co pozwala policzyć liczbę elementów za pomocą just#define X(val) +1
constexpr std::size_t len = MyEnumDef;
.Jedną ze sztuczek, które możesz wypróbować, jest dodanie wartości wyliczenia na końcu listy i użycie jej jako rozmiaru. W twoim przykładzie
enum class Example { A, B, C, D, E, ExampleCount };
źródło
enum
s, to nie będzie działać zgodnieExampleCount
z typemExample
. Aby uzyskać liczbę elementów wExample
,ExampleCount
należałoby rzutować na typ całkowity.Jeśli korzystasz z narzędzi preprocesora boost, możesz uzyskać liczbę za pomocą
BOOST_PP_SEQ_SIZE(...)
.Na przykład można zdefiniować
CREATE_ENUM
makro w następujący sposób:#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
Następnie wywołując makro:
wygeneruje następujący kod:
enum class Example : std::int32_t { A, B, C, D, E }; static constexpr std::int32_t ExampleCount = 5;
To tylko zarysowanie powierzchni w odniesieniu do narzędzi preprocesora boost. Na przykład, twoje makro może również definiować narzędzia konwersji ciągów do / z i operatory ostream dla twojego silnie wpisanego wyliczenia.
Więcej na temat narzędzi preprocesora boost tutaj: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html
Na marginesie, tak się składa, że zdecydowanie zgadzam się z @FantasticMrFox, że dodatkowa
Count
wyliczona wartość zastosowana w zaakceptowanej odpowiedzi spowoduje bóle głowy ostrzegawcze kompilatora, jeśli użyjeszswitch
instrukcji. Uważam, żeunhandled case
ostrzeżenie kompilatora jest całkiem przydatne dla bezpieczniejszej obsługi kodu, więc nie chciałbym go podważać.źródło
Można to rozwiązać podstępem ze 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__}); \ }(); \ };
Stosowanie:
#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; }
źródło
Jest inny sposób, który nie opiera się na liczbie linii ani szablonach. Jedynym wymaganiem jest umieszczenie wartości wyliczenia we własnym pliku i wykonanie obliczeń przez preprocesor / kompilator w następujący sposób:
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" ;
Pozwala to na umieszczanie komentarzy z wartościami wyliczenia, ponowne przypisywanie wartości i nie wstrzykuje nieprawidłowej wartości wyliczenia „count”, którą należy zignorować / uwzględnić w kodzie.
Jeśli nie zależy Ci na komentarzach, nie potrzebujesz dodatkowego pliku i możesz zrobić tak, jak ktoś powyżej, np .:
#define MY_ENUM_LIST \ ENUMVAL(BANANA) \ ENUMVAL(ORANGE = 7) \ ENUMVAL(KIWI)
i zamień
#include "my_enum_inc.h"
dyrektywy na MY_ENUM_LIST, ale będziesz musiał to zrobić#undef ENUMVAL
po każdym użyciu.źródło
Innym rodzajem „głupiego” rozwiązania tego problemu jest:
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; }();
Kompilując to z
-Werror=switch
tobą, upewnij się, że otrzymasz ostrzeżenie kompilatora, jeśli pominiesz lub zduplikujesz dowolny przypadek przełącznika. Jest to również constexpr, więc jest obliczane w czasie kompilacji.Ale zauważ, że nawet dla en
enum class
domyślną zainicjowaną wartością jest 0, nawet jeśli pierwsza wartość wyliczenia nie jest równa 0. Musisz więc albo zacząć od 0, albo jawnie użyć pierwszej wartości.źródło
Nie, musisz to zapisać w kodzie.
źródło
Możesz także rozważyć,
static_cast<int>(Example::E) + 1
która eliminuje dodatkowy element.źródło
Example::E
ostatnią wartość w wyliczeniu. Nawet jeśli tak nie jest,Example::E
dosłowna wartość może się zmienić.Reflection TS: statyczne odbicie wyliczeń (i innych typów)
Reflection TS , w szczególności [Reflection TS.enum] / 2 najnowszej wersji szkicu Reflection TS oferuje
get_enumerators
TransformationTrait
operację:[reflekt.ops.objseq] projektu obejmuje
ObjectSequence
operacje, gdzie w szczególności [reflekt.ops.objseq] / 1 obejmujeget_size
cechę wyodrębniania liczby elementów dla meta-obiektu spełniającegoObjectSequence
:W związku z tym w Reflection TS miało zostać zaakceptowane i zaimplementowane w obecnej formie, liczbę elementów wyliczenia można obliczyć w czasie kompilacji w następujący sposób:
enum class Example { A, B, C, D, E }; using ExampleEnumerators = get_enumerators<Example>::type; static_assert(get_size<ExampleEnumerators>::value == 5U, "");
gdzie prawdopodobnie zobaczymy szablony aliasów
get_enumerators_v
i dodatkowoget_type_v
uprościmy refleksję:enum class Example { A, B, C, D, E }; using ExampleEnumerators = get_enumerators_t<Example>; static_assert(get_size_v<ExampleEnumerators> == 5U, "");
Stan na Reflection TS
Jak stwierdzono w raporcie z podróży Herb Suttera : Letnie spotkanie norm ISO C ++ (Rapperswil) z letniego posiedzenia komisji ISO C ++ 9 czerwca 2018 r., Reflection TS zostało uznane za kompletne
i był początkowo planowany dla C ++ 20 , ale jest trochę niejasne, czy Reflection TS nadal będzie miało szansę na pojawienie się w wersji C ++ 20.
źródło