web-dev-qa-db-fra.com

Est-il possible de déterminer le nombre d'éléments d'une classe d'énumération c ++?

Est-il possible de déterminer la cardinalité d'un c ++ enum class:

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

J'ai essayé d'utiliser sizeof, cependant, il renvoie la taille d'un élément enum.

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

Existe-t-il un moyen standard d'obtenir la cardinalité (5 dans mon exemple)?

59
bquenin

Pas directement, mais vous pouvez utiliser l'astuce suivante:

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

La cardinalité est alors disponible sous la forme (int)Example::Count.

Bien sûr, cela ne fonctionne bien que si vous laissez les valeurs de l'énumération attribuées automatiquement, à partir de 0. Si ce n'est pas le cas, vous pouvez attribuer manuellement la cardinalité correcte à Count, ce qui n'est vraiment pas différent d'avoir à maintenir une constante distincte en tous cas:

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

Le seul inconvénient est que le compilateur vous permettra d'utiliser Example::Count comme argument pour une valeur enum - soyez donc prudent si vous l'utilisez! (Je trouve personnellement que ce n'est pas un problème dans la pratique, cependant.)

54
Cameron
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;

Ceci est dérivé de réponse d'UglyCoder mais l'améliore de trois manières.

  • Il n'y a aucun élément supplémentaire dans l'énumération type_safe (BEGIN et SIZE) ( la réponse de Cameron a également ce problème.)
    • Le compilateur ne se plaindra pas de leur absence dans une instruction switch (un problème important)
    • Ils ne peuvent pas être transmis par inadvertance à des fonctions qui attendent votre énumération. (pas un problème courant)
  • Il ne nécessite pas de moulage pour être utilisé. ( réponse de Cameron a aussi ce problème.)
  • La soustraction ne perturbe pas la taille du type de classe enum.

Il conserve glyCoder avantage sur réponse de Cameron que les énumérateurs peuvent se voir attribuer des valeurs arbitraires.

Un problème (partagé avec glyCoder mais pas avec Cameron ) est qu'il rend les nouvelles lignes et les commentaires significatifs ... ce qui est inattendu. Ainsi, quelqu'un pourrait ajouter une entrée avec un espace ou un commentaire sans ajuster le calcul de TEST_SIZE.

16
Eponymous

Pour C++ 17, vous pouvez utiliser magic_enum::enum_count De lib https://github.com/Neargye/magic_enum :

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

7
Neargye
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;
7
UglyCoder

Une astuce que vous pouvez essayer est d'ajouter une valeur d'énumération à la fin de votre liste et de l'utiliser comme taille. Dans votre exemple

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

Non, vous devez l'écrire dans le code.

2
user1944441

Il existe une astuce basée sur X () - macros: image, vous avez l'énumération suivante:

enum MyEnum {BOX, RECT};

Reformatez-le pour:

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

Ensuite, le code suivant définit le type d'énumération:

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

Et le code suivant calcule le nombre d'éléments 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
...
2
Kirill Suetnov

Vous pouvez également considérer static_cast<int>(Example::E) + 1 qui élimine l'élément supplémentaire.

1
Fabio A. Correa

Si vous utilisez les utilitaires de préprocesseur de boost, vous pouvez obtenir le nombre en utilisant BOOST_PP_SEQ_SIZE(...).

Par exemple, on pourrait définir le CREATE_ENUM macro comme suit:

#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   

Ensuite, appelez la macro:

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

générerait le code suivant:

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

Cela ne fait qu'effleurer la surface en ce qui concerne les outils de préprocesseur de boost. Par exemple, votre macro peut également définir des utilitaires de conversion de/vers et des opérateurs ostream pour votre énumération fortement typée.

Plus d'informations sur les outils de préprocesseur boost ici: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html


En passant, je suis fortement d'accord avec @FantasticMrFox que la valeur énumérée Count supplémentaire utilisée dans la réponse acceptée créera de nombreux maux de tête d'avertissement du compilateur si vous utilisez un switch déclaration. Je trouve le unhandled case avertissement du compilateur assez utile pour une maintenance plus sûre du code, donc je ne voudrais pas le miner.

0
arr_sea