Можно ли определить количество элементов класса С++ enum?

Можно ли определить мощность С++ enum class:

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

Я попытался использовать sizeof, однако он возвращает размер элемента перечисления.

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

Есть ли стандартный способ получить мощность (5 в моем примере)?

Ответы

Ответ 1

Не напрямую, но вы можете использовать следующий трюк:

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

Тогда мощность доступна как (int)Example::Count.

Конечно, это работает только хорошо, если вы позволяете автоматически присваивать значения перечисления, начиная с 0. Если это не так, вы можете вручную назначить правильную мощность Count, которая действительно ничем не отличается от необходимости поддерживать в любом случае, отдельная константа:

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

Единственным недостатком является то, что компилятор позволит вам использовать Example::Count в качестве аргумента для значения перечисления - поэтому будьте осторожны, если вы используете это! (Я лично считаю, что это не проблема на практике.)

Ответ 2

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;

Это получено из ответа UglyCoder, но улучшает его тремя способами.

  • Никаких дополнительных элементов в перечислении type_safe (BEGIN и SIZE) (ответ Cameron также возникает эта проблема.)
    • Компилятор не будет жаловаться на то, что они отсутствуют в инструкции switch (значительная проблема).
    • Они не могут быть непреднамеренно переданы функциям, ожидающим вашего перечисления. (не общая проблема)
  • Это не требует литья для использования. (ответ Cameron имеет и эту проблему.)
  • Вычитание не связано с размером класса класса перечисления.

Он сохраняет преимущество UglyCoder перед ответом Камерона, чтобы перечислениям были назначены произвольные значения.

Проблема (совместно с UglyCoder, но не с Cameron) заключается в том, что она делает новые строки и комментарии значимыми... что неожиданно. Поэтому кто-то может добавить запись с пробелом или комментарием без корректировки расчета TEST_SIZE.

Ответ 3

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;

Ответ 4

Один трюк, который вы можете попробовать, - добавить значение перечисления в конце вашего списка и использовать его как размер. В вашем примере

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

Ответ 5

Нет, вам нужно записать его в код.

Ответ 6

Существует один трюк, основанный на X() - макросах: изображение, у вас есть следующее перечисление:

enum MyEnum {BOX, RECT};

Отформатировать его до:

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

Затем следующий код определяет тип перечисления:

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

И следующий код вычисляет количество элементов перечисления:

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

Ответ 8

Вы также можете рассмотреть static_cast<int>(Example::E) + 1, который исключает дополнительный элемент.

Ответ 9

Если вы используете утилиты повышения препроцессора, вы можете получить счет с помощью BOOST_PP_SEQ_SIZE(...).

Например, можно определить макрос CREATE_ENUM следующим образом:

#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   

Затем вызываем макрос:

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

сгенерирует следующий код:

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

Это только царапает поверхность относительно инструментов повышения препроцессора. Например, ваш макрос может также определять утилиты преобразования строк и/и операторы ostream для строго перечисленных перечислений.

Подробнее об инструментах повышения препроцессора здесь: https://www.boost.org/doc/libs/1_70_0/libs/preprocessor/doc/AppendixA-AnIntroductiontoPreprocessorMetaprogramming.html


Кроме того, я полностью согласен с @FantasticMrFox, что дополнительное перечисляемое значение Count используемое в принятом ответе, создаст излишнюю головную боль при предупреждении компилятора при использовании оператора switch. Я нахожу unhandled case предупреждение компилятора unhandled case весьма полезным для более безопасного обслуживания кода, поэтому я не хотел бы его подрывать.