Чи можна визначити кількість елементів класу переліку c ++?


85

Чи можна визначити потужність c ++ enum class:

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

Я намагався використовувати sizeof, однак, він повертає розмір елемента перерахування.

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

Чи існує стандартний спосіб отримати потужність (5 у моєму прикладі)?


Я думав, що міг існувати певний механізм c ++ 11
bquenin

6
До речі, це не дублікат. enumі enum classце дуже різні поняття.
Взуття

@ Шоу ... а вони насправді?
Kyle Strand

1
Це здається проблемою XY, я знаю, що це було давно, але чи пам’ятаєте ви, навіщо вам це потрібно було робити? Ви не можете перебирати enum classзначення, то якою була б користь від знання числа?
Фантастичний містер Фокс,

Відповіді:


71

Не безпосередньо, але ви можете скористатися наступним фокусом:

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' тут буде типу Приклад, а не int, правда? Вам доведеться спочатку передати "Count" в int, щоб використовувати його для розміру.
Man of One Way

@Man: Так, цей фокус трохи заплутаніший з enum classes замість простих enums. Я відредагую в акторському складі, щоб було зрозуміло.
Камерон

11
Якщо ви використовуєте оператор switch із цим переліченням, будь-який гідний компілятор попередить вас, що вам не вистачає одного випадку. Якщо це використовується багато, що може дуже дратувати .. Може бути краще просто мати відокремлену змінну в цьому конкретному випадку.
Фантастичний містер Фокс

@FantasticMrFox Я згоден на 100%, виходячи з досвіду. Це попередження компілятора також є важливим. Я опублікував альтернативний підхід, який більше відповідає духу вашої рекомендації.
arr_sea

26

Для C ++ 17 ви можете використовувати magic_enum::enum_countз lib https://github.com/Neargye/magic_enum :

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

Де недолік?

Ця бібліотека використовує специфічний для компілятора хак (на основі __PRETTY_FUNCTION__/ __FUNCSIG__), який працює на Clang> = 5, MSVC> = 15.3 та 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 (значна проблема)
    • Вони не можуть бути ненавмисно передані функціям, які очікують вашого переліку. (не поширена проблема)
  • Для використання не потрібно лиття. (Відповідь Камерона теж має цю проблему.)
  • Віднімання не змішується з розміром типу класу перечислення.

Він зберігає перевагу 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

@Kyle Strand: є така проблема: використовуючи char, у вас також є понад 256 перечислювачів. Але компілятор має хороші манери повідомляти вас про усічення тощо. LINE є цілочисельним літералом, а використання #line має обмеження [1, 2147483647]
UglyCoder

Гаразд Тим не менше, навіть перелік, який інакше був shortби знаком, міг би підштовхнутись, intнаприклад, під час побудови єдності. (Однак я б сказав, що це більше проблема з побудовою єдності, ніж із запропонованим трюком.)
Кайл Странд

Фокус? :-) Я користуюся ним, але рідко і з належним судженням. Як і все в кодуванні, нам потрібно знайти плюси і мінуси, і особливо довгострокові наслідки обслуговування. Нещодавно я використовував його для створення класу переліку зі списку C #defines (OpenGL wglExt.h).
UglyCoder

5

Існує одна хитрість, заснована на X () - макроси: 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), що дозволяє підрахувати кількість елементів, використовуючи just #define X(val) +1 constexpr std::size_t len = MyEnumDef;.
HolyBlackCat

4

Одним прийомом, який ви можете спробувати, є додавання значення перерахування в кінці вашого списку та використання його як розміру. У вашому прикладі

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

Порівняно з поведінкою з звичайними enums, це не буде працювати, як ExampleCountі тип 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

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

Це дозволяє розміщувати коментарі зі значеннями перерахування, повторно призначати значення і не вводить недійсне значення перерахування, яке потрібно ігнорувати / враховувати в коді.

Якщо ви не дбаєте про коментарі, вам не потрібен додатковий файл, і ви можете зробити те, що хтось згадав, наприклад:

#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 , зокрема [Reflect.ops.enum] / 2 останньої версії проекту Reflection TS пропонує get_enumerators TransformationTraitоперацію:

[Reflect.ops.enum] / 2

template <Enum T> struct get_enumerators

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

[Reflect.ops.objseq] проекту охоплює ObjectSequenceоперації, де зокрема [Reflect.ops.objseq] / 1 охоплює get_sizeознаку для вилучення кількості елементів для мета-об'єкта, що задовольняє ObjectSequence:

[Reflect.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, "");

Статус на рефлексії TS

Як зазначено у звіті поїздки Herb Sutter : Літня зустріч стандартів 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.