C++ - Лучшие способы преобразования перечисления в строку

GuDron

dumpz.ws
Admin
Регистрация
28 Янв 2020
Сообщения
7,564
Реакции
1,435
Credits
24,411

Лучшие способы преобразования перечисления в строку​

Одна из старейших проблем, с которой когда-либо сталкивались разработчики 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 function
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 };

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;
}
Prior to C++11, you can remove the enum class specifier and use a plain enum instead.

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;
}
До C ++ 11 вы можете удалить enum classспецификатор и использовать enumвместо него обычный.

Недостатки​

  • Наличие нескольких возвратов в 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;
}
Here, the PROCESS_ONE_ELEMENT “converts” the item to its stringized version (calling BOOST_PP_STRINGIZE), and the BOOST_PP_SEQ_FOR_EACH_I iterates over every item of __VA_ARGS__ (which is the whole macro’s parameter pack).

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.
NB: Хотя библиотека boost все еще является сторонней библиотекой, она часто более приемлема, чем другие библиотеки (например, малоизвестная библиотека Magic Enum), поэтому (среди прочего) эта версия может быть предпочтительнее первой.

Завершение​

Вот небольшое резюме методов, описанных здесь:
ИмяЯвляется статическим?Является ли generic?сторонние библиотекиИспользует макросы?Является ли исключение безопасным?
Волшебное перечислениеДа (C ++ 17)ДАДа (волшебное перечисление)НЕТНЕТ
Функция w/ exceptionДа (C++ 14)НЕТНЕТНЕТНЕТ
Функция без исключенияДа (C++ 14)НЕТНЕТНЕТДА
МакросДа (C ++ 11)НЕТНЕТДАДА
Макрос и BoostДа (C ++ 11)ДАYes (Boost)ДАДА
Опять же, если вы знаете хороший способ преобразования перечислений в строку, пожалуйста, скажите об этом в комментариях.