Комментарии 25
- реализованы не только «обычные» перечисления, но и битовые поля, и смешанные enum/bitfields
- реализованы операции +- и битовая арифметика для bitfields (важно для enum class'ов)
- поддерживается ручное присвоение числовых значений
- можно как объявлять enum, так и адаптировать уже существующий
- поддерживается enum внутри класса
- поддерживается до 256 значений
- есть манипуляторы enum_open, enum_close, bitfield_delim для управления открывающим тэгом, закрывающим тэгом и разделителем
Недостатки
- Зависит от boost.preprocessor
Синтаксис примерно похожий:
DECLARE_ENUM
(
my_enum,
(ea) // 0
(eb, 10) // 10
(ec) // 11
);
std::cout << ea << "\n"; // [ea]
// or
DECLARE_BITFIELD
(
my_bitfield,
(ba, 1)
(bb, 2)
(bc, 4)
);
std::cout << (ba | bc) << "\n"; // [ba, bc]
Как-то кринжово DECLARE_ENUM
объявление энума выглядит.
Запилил своё https://godbolt.org/z/szhzoM8WE. Правда нужен 20ый стандарт.
QS_ENUM(LogLevel, // enum class LogLevel
Alert = -10, // LogLevel::Alert
Critical, // LogLevel::Critical
Error, // LogLevel::Error
Warning, // LogLevel::Warning
Notice = 10, // LogLevel::Notice
Info, // LogLevel::Info
Debug // LogLevel::Debug
);
QS_ENUM(Test, // enum class Test
Test0, // Test::Test0
Test1, // Test::Test1
Test2 = -3, // Test::Test2
Test3, // Test::Test3
);
> Как-то кринжово DECLARE_ENUM
объявление энума выглядит.
Как вы думаете, почему
BOOST_FUSION_DEFINE_STRUCT_INLINE( struct_name, (member_type0, member_name0) (member_type1, member_name1) ... )
выглядит так? Или, в терминах boost.preprocessor используется sequence.
А по поводу вашей реализации, поддерживаются ли битовые поля? В принципе, идея у всех аналогичная. Вопрос в реализации.
Поддерживаются, и объявление выглядит более привычно.
Надо one | four
? Я то подумал про объявление с присвоением, не подумал про std::cout << (ba | bc) << "\n"; // [ba, bc]
https://godbolt.org/z/a3nY3b8xE Правда в рантайме текст генерит.
Хорошо, что я пишу на C++/Qt и таких проблем нет — всё уже реализовано из коробки!
А как вы его декларируете без вложенности в структуру с макросом Q_GADGET
В смысле как? Использую Q_ENUM
.
enum qwe {
a,
b,
c,
d,
e,
f
};
Q_ENUM(qwe);
C:\...\QtPro\mainwindow.cpp(17): error C2255: friend: не допускается вне определения класса
C:\...\QtPro\mainwindow.cpp(17): error C2065: staticMetaObject: необъявленный идентификатор
C:\...\QtPro\mainwindow.cpp(17): error C3615: Функция constexpr "qt_getEnumMetaObject" не может возвращать константное выражение в качестве результата
Както скучновато получилось. А чем это лучше std:map?
Поскольку значения известны на этапе компиляции, можно было сгенерировать шаблонами компилируемый поиск по префиксному дереву, например, в стиле Александреску. Макросы скорее для чистого Си.
std::cout << enum_to_string(LogLevel, LogLevel::MAX) << '\n'; // что-то выведет
кстати на практике писать придётся как-то так:
auto x = get_value();
enum_to_string(decltype(x), x);
и как то мне не нравится это повторение.
Хорошие новости что этого всего можно избежать при помощи С++11.
Есть такая прекрасная библиотека magic_enum. Работает отлично, не требует никаких дополнительных обвесов вокруг enum-ов.
Еще бы объяснения того, как она работает. Откуда библиотека берет имена перечислений? Без поддержки от компилятора тут не обойтись. Суда по коду, делается какой-то грязный трюк с __PRETTY_FUNCTION__
: https://github.com/Neargye/magic_enum/blob/59aa63ac647b9747443a7e9b688ad3dcfc687fcf/include/magic_enum.hpp#L299-L313
Автор хоть бы удосужился комментарий к функции написать об идеи решения! А если там баг найдут, а у автора не будет времени на его исправление? Не очень хочется погружаться во все эти дебри не понимая идеи.
внутри n() __PRETTY_FUNCTION__ развернётся в «return_type n()» и т.к. тип фиксирован то можно использовать константные смещения для вычленения желаемой EnumValue из этой строки.
Минус подхода в том, что он не знает как enum объявлен, поэтому например для полного списка значений он перебирает значения в задаваемом диапазоне и пытается сконвертировать их в желаемый enum.
Ну, документация GCC лишь говорит, что она есть и что-то красивое там будет. Другой документации и для clang тоже, что то не находится.
Я на само деле уже покопался в исходниках. Идея метода такая:
заводим пару шаблонных функций, вычисляемых во время компиляции:
/// E -- тип перечисления template<typename E> constexpr const char* enum_name() { ... } /// E -- тип перечисления /// value -- одно конкретное значение этого типа template<typename E, E value> constexpr const char* enum_value_name() { ... }
Внутри функций используем
__PRETTY_FUNCTION__
. Благодаря шаблону имя нашего перечисления или значения станет частью имени функции
Вычленяем из имени функции интересующие нас части
Вообщем, очень остроумное решение, респект автору. Жалко, что оно немножко нестабильное. Так, не факт, что value
в enum_value_name
будет именем константы, а не (E)число
, как делает clang в режиме -std=c++14 (возможно из-за этого ограничение на -std=c++17).
Кроме того, есть еще вопрос, как из имени перечисления получить список список всех его допустимых значений. Если я правильно понял, это делается перебором всех числовых значений в некотором диапазоне (по умолчанию [-128; 128]
), трактованием его как элемента перечисления и попыткой взять имя этого элемента. Если имя есть, то это элемент перечисления, а если нет — то просто число
Надеялся увидеть решение на основе парсера clang
static int string_to_enum_int(const char* const tokens[], int max, const char* value) { for (int i = 0; i < max; ++i) { if (string_equals(tokens[i], value)) { return i; } } return max; }
Серьёзно? Конвертирование из строки в энум линейной сложности?
Кто сказал линейной? Можно и в компайлтайм упороться https://godbolt.org/z/7q191q36d
#include <algorithm>
#include <array>
#include <ranges>
#include <string_view>
#define QS_ENUM(T, ...) \
enum class T { __VA_ARGS__ }; \
template <> \
inline constexpr auto Impl::Max<T> = Impl::enum_size(#__VA_ARGS__); \
template <> \
inline constexpr auto Impl::Tokens<T> = \
Impl::tokenize_enum<Impl::Max<T>>(#__VA_ARGS__);
namespace Impl {
template <class Ty>
inline constexpr Ty Max = Ty{};
template <class Ty>
inline constexpr Ty Tokens = Ty{};
consteval size_t enum_size(std::string_view sv) {
return std::ranges::count(sv, ',') + !sv.ends_with(',');
}
template <size_t N>
consteval std::array<std::string_view, N> tokenize_enum(std::string_view base) {
size_t count{};
std::array<std::string_view, N> tokens;
for (const auto word : std::views::split(base, std::string_view{", "}))
tokens[count++] = {word.begin(), *(word.end() - 1) == ','
? word.end() - 1
: word.end()};
return tokens;
}
} // namespace Impl
template <class E>
constexpr auto enumToString(E e) {
return Impl::Tokens<E>[static_cast<std::underlying_type_t<E>>(e)];
}
template <class E>
constexpr E stringToEnum(auto value) {
for (int i = 0; i < Impl::Max<E>; ++i)
if (Impl::Tokens<E>[i] == value) return static_cast<E>(i);
return static_cast<E>(-1);
}
C++ enum <-> string? Легко