Вот, скажем, один из самых популярных примеров. Можно сказать, классических. Сериализуются данные в, скажем, json. В структуре есть enum-поле, которое хочется сохранять в текстовом виде (а не числом). Всё. Стоп. Простого способа решить эту элементарную задачу на C++ не существует. (c)
... Но очень хочется.
За последний год я видел, как чуть ли не в каждом проекте разработчик предлагал своё видение этой проблемы. И везде было дублирование кода, везде какие-то костыли, и "тонкости". Да что уж там, мне самому приходится время от времени возвращаться к этой теме. Хватит. Решил раз и навсегда закрыть вопрос, по крайней мере для себя.
Код далёк от совершенства (надеюсь,анонимуспоправит), но свою задачу выполняет. Может кому ипригодится:
Реализация
// enum_string.h#pragma once#define DECLARE_ENUM(T, values...) \ enum class T { values, MAX }; \ char enum_##T##_base[sizeof(#values)] = #values; \ const char* T##_tokens[static_cast<__underlying_type(T)>(T::MAX)]; \ const char* const* T##_tmp_ptr = tokenize_enum_string( \ const_cast<char*>(enum_##T##_base), sizeof(#values), T##_tokens,\ static_cast<__underlying_type(T)>(T::MAX));#define enum_to_string(T, value) \ (T##_tokens[static_cast<__underlying_type(T)>(value)])static const char* const* tokenize_enum_string(char* base, int length, const char* tokens[], int size) { int count = 0; tokens[count++] = base; for (int i = 1; i < length; ++i) { if (base[i] == ',') { base[i] = '\0'; if (count == size) { return tokens; } do { if (++i == length) { return tokens; } } while (' ' == base[i]); tokens[count++] = base + i; } } return tokens;}static bool string_equals(const char* a, const char* b) { int i = 0; for (; a[i] && b[i]; ++i) { if (a[i] != b[i]) { return false; } } return (a[i] == b[i]);}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;}#define string_to_enum(T, value) \ static_cast<T>(string_to_enum_int( \ T##_tokens, static_cast<__underlying_type(T)>(T::MAX), value))
Работу со строками можете без проблем заменить на ваши любимые библиотеки, большинство кода здесь - это как раз парсинг строки (уж очень хотелось обойтись без STL).
Главная идея была в том, чтобы гарантировать биективность
множества enum и его строкового эквивалента, а также сделать
реализацию универсальной по количеству элементов (до
свидания, вырвиглазный хардкодный макрос _NARG). Ну
и, чтобы использование было максимально няшным.
// main.cpp#include <iostream>#include "enum_string.h"DECLARE_ENUM(LogLevel, // enum class LogLevel Alert, // LogLevel::Alert Critical, // LogLevel::Critical Error, // LogLevel::Error Warning, // LogLevel::Warning Notice, // LogLevel::Notice Info, // LogLevel::Info Debug // LogLevel::Debug );int main() { // serialize LogLevel a = LogLevel::Critical; std::cout << enum_to_string(LogLevel, a) << std::endl; // deserialize switch (string_to_enum(LogLevel, "Notice")) { case LogLevel::Alert: { std::cout << "ALERT" << std::endl; } break; case LogLevel::Critical: { std::cout << "CRITICAL" << std::endl; } break; case LogLevel::Error: { std::cout << "ERROR" << std::endl; } break; case LogLevel::Warning: { std::cout << "WARN" << std::endl; } break; case LogLevel::Notice: { std::cout << "NOTICE" << std::endl; } break; case LogLevel::Info: { std::cout << "INFO" << std::endl; } break; case LogLevel::Debug: { std::cout << "DEBUG" << std::endl; } break; case LogLevel::MAX: { std::cout << "Incorrect value" << std::endl; } break; } return 0;}
Как по мне, в дополнительном обьяснении не нуждается.
Также, залил на github.
Любезно приглашаю критиков на ревью.