Как стать автором
Обновить

Комментарии 25

Как вариант
Я не хотел бы хвастаться, но, похоже, у меня наиболее полная реализация вывода enum'ов.
  1. реализованы не только «обычные» перечисления, но и битовые поля, и смешанные enum/bitfields
  2. реализованы операции +- и битовая арифметика для bitfields (важно для enum class'ов)
  3. поддерживается ручное присвоение числовых значений
  4. можно как объявлять enum, так и адаптировать уже существующий
  5. поддерживается enum внутри класса
  6. поддерживается до 256 значений
  7. есть манипуляторы enum_open, enum_close, bitfield_delim для управления открывающим тэгом, закрывающим тэгом и разделителем


Недостатки
  1. Зависит от 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]

Хорошо, что я пишу на C++/Qt и таких проблем нет — всё уже реализовано из коробки!

А как вы его декларируете без вложенности в структуру с макросом Q_GADGET

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" не может возвращать константное выражение в качестве результата

В классе Q_OBJECT, а не везде подряд!

"А как вы его декларируете без вложенности в структуру с макросом Q_GADGET"
О том и речь, что без Q_OBJECT или Q_GADGET и вложенности в класс ни как!

Както скучновато получилось. А чем это лучше std:map?
Поскольку значения известны на этапе компиляции, можно было сгенерировать шаблонами компилируемый поиск по префиксному дереву, например, в стиле Александреску. Макросы скорее для чистого Си.

MAX уберите, это антипаттерн т.к. рушит саму идею энамов — перечисление опций, плюс реализация не задалась:
std::cout << enum_to_string(LogLevel, LogLevel::MAX) << '\n'; // что-то выведет

кстати на практике писать придётся как-то так:
auto x = get_value();
enum_to_string(decltype(x), x);

и как то мне не нравится это повторение.
Хорошие новости что этого всего можно избежать при помощи С++11.

Есть такая прекрасная библиотека magic_enum. Работает отлично, не требует никаких дополнительных обвесов вокруг enum-ов.


https://github.com/Neargye/magic_enum

Еще бы объяснения того, как она работает. Откуда библиотека берет имена перечислений? Без поддержки от компилятора тут не обойтись. Суда по коду, делается какой-то грязный трюк с __PRETTY_FUNCTION__: https://github.com/Neargye/magic_enum/blob/59aa63ac647b9747443a7e9b688ad3dcfc687fcf/include/magic_enum.hpp#L299-L313


Автор хоть бы удосужился комментарий к функции написать об идеи решения! А если там баг найдут, а у автора не будет времени на его исправление? Не очень хочется погружаться во все эти дебри не понимая идеи.

Ну если открыть документацию на __PRETTY_FUNCTION__ то всё очевидно:
внутри 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;
}


Серьёзно? Конвертирование из строки в энум линейной сложности?
#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);
}
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации