Лучшие способы преобразования перечисления в строку
Одна из старейших проблем, с которой когда-либо сталкивались разработчики C ++, - это вывод значения типа enum.Хорошо, я могу быть немного чрезмерно драматичным здесь, но это проблема, с которой сталкиваются многие разработчики C ++, даже самые случайные.
Дело в том, что на этот вопрос нет ни одного верного ответа. Это зависит от многих вещей, таких как ваши ограничения, ваши потребности и, как всегда, версия компилятора C ++.
Эта статья представляет собой небольшой список способов добавления отражения в перечисления.
NB: Если вы знаете способ, который не указан здесь и имеет свои преимущества, не стесняйтесь поделиться им в комментариях.
Волшебная библиотека перечислений
Magic Enum - это библиотека только для заголовков, которая дает статическое отражение перечислениям.Вы можете конвертировать строки из и в строки и перебирать значения перечисления. Он добавляет функцию “enum_cast”.
Недостатки
- Это сторонняя библиотека.
- Работает только в C ++ 17.
- Вам нужны конкретные версии вашего компилятора для его работы (Clang>= 5, MSVC>= 15.3 и GCC>= 9).
- У вас есть несколько других ограничений, связанных с реализацией библиотеки. Проверьте страницу ограничений документации (Для просмотра ссылки Войди
или Зарегистрируйся)
Использование выделенной функции с исключением
Статическая версия
constexpr это великолепный инструмент, который позволяет нам статически определять вещи. При использовании в качестве возвращаемого значения функции он позволяет нам оценить возвращаемое значение функции во время компиляции.В этой версии я добавил исключение в default, поэтому, если произойдет так, что элемент будет добавлен, исключение будет вызвано.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include <iostream> enum class Esper { Unu, Du, Tri, Kvar, Kvin, Ses, Sep, Ok, Naux, Dek }; constexpr const char* EsperToString(Esper e) throw() { switch (e) { case Esper::Unu: return "Unu"; case Esper:: Du: return "Du"; case Esper::Tri: return "Tri"; case Esper::Kvar: return "Kvar"; case Esper::Kvin: return "Kvin"; case Esper::Ses: return "Ses"; case Esper::Sep: return "Sep"; case Esper::Ok: return "Ok"; case Esper::Naux: return "Naux"; case Esper:: Dek: return "Dek"; default: throw std::invalid_argument("Unimplemented item"); } } int main() { std::cout << EsperToString(Esper::Kvin) << std::endl; } |
Dynamic version
The thing is, having several returns in a constexpr function is C++14. Prior to C++14, you can remove the constexpr specifier to write a dynamic version of this function1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | #include <iostream> enum class Esper { Unu, Du, Tri, Kvar, Kvin, Ses, Sep, Ok, Naux, Dek }; const char* EsperToString(Esper e) throw() { switch (e) { case Esper::Unu: return "Unu"; case Esper:: Du: return "Du"; case Esper::Tri: return "Tri"; case Esper::Kvar: return "Kvar"; case Esper::Kvin: return "Kvin"; case Esper::Ses: return "Ses"; case Esper::Sep: return "Sep"; case Esper::Ok: return "Ok"; case Esper::Naux: return "Naux"; case Esper:: Dek: return "Dek"; default: throw std::invalid_argument("Unimplemented item"); } } int main() { std::cout << EsperToString(Esper::Kvin) << std::endl; } |
Drawbacks
- Наличие нескольких возвратов в constexprфункции - это C ++ 14 (для статической версии).
- Специфичный для каждого перечисления и очень многословный.
- Является исключением - небезопасно.
Использование специальной функции защиты от исключений
Статическая версия
Иногда вы предпочитаете код, который не бросает, несмотря ни на что. Или , может быть , вы похожи на меня и компилируете с.-Werror Если это так, вы можете написать функцию, защищенную от исключений, без defaultрегистра.Вам просто нужно следить за предупреждениями, когда вам нужно добавить элемент.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <iostream> enum class Esper { Unu, Du, Tri, Kvar, Kvin, Ses, Sep, Ok, Naux, Dek }; constexpr const char* EsperToString(Esper e) noexcept { switch (e) { case Esper::Unu: return "Unu"; case Esper:: Du: return "Du"; case Esper::Tri: return "Tri"; case Esper::Kvar: return "Kvar"; case Esper::Kvin: return "Kvin"; case Esper::Ses: return "Ses"; case Esper::Sep: return "Sep"; case Esper::Ok: return "Ok"; case Esper::Naux: return "Naux"; case Esper:: Dek: return "Dek"; } } int main() { std::cout << EsperToString(Esper::Kvin) << std::endl; } |
Dynamic version
Again, a dynamic version without constexpr:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | #include <iostream> enum class Esper { Unu, Du, Tri, Kvar, Kvin, Ses, Sep, Ok, Naux, Dek }; const char* EsperToString(Esper e) noexcept { switch (e) { case Esper::Unu: return "Unu"; case Esper:: Du: return "Du"; case Esper::Tri: return "Tri"; case Esper::Kvar: return "Kvar"; case Esper::Kvin: return "Kvin"; case Esper::Ses: return "Ses"; case Esper::Sep: return "Sep"; case Esper::Ok: return "Ok"; case Esper::Naux: return "Naux"; case Esper:: Dek: return "Dek"; } } int main() { std::cout << EsperToString(Esper::Kvin) << std::endl; } |
Недостатки
- Наличие нескольких возвратов в constexprфункции - это C ++ 14 (для статической версии).
- Специфичный для каждого перечисления и очень многословный.
- Предупреждения, как правило, игнорируются.
Использование макросов
Макросы могут делать многое, чего не может сделать динамический код. Вот две реализации с использованием макросов.Статическая версия
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #include <iostream> #define ENUM_MACRO(name, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10)\ enum class name { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10 };\ const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7, #v8, #v9, #v10};\ template<typename T>\ constexpr const char *name##ToString(T value) { return name##Strings[static_cast<int>(value)]; } ENUM_MACRO(Esper, Unu, Du, Tri, Kvar, Kvin, Ses, Sep, Ok, Naux, Dek); int main() { std::cout << EsperToString(Esper::Kvin) << std::endl; } |
Динамическая версия
Очень похоже на статический, но если вам это нужно в версии до C ++ 11, вам придется избавиться от constexprспецификатора. Кроме того, поскольку это версия до C ++ 11, вы не можете иметьenum class, вам придется пойти с равнинойenum.1 2 3 4 5 6 7 8 9 10 11 12 13 | #include <iostream> #define ENUM_MACRO(name, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10)\ enum name { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10 };\ const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7, #v8, #v9, #v10 };\ const char *name##ToString(int value) { return name##Strings[value]; } ENUM_MACRO(Esper, Unu, Du, Tri, Kvar, Kvin, Ses, Sep, Ok, Naux, Dek); int main() { std::cout << EsperToString(Kvin) << std::endl; } |
Drawbacks
- Uses macros (I could — has and will — write an article about why macros are bad practice in C++, but won’t do it here. For now, just keep in mind that if you don’t know why macros can be bad, then you shouldn’t use them)
- You need to write a different macro each time you need a reflective enum with a different number of items (with a different macro name, which is upsetting).
Using macros and boost
We can work around the “fixed number of enum items” drawback of the previous version by using Boost.Static version
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <iostream> #include <boost/preprocessor.hpp> #define PROCESS_ONE_ELEMENT(r, unused, idx, elem) \ BOOST_PP_COMMA_IF(idx) BOOST_PP_STRINGIZE(elem) #define ENUM_MACRO(name, ...)\ enum class name { __VA_ARGS__ };\ const char *name##Strings[] = { BOOST_PP_SEQ_FOR_EACH_I(PROCESS_ONE_ELEMENT, %%, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) };\ template<typename T>\ constexpr const char *name##ToString(T value) { return name##Strings[static_cast<int>(value)]; } ENUM_MACRO(Esper, Unu, Du, Tri, Kvar, Kvin, Ses, Sep, Ok, Naux, Dek); int main() { std::cout << EsperToString(Esper::Kvin) << std::endl; } |
Dynamic version
Again, it’s a very similar version of the static one, but without the constexpr or other C++11 specifiers.1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <iostream> #include <boost/preprocessor.hpp> #define PROCESS_ONE_ELEMENT(r, unused, idx, elem) \ BOOST_PP_COMMA_IF(idx) BOOST_PP_STRINGIZE(elem) #define ENUM_MACRO(name, ...)\ enum name { __VA_ARGS__ };\ const char *name##Strings[] = { BOOST_PP_SEQ_FOR_EACH_I(PROCESS_ONE_ELEMENT, %%, BOOST_PP_VARIADIC_TO_SEQ(__VA_ARGS__)) };\ const char *name##ToString(int value) { return name##Strings[value]; } ENUM_MACRO(Esper, Unu, Du, Tri, Kvar, Kvin, Ses, Sep, Ok, Naux, Dek); int main() { std::cout << EsperToString(Kvin) << std::endl; } |
Недостатки
- Использует макросы.
- Использует Boost.
Завершение
Вот небольшое резюме методов, описанных здесь:Имя | Является статическим? | Является ли generic? | сторонние библиотеки | Использует макросы? | Является ли исключение безопасным? |
Волшебное перечисление | Да (C ++ 17) | ДА | Да (волшебное перечисление) | НЕТ | НЕТ |
Функция w/ exception | Да (C++ 14) | НЕТ | НЕТ | НЕТ | НЕТ |
Функция без исключения | Да (C++ 14) | НЕТ | НЕТ | НЕТ | ДА |
Макрос | Да (C ++ 11) | НЕТ | НЕТ | ДА | ДА |
Макрос и Boost | Да (C ++ 11) | ДА | Yes (Boost) | ДА | ДА |