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


86

Можно ли определить количество элементов c ++ enum class:

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

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

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

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


Я подумал, что мог быть конкретный механизм С ++ 11
bquenin

6
Кстати, это не дубликат. enumи enum classes - это очень разные концепции.
Shoe

@ Обувь ... правда ли?
Кайл Стрэнд

1
Это похоже на проблему XY, я знаю, что это было давно, но вы помните, зачем вам это было нужно? Вы не можете перебирать enum classзначения, так какая польза от знания числа?
Фантастический мистер Фокс

Ответы:


73

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

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

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

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

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

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


1
Значения перечисления являются типобезопасными в классе перечисления, поэтому здесь «Count» будет иметь тип Example, а не int, верно? Вам нужно сначала преобразовать Count в int, чтобы использовать его для размера.
Man of One Way

@Man: Да, этот трюк немного сложнее с enum classes, а не с простым enums. Я отредактирую гипс, чтобы было понятно.
Кэмерон

11
Если вы используете оператор switch с этим перечислением, любой достойный компилятор предупредит вас, что вам не хватает одного случая. Если это часто используется, это может быть очень раздражающим ... Может быть лучше просто иметь отдельную переменную в этом конкретном случае.
Фантастический мистер Фокс

@FantasticMrFox Согласен на 100%, исходя из опыта. Это предупреждение компилятора тоже важно. Я опубликовал альтернативный подход, более соответствующий духу вашей рекомендации.
arr_sea 01

28

Для С ++ 17 вы можете использовать magic_enum::enum_countиз библиотеки https://github.com/Neargye/magic_enum :

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

Где недостаток?

Эта библиотека использует специфичный для компилятора хак (на основе __PRETTY_FUNCTION__/__FUNCSIG__), which works on Clang >= 5, MSVC >= 15.3 and GCC >= 9.

Мы проходим заданный диапазон интервалов и находим все перечисления с именем, это будет их количество. Узнать больше об ограничениях

Подробнее об этом взломе читайте в этом посте https://taylorconor.com/blog/enum-reflection .


2
Это круто! Нет необходимости изменять существующий код для подсчета количества членов перечисления. Также это кажется очень элегантно реализованным (просто бегло просмотрел код)!
andreee

Ответы только по ссылкам обычно не приветствуются. Не могли бы вы дополнить это описанием техники, которую использует ваша библиотека?
Адриан Маккарти

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;

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

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

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

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


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;

Умная! Конечно, не может быть никаких комментариев или необычных интервалов, а для действительно больших исходных файлов базовый тип значения может быть больше, чем в противном случае.
Кайл Стрэнд

@Kyle Strand: вот в чем проблема: при использовании char у вас тоже более 256 счетчиков. Но у компилятора есть хорошие манеры, чтобы уведомлять вас об усечениях и т. Д. LINE - это целочисленный литерал, а использование #line имеет предел [1, 2147483647]
UglyCoder 06

Ах хорошо. Тем не менее, даже перечисление, которое иначе было бы, shortможет быть увеличено, intнапример, при сборке единства. (Я бы сказал, что это больше проблема с сборками единства, чем с предложенным вами трюком.)
Кайл Стрэнд

Уловка? :-) Использую, но редко и с осмотрительностью. Как и все в кодировании, нам нужно поднимать все за и против, и особенно на долгосрочное обслуживание. Недавно я использовал его для создания класса перечисления из списка определений C # (OpenGL wglExt.h).
UglyCoder 08

5

Есть одна хитрость, основанная на X () - macros: image, у вас есть следующее перечисление:

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

Это можно упростить, удалив запятую #define MyEnumDef(и вставив ее #define X(val) val), что позволяет подсчитывать количество элементов, используя только #define X(val) +1 constexpr std::size_t len = MyEnumDef;.
HolyBlackCat

4

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

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

1
По сравнению с поведением с обычным enums, это не будет работать, как ExampleCountis type Example. Чтобы получить количество элементов в Example, ExampleCountнеобходимо привести к целочисленному типу.
яблочный суп

3

Если вы используете утилиты препроцессора boost, вы можете получить счет, используя 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 для строго типизированного перечисления.

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


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


@FantasticMrFox Спасибо за указание на проблему с принятым ответом. Здесь я предложил альтернативный подход, который больше соответствует духу вашей рекомендации.
arr_sea 01

2

Это можно решить с помощью трюка с 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__}); \
    }();                                                          \
};

Применение:

#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

Есть еще один способ, который не зависит от количества строк или шаблонов. Единственное требование - вставить значения перечисления в их собственный файл и заставить препроцессор / компилятор выполнять подсчет следующим образом:

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

Это позволяет вам помещать комментарии со значениями перечисления, повторно назначать значения и не вводить недопустимое значение перечисления count, которое необходимо игнорировать / учитывать в коде.

Если вас не интересуют комментарии, вам не нужен дополнительный файл, и вы можете действовать так, как указано выше, например:

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

и замените #include "my_enum_inc.h"директивы на MY_ENUM_LIST, но это потребуется #undef ENUMVALпосле каждого использования.


1

Другой вид "глупого" решения этой проблемы:

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

Компилируя это с -Werror=switch вы убедитесь, что получите предупреждение компилятора, если вы пропустите или дублируете любой случай переключения. Это также constexpr, поэтому он вычисляется во время компиляции.

Но обратите внимание, что даже для en enum classинициализированное значение по умолчанию равно 0, даже если первое значение перечисления не равно 0. Таким образом, вы должны либо начать с 0, либо явно использовать первое значение.


0

Нет, вы должны написать это в коде.


0

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


8
Этот ответ верен для данной конкретной проблемы программирования, но в целом далеко не изящен и не подвержен ошибкам. В будущем перечисление может быть расширено новыми значениями, которые можно заменить Example::Eпоследним значением в перечислении. Даже если это не так, Example::Eбуквальное значение может измениться.
Матиас

0

Reflection TS: статическое отражение перечислений (и других типов)

Reflection TS , в частности [Reflection.ops.enum] / 2 последней версии проекта Reflection TS, предлагает следующие get_enumerators TransformationTraitоперации:

[think.ops.enum] / 2

template <Enum T> struct get_enumerators

Все специализации get_enumerators<T>должны соответствовать TransformationTraitтребованиям (20.10.1). Вложенный тип с именем typeобозначает удовлетворяющий тип мета-объекта ObjectSequence, содержащий элементы, которые удовлетворяют Enumeratorи отражают перечислители типа перечисления, отраженного T.

[Reflect.ops.objseq] черновика охватывает ObjectSequenceоперации, где, в частности, [detect.ops.objseq] / 1 охватывает типаж get_sizeдля извлечения количества элементов для мета-объекта, удовлетворяющего ObjectSequence:

[reflection.ops.objseq] / 1

template <ObjectSequence T> struct get_size;

Все специализации get_size<T>должны соответствовать UnaryTypeTraitтребованиям (20.10.1) с базовой характеристикой integral_constant<size_t, N>, где N- количество элементов в последовательности объектов.

Таким образом, в Reflection TS должны были быть приняты и реализованы в его текущей форме, количество элементов перечисления может быть вычислено во время компиляции следующим образом:

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

using ExampleEnumerators = get_enumerators<Example>::type;

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

где мы, вероятно, увидим шаблоны псевдонимов get_enumerators_vи еще get_type_vбольше упростим отражение:

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

using ExampleEnumerators = get_enumerators_t<Example>;

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

Статус на Reflection TS

Как указано в отчете о поездке Херба Саттера : Летнее собрание стандартов ISO C ++ (Rapperswil) на летнем заседании комитета ISO C ++ 9 июня 2018 г., Reflection TS был объявлен полностью функциональным.

Reflection TS является полнофункциональным : Reflection TS был объявлен полнофункциональным и разослан для голосования по основным комментариям в течение лета. Заметим еще раз, что текущий синтаксис, основанный на метапрограммировании шаблона TS, является просто заполнителем; запрашиваемая обратная связь находится в основе проекта, и комитет уже знает, что намеревается заменить поверхностный синтаксис более простой моделью программирования, которая использует обычный код времени компиляции, а не <>метапрограммирование в нестандартном стиле.

и изначально планировался для C ++ 20 , но несколько неясно, будет ли у Reflection TS еще шанс попасть в выпуск C ++ 20.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.