Макрос C++ для определения перечислений и вспомогательной функции для преобразования из int в это перечисление

Я хотел бы создать макрос для определения перечисления, а также создать вспомогательную функцию, которая преобразует int в это перечисление и возвращает std::nullopt, если нет перечисления с переданным значением.

Я хотел бы иметь возможность создать такое перечисление:

SOME_MACRO(ENUM_NAME, 
    (foo, 1),
    (bar, 5), 
)

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

Вот самое близкое, что мне удалось получить:

#define DEFINE_ENUM_WITH_CONVERSION(EnumName, ENUM_LIST)                         \
    enum class EnumName {                                                        \
        ENUM_LIST(ENUM_ENTRY)                                                    \
    };                                                                           \
                                                                                 \
    std::optional<EnumName> intTo##EnumName(int value) {                         \
        switch (value) {                                                         \
            ENUM_LIST(ENUM_CASE)                                                 \
            default:                                                             \
                return std::nullopt;                                             \
        }                                                                        \
    }

// Helper macros for enum definition and switch cases
#define ENUM_ENTRY(name, value) name = value,
#define ENUM_CASE(name, value) case value: return EnumName::name;

// Define the list of enum values
#define VALUE_LIST(X)                                                        \
    X(value1, 0)                                                       \
    X(value2, 1)

// Correct usage of the macro to define the enum and the conversion function
DEFINE_ENUM_WITH_CONVERSION(Value, VALUE_LIST)

Но в #define ENUM_CASE(name, value) case value: return EnumName::name;EnumName настоящее имя не заменяется.

ПРИМЕЧАНИЕ. Я пробовал использовать ключевое слово using enum EnumName, но могу использовать только C++17, а не C++20.

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

разве std::unordered_map<int,std::string> не сделал бы то же самое (и даже больше) с гораздо меньшими усилиями?

463035818_is_not_an_ai 19.08.2024 15:00

Возможно, вам нужен третий аргумент базового макроса, например #define ENUM_ENTRY(EnumName, name, value), #define ENUM_CASE(EnumName, name, value)` и передать его соответствующим образом.

Alexey S. Larionov 19.08.2024 15:01

@ 463035818_is_not_an_ai это будет работать, но это встроенная среда, и это, вероятно, будет слишком много накладных расходов... Что-то вроде случая переключения и перечисления намного проще

Roconx 19.08.2024 15:05

@Roconx std::flat_map вдохновленная вещь, возможно, поможет?

Ted Lyngmo 19.08.2024 15:06

@AlexeyS.Larionov Это сработает, но это совсем не чисто, поскольку #define VALUE_LIST(X) \ X(value1, 0) \ X(value2, 1) также понадобится третий аргумент. Если я что-то упускаю

Roconx 19.08.2024 15:09

@TedLyngmo Я считаю, что std::flat_map — это C++23 и выше, и я могу использовать только C++17. Кроме того, это встроенная среда, поэтому я не думаю, что это будет лучший вариант...

Roconx 19.08.2024 15:13

Я думаю, проблема в том, что EnumName расширяется до Value раньше, чем EMUM_CASE расширяется. Менее запутанное использование макросов X, вероятно, решило бы эту проблему. Например, вы можете включить имя перечисления как часть списка макросов X (третий элемент параметра).

Lundin 19.08.2024 15:13

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

Roconx 19.08.2024 15:15

Можете ли вы использовать магическое перечисление?

Eljay 19.08.2024 15:16

@Eljay Выглядит довольно интересно, я посмотрю!

Roconx 19.08.2024 15:19

Вместо макроса, почему бы просто не использовать Python или что-то еще для генерации соответствующих заголовков во время настройки/сборки? С макросами вообще не надо возиться.

Stephen Newell 19.08.2024 15:19

Во время проверки кода вам посоветуют подумать о том, как сделать этот код более простым и читабельным. И перестаньте думать о том, как еще больше усложнить задачу — это уже настоящий беспорядок с X-макросами. Особенно для C++, где вы можете переложить часть этого на template — роскошь, которой не было бы в эквивалентном коде C. Также очень странно, что вспомогательная функция не включена в класс, что устраняет необходимость называть ее отдельно. Используйте объектно-ориентированный дизайн.

Lundin 19.08.2024 15:22

Также это хороший пример использования LUT или чего-то еще constexpr. Нет switch.

Lundin 19.08.2024 15:25

@Roconx Да, flat_map — это C++23, но создание контейнера, вдохновленного flat_map, не должно быть таким уж сложным — и я не думаю, что для него потребуется гораздо больше памяти, чем для ваших макросов.

Ted Lyngmo 19.08.2024 15:26

@StephenNewell Интеграция Python в процесс сборки намного сложнее, чем написание макроса, IMO.

HolyBlackCat 19.08.2024 15:29

Преобразование int в name в перечислении в стиле C ненадежно по определению, поскольку несколько записей могут иметь одно и то же числовое значение. Перечисления — это не что иное, как инструмент, делающий код более читабельным. Они не должны быть частью программы. Я думаю, вы пытаетесь заставить перечисления делать то, чего они не должны делать. Если у вас есть опыт работы на других языках с первоклассными перечислениями, ваш опыт вводит вас в заблуждение. Enum в Java и enum в C++ похожи на английское «kinder» и немецкое «kinder» — одно и то же слово, но разное значение.

Agent_L 19.08.2024 15:37

@Agent_L Функция преобразования должна принимать аргумент типа enum class, а не int, как сейчас.

Ted Lyngmo 19.08.2024 15:50

Тип @TedLyngmo используется как общий параметр. Я говорю о падеже enum Foo { eeny=1, meeny=1, miny=1, mo=1}; - поэтому вопрос "что означает 1?" имеет несколько правильных ответов.

Agent_L 19.08.2024 16:50

@Agent_L Да, 1 не имеет значения в enum class. Только имя :)

Ted Lyngmo 19.08.2024 16:51
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
19
72
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Простой способ исправить это — добавить using ThisEnum = EnumName; к intTo##EnumName() и использовать его в ENUM_CASE вместо EnumName.

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

DEFINE_ENUM_WITH_CONVERSION( Value,
    (value1, 0)
    (value2, 1)
)
#define DEFINE_ENUM_WITH_CONVERSION(EnumName, Elems) \
    enum class EnumName \
    { \
        END( ENUM_LIST_A Elems ) \
    }; \
    \
    std::optional<EnumName> intTo##EnumName(int value) \
    { \
        using ThisEnum = EnumName; \
        switch (value) \
        { \
            END( ENUM_CASE_A Elems ) \
            default: \
                return std::nullopt; \
        } \
    }

#define END(...) END_(__VA_ARGS__)
#define END_(...) __VA_ARGS__##_END

#define ENUM_LIST_A(name, ...) name = __VA_ARGS__, ENUM_LIST_B
#define ENUM_LIST_B(name, ...) name = __VA_ARGS__, ENUM_LIST_A
#define ENUM_LIST_A_END
#define ENUM_LIST_B_END

#define ENUM_CASE_A(name, ...) case __VA_ARGS__: return ThisEnum::name; ENUM_CASE_B
#define ENUM_CASE_B(name, ...) case __VA_ARGS__: return ThisEnum::name; ENUM_CASE_A
#define ENUM_CASE_A_END
#define ENUM_CASE_B_END

А для решения общей проблемы использования внешних параметров в цикле препроцессора (которой мы здесь избежали с помощью using), вы можете использовать что-то вроде этой моей библиотеки: https://github.com/HolyBlackCat/macro_sequence_for

Это именно то, что мне нужно! Большое спасибо!

Roconx 19.08.2024 16:22

Другие вопросы по теме