Czy można określić liczbę elementów klasy wyliczeniowej c ++?

85

Czy można określić liczność c ++ enum class:

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

Próbowałem jednak użyć funkcji sizeofzwracającej rozmiar elementu wyliczeniowego.

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

Czy istnieje standardowy sposób uzyskania liczności (w moim przykładzie 5)?

bquenin
źródło
Pomyślałem, że mógł istnieć specyficzny mechanizm C ++ 11
bquenin
6
Nawiasem mówiąc, to nie jest duplikat. enumi enum classes to bardzo różne pojęcia.
But
@Shoe ... czy oni naprawdę są?
Kyle Strand
1
Wydaje się, że to problem XY, wiem, że to było dawno temu, ale czy pamiętasz, dlaczego musiałeś to zrobić? Nie możesz iterować po enum classwartościach, więc jaka będzie korzyść ze znajomości liczby?
Fantastyczny pan Fox

Odpowiedzi:

72

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::Countjako 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).

Cameron
źródło
1
Wartości wyliczenia są bezpieczne dla typu w klasie wyliczenia, więc „Count” będzie tutaj typu Przykład, a nie int, prawda? Musiałbyś najpierw rzucić 'Count' na int, aby użyć go do rozmiaru.
Man of One Way
@Man: Tak, ta sztuczka jest trochę bardziej skomplikowana z enum classes zamiast zwykłych enums. Zmienię w obsadzie, żeby było jasne.
Cameron
11
Jeśli użyjesz instrukcji switch z tym wyliczeniem, każdy przyzwoity kompilator ostrzeże Cię, że brakuje jednego przypadku. Jeśli jest to często używane, może to być bardzo denerwujące. W tym konkretnym przypadku lepiej byłoby po prostu mieć oddzielną zmienną.
Fantastyczny pan Fox
@FantasticMrFox Zgadzam się w 100% na podstawie doświadczenia. To ostrzeżenie kompilatora jest również ważne. Opublikowałem alternatywne podejście, bardziej zgodne z duchem Twojej rekomendacji.
arr_sea
27

W przypadku C ++ 17 możesz użyć magic_enum::enum_countz 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 .

Neargye
źródło
2
To jest niesamowite! Nie ma potrzeby modyfikowania istniejącego kodu, aby policzyć liczbę członków wyliczenia. Wydaje się, że jest to bardzo elegancko zaimplementowane (po prostu przejrzałem kod)!
andreee
Generalnie odradza się udzielanie odpowiedzi zawierających tylko łącza. Czy mógłbyś to rozszerzyć o opis techniki, której używa twoja biblioteka?
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;

Wynika to z odpowiedzi UglyCodera, ale poprawia ją na trzy sposoby.

  • W wyliczeniu type_safe ( BEGINi SIZE) nie ma żadnych dodatkowych elementów ( odpowiedź Camerona również zawiera ten problem).
    • Kompilator nie będzie narzekał, że brakuje ich w instrukcji switch (poważny problem)
    • Nie można ich przypadkowo przekazać do funkcji oczekujących na Twoje wyliczenie. (nie jest to powszechny problem)
  • Nie wymaga odlewania do użytku. ( Odpowiedź Camerona również zawiera ten problem).
  • Odejmowanie nie ma wpływu na rozmiar typu klasy wyliczeniowej.

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_SIZEobliczeń.

Tytułowy
źródło
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
źródło
Sprytny! Oczywiście nie może mieć żadnych komentarzy ani nietypowych odstępów, a dla naprawdę dużych plików źródłowych podstawowy typ wartości może być większy niż byłby w innym przypadku.
Kyle Strand
@Kyle Strand: jest ten problem: używanie char i masz też więcej niż 256 modułów wyliczających. Ale kompilator ma dobre maniery, aby powiadomić cię o obcięciach itp. LINE jest literałem całkowitym i użycie #line ma limit [1, 2147483647]
UglyCoder
Ah, dobrze. Mimo to, nawet wyliczenie, które w innym przypadku byłoby a, shortmoże zostać podniesione, intnp. Podczas budowania jedności. (Powiedziałbym, że jest to bardziej problem z kompilacjami jedności niż z proponowaną sztuczką.)
Kyle Strand
Sztuczka? :-) Używam go, ale rzadko i z należytym osądem. Jak wszystko w kodowaniu, musimy zwiększyć zalety i wady, a zwłaszcza długoterminowe konsekwencje związane z konserwacją. Ostatnio użyłem go do stworzenia klasy wyliczenia z listy definicji C # (OpenGL wglExt.h).
UglyCoder,
5

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
...
Kirill Suetnov
źródło
Można to ułatwić, usuwając przecinek z #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;.
HolyBlackCat
4

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 };
David Nehme
źródło
W porównaniu z zachowaniem ze zwykłymi literami enums, to nie będzie działać zgodnie ExampleCountz typem Example. Aby uzyskać liczbę elementów w Example, ExampleCountnależałoby rzutować na typ całkowity.
zupa jabłkowa
3

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_ENUMmakro 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:

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

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 Countwyliczona wartość zastosowana w zaakceptowanej odpowiedzi spowoduje bóle głowy ostrzegawcze kompilatora, jeśli użyjesz switchinstrukcji. Uważam, że unhandled caseostrzeżenie kompilatora jest całkiem przydatne dla bezpieczniejszej obsługi kodu, więc nie chciałbym go podważać.

arr_sea
źródło
@FantasticMrFox Dziękujemy za zwrócenie uwagi na problem z zaakceptowaną odpowiedzią. Przedstawiłem tutaj alternatywne podejście, które jest bardziej zgodne z duchem Państwa rekomendacji.
arr_sea
2

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;
}
ixjxk
źródło
2

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 ENUMVALpo każdym użyciu.

MetalTurnip
źródło
1

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 classdomyś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.

Zitrax
źródło
0

Nie, musisz to zapisać w kodzie.


źródło
0

Możesz także rozważyć, static_cast<int>(Example::E) + 1która eliminuje dodatkowy element.

Fabio A. Correa
źródło
8
Ta odpowiedź jest poprawna dla tego konkretnego problemu programistycznego, ale generalnie jest daleka od eleganckiej i podatnej na błędy. Wyliczenie można w przyszłości rozszerzyć o nowe wartości, które mogą zastąpić Example::Eostatnią wartość w wyliczeniu. Nawet jeśli tak nie jest, Example::Edosłowna wartość może się zmienić.
Matthias
0

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 TransformationTraitoperację:

[reflekt.ops.enum] / 2

template <Enum T> struct get_enumerators

Wszystkie specjalizacje get_enumerators<T>powinny spełniać TransformationTraitwymagania (20.10.1). Zagnieżdżony typ o nazwie typewyznacza spełniający typ metaobiektu ObjectSequence, zawierający elementy, które spełniają Enumeratori odzwierciedlają elementy wyliczające typu wyliczenia odzwierciedlone przez T.

[reflekt.ops.objseq] projektu obejmuje ObjectSequenceoperacje, gdzie w szczególności [reflekt.ops.objseq] / 1 obejmuje get_sizecechę wyodrębniania liczby elementów dla meta-obiektu spełniającego ObjectSequence:

[reflekt.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

Wszystkie specjalizacje get_size<T>muszą spełniać UnaryTypeTraitwymagania (20.10.1) o charakterystyce bazowej integral_constant<size_t, N>, gdzie Njest liczbą elementów w ciągu obiektowym.

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_vi dodatkowo get_type_vuproś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

Reflection TS jest kompletne pod względem funkcji : Reflection TS został uznany za kompletny i zostanie wysłany do głównego głosowania na komentarze w lecie. Zauważ ponownie, że obecna składnia TS oparta na metaprogramowaniu jest tylko symbolem zastępczym; informacja zwrotna, o którą proszono, dotyczy podstawowych „wnętrzności” projektu, a komisja już wie, że zamierza zastąpić składnię powierzchni prostszym modelem programowania, który wykorzystuje zwykły kod czasu kompilacji i <>metaprogramowanie w innym stylu.

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.

dfrib
źródło