#опытным
Много раз уже упоминали эту библиотечку в комментах, пора удостоить ее отдельного поста.
В чем проблема: хочется enum сериализовывать/десериализовывать в строку и из строки. То есть значение перечислителя заменить его строковым представлением в коде.
Такая задача требует рефлексии, которая в С++ только в 26 стандарте появится. Рефлексия - способность программы знать о том, как она написана на языке программирования. В скомпилированной программе перечислитель это всего лишь число, а чтобы получить имя, которое он имел в С++ коде, в программе должна эта информация где-то сохраниться.
Ну то есть, если у вас нет С++26, то нужно терпеть(но мы привыкли) и писать костыли. Что делается обычно:
enum class Color { RED, GREEN, BLUE };
std::unordered_map<Color, std::string> map = {{Color::RED, "RED"}, {Color::GREEN, "GREEN"}, {Color::BLUE, "BLUE"}};
std::cout << map[Color::RED] << std::endl;Для больших перечислений опупеете писать эти мапы, плюс код в принципе засоряется ими.
Но на любую проблему стандартных плюсов найдется своя библиотека, которая ее решит. В этом случае пригодится magic_enum.
Это хэдэр-онли С++17 библиотечка, которая предоставляет статическую рефлексию для перечислений и работает без макросни в интерфейсе и без бойлерплейта.
Простой пример:
Color color = Color::RED;
std::string color_name = magic_enum::enum_name(color);
assert(color_name == "RED);
Как это работает?
magic_enum рассчитан на работу с небольшими enum'ами. По дефолту интервал underlying значений от -128 до 127.
Если сильно упрощенно, то magic_enum пытается инстанцировать специальную функцию для каждого значения из этого интервала. Так как шаблонный параметр будет NTTP, его значение будет вшито в саму сигнатуру функции. А ее можно получить с помощью __PRETTY_FUNCTION__ или __FUNCSIG__:
template <auto V>
constexpr std::string_view get_enum_name() {
return extract_name(PRETTY_FUNCTION);
}
Только для реально существующих перечислителей __PRETTY_FUNCTION__ будет содержать корректное имя. Так и происходит маппинг значений перечислителей на их имена.
Вот примеры сигнатуры функции для существующего и несуществующего перечислителя:
enum class Color { RED, GREEN, BLUE };
constexpr Color c = static_cast<Color>(4);
get_enum_name<c>(); // constexpr void get_enum_name() [with auto V = (Color)4]
get_enum_name<Color::GREEN>(); // constexpr void get_enum_name() [with auto V = Color::GREEN]Чтобы это все реализовать и вместе собрать нужно еще много шаблонной магии, но суть такова.
magic_enum еще много чего умеет:
👉🏿 Обратный маппинг имен на значения перечисления
std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name);
if (color.has_value()) {
// color.value() -> Color::GREEN
}👉🏿 Доступ к элементам перечисления по индексу
std::size_t i = 0;
Color color = magic_enum::enum_value<Color>(i);
👉🏿 Можно получить количество элементов enum'а:
constexpr std::size_t color_count = magic_enum::enum_count<Color>();
👉🏿 Свои перегруженные io операторы:
using magic_enum::iostream_operators::operator<<; // out-of-the-box ostream operators for enums.
Color color = Color::BLUE;
std::cout << color << std::endl; // "BLUE"
и много чего еще.
Обзор фукнциональности можно найти в репе на гитхабе. Обратите особое внимание на ограничения библиотеки.
Use magic. Stay cool.
#tools #template