Как преобразовать enum в строку в современном C++ (C++17/C++20)
Лучший подход: макрос + шаблонный контейнер (C++17)
В современном C++ нет встроенной рефлексии для enum, но можно создать компактное решение, которое работает для любого перечисления без дублирования кода. Вот проверенный способ с использованием variadic макроса и constexpr-массива пар:
#include <iostream>
#include <string_view>
#include <array>
#include <algorithm>
// Вспомогательный макрос для генерации пар "значение-строка"
#define ENUM_STRING_PAIR(name) { name, #name }
// Основной макрос, создающий функцию toString
#define DEFINE_ENUM_TO_STRING(EnumType, ...) \
constexpr std::pair<EnumType, std::string_view> \
EnumType##Pairs[] = { __VA_ARGS__ }; \
\
inline std::string_view toString(EnumType value) { \
for (const auto& [key, str] : EnumType##Pairs) { \
if (key == value) return str; \
} \
return "Unknown"; \
}
// Использование:
enum class Color {
Red, Green, Blue, Yellow
};
DEFINE_ENUM_TO_STRING(Color,
ENUM_STRING_PAIR(Color::Red),
ENUM_STRING_PAIR(Color::Green),
ENUM_STRING_PAIR(Color::Blue),
ENUM_STRING_PAIR(Color::Yellow)
)
int main() {
Color c = Color::Green;
std::cout << toString(c) << '\n'; // Выведет "Green"
return 0;
}
Преимущества этого подхода:
- Минимум дублирования — каждое значение перечисления указывается только один раз (в макросе)
- Безопасность типов — функция toString принимает только правильный тип enum
- Производительность — поиск по маленькому массиву быстрее, чем switch-case
- Читаемость — макрос скрывает шаблонный код
Расширенная версия с обратным преобразованием (C++20)
Если нужно и строку обратно в enum, можно добавить вторую функцию:
#include <optional>
#define DEFINE_ENUM_BIDIRECTIONAL(EnumType, ...) \
DEFINE_ENUM_TO_STRING(EnumType, __VA_ARGS__) \
\
inline std::optional<EnumType> fromString( \
std::string_view name) { \
for (const auto& [key, str] : EnumType##Pairs) { \
if (str == name) return key; \
} \
return std::nullopt; \
}
// Использование:
auto opt = fromString<Color>("Green");
if (opt) {
// opt.value() == Color::Green
}
Важные замечания:
- Макрос
ENUM_STRING_PAIRиспользует оператор # для преобразования имени в строку — это стандартная фича препроцессора - Для C++17 обязательно использовать
std::string_viewдля избежания лишних аллокаций - Если enum содержит много значений (100+), лучше использовать
std::unordered_mapвместо линейного поиска - Это решение не поддерживает enum с явно заданными значениями (например,
Red = 5) — для таких случаев нужно модифицировать макрос
Этот подход используется в реальных проектах и считается "best practice" для C++17/20, когда внешние библиотеки недоступны.
<<Альтернатива: "магический" шаблон с auto и constexpr (C++17)
Если вы не любите макросы, можно использовать более шаблонный подход, но он требует больше кода для каждого enum:
template<typename E>
struct EnumTraits {
static constexpr std::array<std::pair<E, std::string_view>, 0> pairs{};
};
template<>
struct EnumTraits<Color> {
static constexpr std::array pairs{
std::pair{Color::Red, "Red"sv},
std::pair{Color::Green, "Green"sv},
std::pair{Color::Blue, "Blue"sv},
std::pair{Color::Yellow,"Yellow"sv}
};
};
template<typename E>
std::string_view toString(E value) {
for (const auto& [key, str] : EnumTraits<E>::pairs) {
if (key == value) return str;
}
return "Unknown";
}
Плюсы и минусы:
- + Нет макросов, чистый C++
- + Работает с любыми значениями enum, включая
Red = 10 - - Нужно писать специализацию для каждого enum — больше кода
- - Сложнее поддерживать при изменении enum
Этот вариант хорошо подходит, если у вас всего 2-3 enum и вы предпочитаете избегать препроцессора. Для больших проектов с десятками enum макрос из первого ответа удобнее.
Совет: комбинируйте оба подхода — используйте макрос для часто меняющихся enum и шаблон для стабильных.
Ответов пока нет.
Войдите чтобы оставить ответ