C++ enum <-> string? Легко

от автора

Вот, скажем, один из самых популярных примеров. Можно сказать, классических. Сериализуются данные в, скажем, 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.

Любезно приглашаю критиков на ревью.

ссылка на оригинал статьи https://habr.com/ru/post/518842/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *