Можно ли определить количество элементов класса С++ 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
...
Ответ 7
Для С++ 17 вы можете использовать magic_enum::enum_count
из lib https://github.com/Neargye/magic_enum:
magic_enum::enum_count<Example>()
→ 4.
Ответ 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
весьма полезным для более безопасного обслуживания кода, поэтому я не хотел бы его подрывать.