Ist es möglich, die Anzahl der Elemente einer C ++ - Enum-Klasse zu bestimmen?

84

Ist es möglich, die Kardinalität eines c ++ zu bestimmen enum class:

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

Ich habe versucht, es zu verwenden sizeof, es gibt jedoch die Größe eines Enum-Elements zurück.

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

Gibt es einen Standardweg, um die Kardinalität zu erhalten (5 in meinem Beispiel)?

Bquenin
quelle
Ich dachte, es könnte einen bestimmten c ++ 11-Mechanismus gegeben haben
bquenin
6
Dies ist übrigens kein Duplikat. enumund enum classes sind sehr unterschiedliche Konzepte.
Schuh
@ Schuh ... sind sie wirklich?
Kyle Strand
1
Dies scheint ein XY-Problem zu sein, ich weiß, es war vor langer Zeit, aber erinnerst du dich, warum du das tun musstest? Sie können nicht über enum classWerte iterieren. Was wäre also von Vorteil, wenn Sie die Zahl kennen würden?
Fantastischer Herr Fox

Antworten:

70

Nicht direkt, aber Sie könnten den folgenden Trick verwenden:

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

Dann steht die Kardinalität als zur Verfügung static_cast<int>(Example::Count).

Dies funktioniert natürlich nur dann gut, wenn Sie die Werte der Aufzählung ab 0 automatisch zuweisen lassen. Wenn dies nicht der Fall ist, können Sie Count manuell die richtige Kardinalität zuweisen, was sich nicht davon unterscheidet, dass Sie eine separate Konstante beibehalten müssen wie auch immer:

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

Der einzige Nachteil ist, dass der Compiler es Ihnen ermöglicht, Example::Countals Argument für einen Aufzählungswert zu verwenden - seien Sie also vorsichtig, wenn Sie diesen verwenden! (Ich persönlich finde dies jedoch in der Praxis kein Problem.)

Cameron
quelle
1
Die Enum-Werte sind in einer Enum-Klasse typsicher, daher ist 'Count' hier vom Typ Example und nicht int, oder? Sie müssten 'Count' zuerst in ein int umwandeln, um es für die Größe zu verwenden.
Mann der
@Man: Ja, dieser Trick ist etwas chaotischer mit enum classes anstatt mit einfachen enums. Ich werde in einer Besetzung bearbeiten, um klar zu sein.
Cameron
11
Wenn Sie eine switch-Anweisung mit dieser Aufzählung verwenden, werden Sie von jedem anständigen Compiler gewarnt, dass Ihnen ein Fall fehlt. Wenn dies häufig verwendet wird, kann dies sehr ärgerlich sein. In diesem speziellen Fall könnte es besser sein, nur eine separate Variable zu haben.
Fantastischer Herr Fox
@FantasticMrFox Ich stimme zu 100% zu, basierend auf Erfahrung. Diese Compiler-Warnung ist ebenfalls wichtig. Ich habe einen alternativen Ansatz veröffentlicht, der eher dem Geist Ihrer Empfehlung entspricht.
arr_sea
25

Für C ++ 17 können Sie magic_enum::enum_countvon lib https://github.com/Neargye/magic_enum verwenden :

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

Wo ist der Nachteil?

Diese Bibliothek verwendet einen compilerspezifischen Hack (basierend auf __PRETTY_FUNCTION__/ __FUNCSIG__), der mit Clang> = 5, MSVC> = 15.3 und GCC> = 9 funktioniert.

Wir gehen den angegebenen Intervallbereich durch und finden alle Aufzählungen mit einem Namen. Dies ist ihre Anzahl. Lesen Sie mehr über Einschränkungen

Viele weitere Informationen zu diesem Hack finden Sie in diesem Beitrag unter https://taylorconor.com/blog/enum-reflection .

Neargye
quelle
2
Das ist fantastisch! Sie müssen den vorhandenen Code nicht ändern, um die Anzahl der Enum-Mitglieder zu zählen. Auch dies scheint sehr elegant implementiert zu sein (nur durch den Code überflogen)!
andreee
Nur-Link-Antworten werden im Allgemeinen nicht empfohlen. Könnten Sie dies mit einer Beschreibung der Technik erweitern, die Ihre Bibliothek verwendet?
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;

Dies leitet sich aus der Antwort von UglyCoder ab , verbessert sie jedoch auf drei Arten.

  • Es gibt keine zusätzlichen Elemente in der Aufzählung type_safe ( BEGINund SIZE) ( Camerons Antwort hat auch dieses Problem.)
    • Der Compiler wird sich nicht darüber beschweren, dass sie in einer switch-Anweisung fehlen (ein erhebliches Problem).
    • Sie können nicht versehentlich an Funktionen übergeben werden, die Ihre Aufzählung erwarten. (kein häufiges Problem)
  • Für die Verwendung ist kein Gießen erforderlich. ( Camerons Antwort hat auch dieses Problem.)
  • Die Subtraktion wirkt sich nicht auf die Größe des Aufzählungstyps aus.

Der Vorteil von UglyCoder gegenüber Camerons Antwort, dass Enumeratoren beliebige Werte zugewiesen werden können, bleibt erhalten .

Ein Problem (gemeinsam mit UglyCoder, aber nicht mit Cameron ) besteht darin, dass dadurch Zeilenumbrüche und Kommentare erheblich werden ... was unerwartet ist. So könnte jemand einen Eintrag mit Leerzeichen oder einen Kommentar hinzufügen, ohne TEST_SIZEdie Berechnung anzupassen .

Gleichnamig
quelle
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
quelle
Klug! Natürlich kann es keine Kommentare oder ungewöhnlichen Abstände geben, und bei wirklich großen Quelldateien ist der zugrunde liegende Werttyp möglicherweise größer als sonst.
Kyle Strand
@ Kyle Strand: Es gibt dieses Problem: Wenn Sie char verwenden, haben Sie auch mehr als 256 Enumeratoren. Aber der Compiler hat die guten Manieren, um Sie über Kürzungen usw. zu informieren. LINE ist ein ganzzahliges Literal und die Verwendung von #line hat ein Limit von [1, 2147483647]
UglyCoder
Ah, okay. Trotzdem könnte sogar eine Aufzählung, die sonst eine shortwäre, intz. B. beim Erstellen eines Einheitsaufbaus erreicht werden. (Ich würde sagen, dies ist eher ein Problem mit Unity Builds als mit Ihrem vorgeschlagenen Trick.)
Kyle Strand
Trick? :-) Ich benutze es, aber selten und mit gebührendem Urteilsvermögen. Wie alles in der Codierung müssen wir die Vor- und Nachteile und insbesondere die langfristigen Auswirkungen auf die Wartung beseitigen. Ich habe es kürzlich verwendet, um eine Aufzählungsklasse aus einer Liste von C # -Definitionen (OpenGL wglExt.h) zu erstellen.
UglyCoder
5

Es gibt einen Trick, der auf X () basiert - Makros: Bild, Sie haben die folgende Aufzählung:

enum MyEnum {BOX, RECT};

Formatieren Sie es neu in:

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

Dann definiert der folgende Code den Aufzählungstyp:

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

Der folgende Code berechnet die Anzahl der Enum-Elemente:

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
quelle
Dies kann erleichtert werden, indem Sie das Komma aus entfernen #define MyEnumDef(und einfügen #define X(val) val), wodurch Sie die Anzahl der Elemente mit just zählen können #define X(val) +1 constexpr std::size_t len = MyEnumDef;.
HolyBlackCat
4

Ein Trick, den Sie versuchen können, besteht darin, am Ende Ihrer Liste einen Aufzählungswert hinzuzufügen und diesen als Größe zu verwenden. In deinem Beispiel

enum class Example { A, B, C, D, E, ExampleCount };
David Nehme
quelle
Im Vergleich zum Verhalten mit einfachen enums funktioniert dies nicht wie es ExampleCountist Example. Um die Anzahl der Elemente in zu erhalten Example, ExampleCountmüsste in einen ganzzahligen Typ umgewandelt werden.
Apfelmus
3

Wenn Sie die Präprozessor-Dienstprogramme von boost verwenden, können Sie die Anzahl mithilfe von ermitteln BOOST_PP_SEQ_SIZE(...).

Zum Beispiel könnte man das CREATE_ENUMMakro wie folgt definieren:

#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   

Rufen Sie dann das Makro auf:

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

würde den folgenden Code erzeugen:

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

Dies kratzt nur die Oberfläche in Bezug auf die Boost-Präprozessor-Werkzeuge. Beispielsweise könnte Ihr Makro auch Dienstprogramme zur Zeichenfolgenkonvertierung und Ostream-Operatoren für Ihre stark typisierte Aufzählung definieren.

Weitere Informationen zu Boost-Präprozessor-Tools finden Sie hier: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html


Abgesehen davon stimme ich @FantasticMrFox nachdrücklich zu, dass der zusätzliche CountAufzählungswert, der in der akzeptierten Antwort verwendet wird, bei Verwendung einer switchAnweisung unzählige Compiler- Warnprobleme verursacht . Ich finde die unhandled caseCompiler-Warnung sehr nützlich für eine sicherere Code-Wartung, daher möchte ich sie nicht untergraben.

arr_sea
quelle
@FantasticMrFox Vielen Dank, dass Sie auf ein Problem mit der akzeptierten Antwort hingewiesen haben. Ich habe hier einen alternativen Ansatz angegeben, der eher dem Geist Ihrer Empfehlung entspricht.
arr_sea
2

Es kann durch einen Trick mit std :: initializer_list gelöst werden:

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

Verwendung:

#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
quelle
2

Es gibt einen anderen Weg, der nicht auf Zeilenanzahl oder Vorlagen beruht. Die einzige Voraussetzung ist, die Enum-Werte in ihre eigene Datei zu stecken und den Präprozessor / Compiler dazu zu bringen, die Zählung wie folgt durchzuführen:

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

Auf diese Weise können Sie Kommentare mit den Aufzählungswerten einfügen, Werte neu zuweisen und keinen ungültigen Aufzählungswert "count" einfügen, der im Code ignoriert / berücksichtigt werden muss.

Wenn Sie sich nicht für Kommentare interessieren, benötigen Sie keine zusätzliche Datei und können dies wie oben beschrieben tun, z.

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

und ersetzen Sie die #include "my_enum_inc.h"Anweisungen durch MY_ENUM_LIST, aber Sie müssen #undef ENUMVALnach jeder Verwendung.

MetalTurnip
quelle
1

Eine andere Art von "dummer" Lösung dafür ist:

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

Wenn Sie dies mit kompilieren -Werror=switch, stellen Sie sicher, dass Sie eine Compiler-Warnung erhalten, wenn Sie einen Switch-Fall weglassen oder duplizieren. Es ist auch constexpr, so dass dies zur Kompilierungszeit berechnet wird.

Beachten Sie jedoch, dass selbst für ein en enum classder initialisierte Standardwert 0 ist, auch wenn der erste Wert der Aufzählung nicht 0 ist. Sie müssen also entweder mit 0 beginnen oder den ersten Wert explizit verwenden.

Zitrax
quelle
0

Nein, du musst es in den Code schreiben.


quelle
0

Sie können auch überlegen, static_cast<int>(Example::E) + 1welches das zusätzliche Element eliminiert.

Fabio A. Correa
quelle
8
Diese Antwort ist für dieses spezielle Programmierproblem richtig, aber im Allgemeinen alles andere als elegant und fehleranfällig. Die Aufzählung kann in Zukunft um neue Werte erweitert werden, die Example::Eals letzter Wert in der Aufzählung ersetzt werden können. Auch wenn dies nicht der Fall ist, Example::Ekann sich der Literalwert ändern.
Matthias
0

Reflexion TS: statische Reflexion von Aufzählungen (und anderen Typen)

Reflection TS , insbesondere [Reflect.ops.enum] / 2 der neuesten Version des Reflection TS-Entwurfs, bietet folgende Möglichkeiten get_enumerators TransformationTrait:

[Reflect.ops.enum] / 2

template <Enum T> struct get_enumerators

Alle Spezialisierungen von get_enumerators<T>müssen den TransformationTraitAnforderungen (20.10.1) entsprechen. Der verschachtelte Typ mit dem Namen typebezeichnet einen befriedigenden Metaobjekttyp ObjectSequence, der Elemente enthält, die Enumeratordie Aufzähler des Aufzählungstyps erfüllen und widerspiegeln T.

[Reflect.ops.objseq] des Entwurfs behandelt ObjectSequenceOperationen, wobei insbesondere [Reflect.ops.objseq] / 1 das get_sizeMerkmal zum Extrahieren der Anzahl von Elementen für ein Metaobjekt abdeckt , das Folgendes erfüllt ObjectSequence:

[Reflect.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

Alle Spezialisierungen von get_size<T>müssen die UnaryTypeTraitAnforderungen (20.10.1) mit einer Basiseigenschaft erfüllen integral_constant<size_t, N>, wobei Ndie Anzahl der Elemente in der Objektsequenz ist.

Wenn also in Reflection TS akzeptiert und in seiner aktuellen Form implementiert werden soll, kann die Anzahl der Elemente einer Aufzählung zur Kompilierungszeit wie folgt berechnet werden:

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

using ExampleEnumerators = get_enumerators<Example>::type;

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

wo wir wahrscheinlich Alias-Vorlagen sehen get_enumerators_vund get_type_vdie Reflexion weiter vereinfachen:

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

using ExampleEnumerators = get_enumerators_t<Example>;

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

Status bei Reflexion TS

Wie im Reisebericht von Herb Sutter angegeben : Sommer-ISO-C ++ - Standardtreffen (Rapperswil) vom Sommertreffen des ISO-C ++ - Komitees am 9. Juni 2018 wurde Reflection TS als vollständig erklärt

Reflection TS ist vollständig : Der Reflection TS wurde für vollständig erklärt und wird im Sommer für seine Hauptkommentarabstimmung verschickt. Beachten Sie erneut, dass die aktuelle Metaprogrammierungs-basierte Syntax des TS nur ein Platzhalter ist. Das angeforderte Feedback bezieht sich auf den Kern des Entwurfs, und das Komitee weiß bereits, dass es beabsichtigt, die Oberflächensyntax durch ein einfacheres Programmiermodell zu ersetzen, das gewöhnlichen Code zur Kompilierungszeit und keine <>Metaprogrammierung im Stil verwendet.

und war ursprünglich für C ++ 20 geplant , aber es ist etwas unklar, ob Reflection TS noch die Chance hat, es in die C ++ 20-Version zu schaffen.

dfrib
quelle