Я хотел бы создать макрос для определения перечисления, а также создать вспомогательную функцию, которая преобразует 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.
Обновлено: Макрос, который я опубликовал, является наиболее близким к тому, что я получил, нет необходимости опираться на него, если есть лучший способ сделать это.
Возможно, вам нужен третий аргумент базового макроса, например #define ENUM_ENTRY(EnumName, name, value), #define ENUM_CASE(EnumName, name, value)` и передать его соответствующим образом.
@ 463035818_is_not_an_ai это будет работать, но это встроенная среда, и это, вероятно, будет слишком много накладных расходов... Что-то вроде случая переключения и перечисления намного проще
@Roconx std::flat_map вдохновленная вещь, возможно, поможет?
@AlexeyS.Larionov Это сработает, но это совсем не чисто, поскольку #define VALUE_LIST(X) \ X(value1, 0) \ X(value2, 1) также понадобится третий аргумент. Если я что-то упускаю
@TedLyngmo Я считаю, что std::flat_map — это C++23 и выше, и я могу использовать только C++17. Кроме того, это встроенная среда, поэтому я не думаю, что это будет лучший вариант...
Я думаю, проблема в том, что EnumName расширяется до Value раньше, чем EMUM_CASE расширяется. Менее запутанное использование макросов X, вероятно, решило бы эту проблему. Например, вы можете включить имя перечисления как часть списка макросов X (третий элемент параметра).
@Лундин, я мог бы это сделать, но это было бы слишком много ненужных повторений и совсем не чисто.
Можете ли вы использовать магическое перечисление?
@Eljay Выглядит довольно интересно, я посмотрю!
Вместо макроса, почему бы просто не использовать Python или что-то еще для генерации соответствующих заголовков во время настройки/сборки? С макросами вообще не надо возиться.
Во время проверки кода вам посоветуют подумать о том, как сделать этот код более простым и читабельным. И перестаньте думать о том, как еще больше усложнить задачу — это уже настоящий беспорядок с X-макросами. Особенно для C++, где вы можете переложить часть этого на template — роскошь, которой не было бы в эквивалентном коде C. Также очень странно, что вспомогательная функция не включена в класс, что устраняет необходимость называть ее отдельно. Используйте объектно-ориентированный дизайн.
Также это хороший пример использования LUT или чего-то еще constexpr. Нет switch.
@Roconx Да, flat_map — это C++23, но создание контейнера, вдохновленного flat_map, не должно быть таким уж сложным — и я не думаю, что для него потребуется гораздо больше памяти, чем для ваших макросов.
@StephenNewell Интеграция Python в процесс сборки намного сложнее, чем написание макроса, IMO.
Преобразование int в name в перечислении в стиле C ненадежно по определению, поскольку несколько записей могут иметь одно и то же числовое значение. Перечисления — это не что иное, как инструмент, делающий код более читабельным. Они не должны быть частью программы. Я думаю, вы пытаетесь заставить перечисления делать то, чего они не должны делать. Если у вас есть опыт работы на других языках с первоклассными перечислениями, ваш опыт вводит вас в заблуждение. Enum в Java и enum в C++ похожи на английское «kinder» и немецкое «kinder» — одно и то же слово, но разное значение.
@Agent_L Функция преобразования должна принимать аргумент типа enum class, а не int, как сейчас.
Тип @TedLyngmo используется как общий параметр. Я говорю о падеже enum Foo { eeny=1, meeny=1, miny=1, mo=1}; - поэтому вопрос "что означает 1?" имеет несколько правильных ответов.
@Agent_L Да, 1 не имеет значения в enum class. Только имя :)





Простой способ исправить это — добавить 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
Это именно то, что мне нужно! Большое спасибо!
разве
std::unordered_map<int,std::string>не сделал бы то же самое (и даже больше) с гораздо меньшими усилиями?