È possibile determinare il numero di elementi di una classe enum c ++?


86

È possibile determinare la cardinalità di un c ++ enum class:

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

Ho provato a usare sizeof, tuttavia, restituisce la dimensione di un elemento enum.

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

Esiste un modo standard per ottenere la cardinalità (5 nel mio esempio)?


Ho pensato che potesse esserci uno specifico meccanismo c ++ 11
bquenin

6
Questo non è un duplicato, comunque. enume enum classsono concetti molto diversi.
Scarpa

@Shoe ... sono davvero, però?
Kyle Strand

1
Sembra un problema XY, so che risale a molto tempo fa, ma ricordi perché avevi bisogno di farlo? Non è possibile iterare su un enum classvalore, quindi quale sarebbe il vantaggio di conoscere il numero?
Fantastico Mr Fox

Risposte:


73

Non direttamente, ma potresti usare il seguente trucco:

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

Quindi la cardinalità è disponibile come static_cast<int>(Example::Count).

Ovviamente, questo funziona bene solo se lasci che i valori dell'enum vengano assegnati automaticamente, a partire da 0. In caso contrario, puoi assegnare manualmente la cardinalità corretta a Count, che in realtà non è diverso dal dover mantenere una costante separata Comunque:

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

L'unico svantaggio è che il compilatore ti permetterà di usarlo Example::Countcome argomento per un valore enum, quindi fai attenzione se lo usi! (Personalmente trovo che questo non sia un problema nella pratica, però.)


1
I valori enum sono indipendenti dai tipi in una classe enum, quindi "Count" sarà di tipo Example qui e non int, giusto? Dovresti prima lanciare "Count" su un int per usarlo per size.
Man of One Way

@ Man: Sì, questo trucco è un po 'più complicato con enum classes invece che con semplici enums. Modificherò in un cast per essere chiari.
Cameron

11
Se usi un'istruzione switch con questo enum, qualsiasi compilatore decente ti avvertirà che ti manca un caso. Se questo viene utilizzato molto, potrebbe essere molto fastidioso .. Potrebbe essere meglio avere solo una variabile separata in questo caso specifico.
Fantastico Mr Fox

@FantasticMrFox Sono d'accordo al 100%, sulla base dell'esperienza. Anche questo avviso del compilatore è importante. Ho pubblicato un approccio alternativo, più in linea con lo spirito della tua raccomandazione.
arr_sea

28

Per C ++ 17 puoi usare magic_enum::enum_countda lib https://github.com/Neargye/magic_enum :

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

Dov'è l'inconveniente?

Questa libreria utilizza un hack specifico del compilatore (basato su __PRETTY_FUNCTION__/ __FUNCSIG__), che funziona su Clang> = 5, MSVC> = 15.3 e GCC> = 9.

Esaminiamo l'intervallo di intervallo dato e troviamo tutte le enumerazioni con un nome, questo sarà il loro conteggio. Ulteriori informazioni sulle limitazioni

Molte altre informazioni su questo hack in questo post https://taylorconor.com/blog/enum-reflection .


2
Questo e spettacolare! Non è necessario modificare il codice esistente per contare il numero di membri enum. Anche questo sembra implementato in modo molto elegante (appena sfogliato il codice)!
andreee

Le risposte solo link sono generalmente scoraggiate. Potresti ampliarlo con una descrizione della tecnica utilizzata dalla tua libreria?
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;

Questo è derivato dalla risposta di UglyCoder ma lo migliora in tre modi.

  • Non ci sono elementi aggiuntivi nell'enumerazione type_safe ( BEGINe SIZE) (anche la risposta di Cameron ha questo problema).
    • Il compilatore non si lamenterà del fatto che mancano da un'istruzione switch (un problema significativo)
    • Non possono essere passati inavvertitamente a funzioni che si aspettano il tuo enum. (non un problema comune)
  • Non richiede casting per l'uso. (Anche la risposta di Cameron ha questo problema.)
  • La sottrazione non interferisce con la dimensione del tipo di classe enum.

Conserva il vantaggio di UglyCoder rispetto alla risposta di Cameron che agli enumeratori possono essere assegnati valori arbitrari.

Un problema (condiviso con UglyCoder ma non con Cameron ) è che rende significative le nuove righe ei commenti ... il che è inaspettato. Quindi qualcuno potrebbe aggiungere una voce con spazi bianchi o un commento senza modificare TEST_SIZEil calcolo di.


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;

Intelligente! Ovviamente non possono contenere commenti o spaziature insolite e per file sorgente molto grandi, il tipo di valore sottostante potrebbe essere più grande di quanto sarebbe altrimenti.
Kyle Strand,

@ Kyle Strand: c'è questo problema: usando char e hai anche più di 256 enumeratori. Ma il compilatore ha le buone maniere di avvisarti di troncamenti ecc. LINE è un intero letterale e l'uso di #line ha un limite di [1, 2147483647]
UglyCoder

Ah ok. Tuttavia, anche una enumerazione che altrimenti sarebbe a shortpotrebbe essere aumentata, ad intesempio, quando si esegue una costruzione di unità. (Direi che questo è più un problema con le build di unità che con il trucco proposto, però.)
Kyle Strand

Trucco? Sorriso Lo uso, ma raramente e con il dovuto giudizio. Come ogni cosa nella codifica, dobbiamo aumentare i pro e i contro e soprattutto le implicazioni di manutenzione a lungo termine. L'ho usato di recente per creare una classe enum da un elenco di C #defines (OpenGL wglExt.h).
UglyCoder

5

C'è un trucco basato su X () - macros: image, hai il seguente enum:

enum MyEnum {BOX, RECT};

Riformattalo in:

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

Quindi il codice seguente definisce il tipo di enumerazione:

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

E il codice seguente calcola il numero di elementi enum:

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

Questo può essere reso più semplice rimuovendo la virgola da #define MyEnumDef(e inserendola #define X(val) val), che ti consente di contare il numero di elementi usando solo #define X(val) +1 constexpr std::size_t len = MyEnumDef;.
HolyBlackCat

4

Un trucco che puoi provare è aggiungere un valore enum alla fine del tuo elenco e usarlo come dimensione. Nel tuo esempio

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

1
Rispetto al comportamento con semplici enums, questo non funzionerà come ExampleCountè di tipo Example. Per ottenere il numero di elementi in Example, ExampleCountdovrebbe essere eseguito il cast su un tipo intero.
zuppa di mele

3

Se usi le utilità del preprocessore di boost, puoi ottenere il conteggio usando BOOST_PP_SEQ_SIZE(...).

Ad esempio, si potrebbe definire la CREATE_ENUMmacro come segue:

#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   

Quindi, chiamando la macro:

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

genererebbe il codice seguente:

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

Questo sta solo graffiando la superficie per quanto riguarda gli strumenti del preprocessore boost. Ad esempio, la tua macro potrebbe anche definire utilità di conversione da / a stringa e operatori ostream per l'enumerazione fortemente tipizzata.

Maggiori informazioni sugli strumenti del preprocessore boost qui: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html


Per inciso, sono assolutamente d'accordo con @FantasticMrFox sul fatto che il Countvalore enumerato aggiuntivo impiegato nella risposta accettata creerà mal di testa da parte del compilatore se si utilizza switchun'istruzione. Trovo che l' unhandled caseavviso del compilatore sia molto utile per una manutenzione del codice più sicura, quindi non vorrei minarlo.


@FantasticMrFox Grazie per aver segnalato un problema relativo alla risposta accettata. Ho fornito un approccio alternativo qui che è più in linea con lo spirito della tua raccomandazione.
arr_sea

2

Può essere risolto con un trucco 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__}); \
    }();                                                          \
};

Utilizzo:

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

2

C'è un altro modo che non si basa su conteggi di riga o modelli. L'unico requisito è inserire i valori enum nel proprio file e fare in modo che il preprocessore / compilatore faccia il conteggio in questo modo:

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

Ciò consente di inserire commenti con i valori di enumerazione, riassegnare i valori e non inietta un valore di enumerazione "conteggio" non valido che deve essere ignorato / considerato nel codice.

Se non ti interessano i commenti non hai bisogno di un file aggiuntivo e puoi fare come qualcuno sopra menzionato, ad esempio:

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

e sostituire le #include "my_enum_inc.h"direttive con MY_ENUM_LIST ma sarà necessario #undef ENUMVALdopo ogni utilizzo.


1

Un altro tipo di soluzione "stupida" a questo problema è:

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

Compilandolo con -Werror=switchti assicuri di ricevere un avviso del compilatore se ometti o duplichi un caso switch. È anche constexpr, quindi viene calcolato in fase di compilazione.

Ma nota che anche per a en enum classil valore inizializzato di default è 0 anche se il primo valore di enum non è 0. Quindi devi iniziare da 0 o usare esplicitamente il primo valore.


0

No, devi scriverlo nel codice.


0

Puoi anche considerare static_cast<int>(Example::E) + 1quale elimina l'elemento extra.


8
Questa risposta è corretta per questo specifico problema di programmazione, ma in generale è tutt'altro che elegante e soggetta a errori. L'enumerazione può essere estesa con nuovi valori in futuro che possono sostituire Example::Ecome ultimo valore nell'enumerazione. Anche se non è così, Example::Eil valore letterale di potrebbe cambiare.
Matthias

0

Reflection TS: riflessione statica di enumerazioni (e altri tipi)

Reflection TS , in particolare [reflection.ops.enum] / 2 dell'ultima versione della bozza di Reflection TS, offre l' get_enumerators TransformationTraitoperazione:

[Reflect.ops.enum] / 2

template <Enum T> struct get_enumerators

Tutte le specializzazioni di get_enumerators<T>devono soddisfare i TransformationTraitrequisiti (20.10.1). Il tipo annidato denominato typedesigna un tipo di meta oggetto soddisfacente ObjectSequence, contenente elementi che soddisfano Enumeratore riflettono gli enumeratori del tipo di enumerazione riflessi da T.

[Reflect.ops.objseq] della bozza copre le ObjectSequenceoperazioni, dove in particolare [Reflect.ops.objseq] / 1 copre il get_sizetratto per estrarre il numero di elementi per un meta oggetto soddisfacente ObjectSequence:

[Reflect.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

Tutte le specializzazioni di get_size<T>devono soddisfare i UnaryTypeTraitrequisiti (20.10.1) con una caratteristica di base di integral_constant<size_t, N>, dove Nè il numero di elementi nella sequenza di oggetti.

Pertanto, in Reflection TS doveva essere accettato e implementato nella sua forma attuale, il numero di elementi di un enum può essere calcolato, in fase di compilazione, come segue:

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

using ExampleEnumerators = get_enumerators<Example>::type;

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

dove è probabile che vedremo modelli di alias get_enumerators_ve get_type_vper semplificare ulteriormente la riflessione:

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

using ExampleEnumerators = get_enumerators_t<Example>;

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

Stato su Reflection TS

Come affermato dal rapporto di viaggio di Herb Sutter : Riunione estiva degli standard ISO C ++ (Rapperswil) dalla riunione estiva del comitato ISO C ++ del 9 giugno 2018, Reflection TS è stato dichiarato completo di funzionalità

Reflection TS è completo di funzionalità : Reflection TS è stato dichiarato completo di funzionalità e verrà inviato per il suo voto di commento principale durante l'estate. Notare ancora che l'attuale sintassi basata sulla metaprogrammazione del modello di TS è solo un segnaposto; il feedback richiesto è sul cuore del progetto, e il comitato sa già che intende sostituire la sintassi superficiale con un modello di programmazione più semplice che utilizza il codice ordinario in fase di compilazione e non la <>metaprogrammazione in stile.

ed era inizialmente progettato per C ++ 20 , ma non è chiaro se Reflection TS avrà ancora la possibilità di entrare nella versione C ++ 20.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.