• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья Одни из лучших способов преобразование перечислений в строки (С++)

Dido

HDD-drive
Пользователь
Регистрация
06.01.2021
Сообщения
35
Реакции
34
Пожалуй одна из старых и геморойных проблем, которая сваливалась на головы С++ кодеров, это как вывести на печать значения типа «Перечисления». Звучит возможно слишком утрирующее, но все же, многие С++ могли иметь с этим дело, в том или ином контексте проблемы\таски\проекта.

Забегая на перед, дать точного ответа на этот вопрос у нас тоже не выйдет. Как и всегда, слишком много факторов, ограничений, потребностей от которых зависит успех этих методов. Даже ваш Трижды любимый компилятор и тот повинен в этом)

Данная статья носит ознакомительный характер, и может походить как один из способов достижения цели, которая будет перед вами стоять при работе с перечислениями и строками.


Библиотека Magic Enum

Magic Enum - это библиотека-заголовков, которая предоставляет статическую рефлексию перечислениям.

Вы можете преобразовывать «из» и «в» строки, и вы можете перебирать значения перечисления. Она добавляет функцию «enum_cast».


GitHub

Минуса:
  • Это «левая» библиотека.
  • Работает только в C ++ 17.
  • Для работы вам нужны определенные версии вашего компилятора (Clang > = 5, MSVC > = 15.3, GCC> = 9).
  • У вас есть еще несколько ограничений, связанных с реализацией библиотеки.
Использование специальной функции с исключением
Статическая версия

Constexpr – толковый инструменты, который позволяет определять статические вещи. Когда его используют в качестве возвращающего значения функции, он предоставляет нам сразу «опробовать» возвращаемое значения сразу в режиме компиляции.

В этом коде я сразу заюзал исключение по дефолту.


C++:
#include <iostream>

enum class Name { Alex, Dima, Sergey, Ivan, Oleg, Valik, Valentin, Masha, Nastya, Pavel };

constexpr const char* EsperToString(Name error) throw()
{
    switch (error)
    {
    case Name::Alex: return "Алексей";
    case Name::Dima: return "Дима";
    case Name::Sergey: return "Сергей";
    case Name::Ivan: return "Иван";
    case Name::Oleg: return "Олег";
    case Name::Valik: return "Валик";
    case Name::Valentin: return "Валентин";
    case Name::Masha: return "Маша";
    case Name::Nastya: return "Настя";
    case Name::Pavel: return "Павел";
    default: throw std::invalid_argument("Неверный аргумент");
    }
}

int main()
{
    setlocale(LC_ALL, "Russian");

    std::cout << EsperToString(Name::Dima) << std::endl;

    return 0;
}

Динамическая версия

Дело в том, что несколько возвратов в функции constexpr – это стандарт C ++ 14. До C ++ 14 вы можете удалить спецификатор constexpr, чтобы написать динамическую версию этой функции.

C++:
#include <iostream>

enum class Name { Alex, Dima, Sergey, Ivan, Oleg, Valik, Valentin, Masha, Nastya, Pavel };

const char* EsperToString(Name error) throw()
{
    switch (error)
    {
    case Name::Alex: return "Алексей";
    case Name::Dima: return "Дима";
    case Name::Sergey: return "Сергей";
    case Name::Ivan: return "Иван";
    case Name::Oleg: return "Олег";
    case Name::Valik: return "Валик";
    case Name::Valentin: return "Валентин";
    case Name::Masha: return "Маша";
    case Name::Nastya: return "Настя";
    case Name::Pavel: return "Павел";
    default: throw std::invalid_argument("Неверный аргумент");
    }
}

int main()
{
    setlocale(LC_ALL, "Russian");

    std::cout << EsperToString(Name::Dima) << std::endl;

    return 0;
}

ОФТОП: До C ++ 11 можно было удалить спецификатор класса enum и вместо этого использовать простое перечисление.

Минуса:
  • Наличие нескольких возра. знач. в функции constexpr - это C ++ 14 (для статической версии).
  • Специфичный для каждого перечисления и через «чур» подробный.
  • Небезопасно в «исключительных» случаях.

Использование специальной «функции-защиты» от исключений​


Статическая версия

Порой мы выбираем код, который не рябит в глазах, и приятно читабельный. Лишние пробрасывания и прочие throw могут излишне усложнить читабельность. Вы можете написать безопасную для исключений функцию по «умолчанию» case.

Только теперь нужно следить за «error», когда нужно добавить элемент.


C++:
#include <iostream>

enum class Name { Alex, Dima, Sergey, Ivan, Oleg, Valik, Valentin, Masha, Nastya, Pavel };

constexpr char* EsperToString(Name error) noexcept
{
    switch (error)
    {
    case Name::Alex: return "Алексей";
    case Name::Dima: return "Дима";
    case Name::Sergey: return "Сергей";
    case Name::Ivan: return "Иван";
    case Name::Oleg: return "Олег";
    case Name::Valik: return "Валик";
    case Name::Valentin: return "Валентин";
    case Name::Masha: return "Маша";
    case Name::Nastya: return "Настя";
    case Name::Pavel: return "Павел";
    }
}

int main()
{
    setlocale(LC_ALL, "Russian");

    std::cout << EsperToString(Name::Dima) << std::endl;

    return 0;
}

Динамическая версия

Опять же, динамическая версия без constexpr:
C++:
#include <iostream>

enum class Name { Alex, Dima, Sergey, Ivan, Oleg, Valik, Valentin, Masha, Nastya, Pavel };

char* EsperToString(Name error) noexcept
{
    switch (error)
    {
    case Name::Alex: return "Алексей";
    case Name::Dima: return "Дима";
    case Name::Sergey: return "Сергей";
    case Name::Ivan: return "Иван";
    case Name::Oleg: return "Олег";
    case Name::Valik: return "Валик";
    case Name::Valentin: return "Валентин";
    case Name::Masha: return "Маша";
    case Name::Nastya: return "Настя";
    case Name::Pavel: return "Павел";
    }
}

int main()
{
    setlocale(LC_ALL, "Russian");

    std::cout << EsperToString(Name::Dima) << std::endl;

    return 0;
}

ОФТОП: До C ++ 11 можно было удалить спецификатор класса enum и вместо этого использовать простое перечисление.

Минуса:
  • Наличие нескольких возра. знач. в функции constexpr - это C ++ 14 (для статической версии).
  • Специфичный для каждого перечисления и через «чур» подробный.
  • Предупреждения часто игнорируются.
Использование макросов
Макросы могут делать многое, чего не может делать динамический код. Вот две реализации с использованием макросов.

Статическая версия
C++:
#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(Name, Alex, Dima, Valik, Oleg, Sveta, Sergey, Valentin, Misha, Yura, Ivan);


int main()
{
    setlocale(LC_ALL, "Russian");

    std::cout << EsperToString(Name::Alex) << std::endl;

    return 0;
}
Динамическая версия

Очень похоже на статический, но если вам это нужно в версии до C ++ 11, вам придется избавиться от спецификатора constexpr. Кроме того, поскольку это версия до C ++ 11, у вас не может быть класса enum, вместо этого вам придется использовать простое перечисление.
C++:
#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(Name, Alex, Dima, Valik, Oleg, Sveta, Sergey, Valentin, Misha, Yura, Ivan);


int main()
{
    setlocale(LC_ALL, "Russian");

    std::cout << EsperToString(Name::Alex) << std::endl;

    return 0;
}

Минуса:
  • Юзаем макросы (не будем задвигать демагогию почему макросы bad-practice, просто если вы не знаете или не знакомы с ними вообще, просто не юзайте)
  • Вам нужно писать другой макрос каждый раз, когда вам нужно рефлексивное перечисление с другим количеством элементов (с другим именем макроса, что бесит).
Использование макросов и Boost
Мы можем обойти недостаток «фиксированного количества элементов перечисления» предыдущей версии с помощью Boost.
C++:
#include <iostream>
#include <boost/preprocessor.hpp>

#define PROCESS_ONE_ELEMENT(r, unused, idx, elem) \
  BOOST_PP_COMMA_IF(idx) BOOST_PP_STRINGIZE(elem)

#define NAME_MACROS(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)]; }

NAME_MACROS(Name, Alex, DIma, Yura, Kostik, Kolya, Sergey, Oleg, Masha, Natali , Ivan);

int main()
{
    setlocale(LC_ALL, "Russian");

    std::cout << EsperToString(Name::Alex) << std::endl;

    return 0;
}
Здесь PROCESS_ONE_ELEMENT «преобразует» элемент в его строковую версию (вызывая BOOST_PP_STRINGIZE), а BOOST_PP_SEQ_FOR_EACH_I выполняет итерацию по каждому элементу __VA_ARGS__ (который является пакетом параметров всего макроса).

Динамическая версия

Опять же, это очень похожая версия статической, но без constexpr или других спецификаторов C ++ 11.

C++:
#include <iostream>
#include <boost/preprocessor.hpp>

#define PROCESS_ONE_ELEMENT(r, unused, idx, elem) \
  BOOST_PP_COMMA_IF(idx) BOOST_PP_STRINGIZE(elem)

#define NAME_MACROS(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]; }

NAME_MACROS(Name, Alex, DIma, Yura, Kostik, Kolya, Sergey, Oleg, Masha, Natali , Ivan);

int main()
{
    setlocale(LC_ALL, "Russian");

    std::cout << EsperToString(Name::Alex) << std::endl;

    return 0;
}

Минуса:
  • Юзаем макросы.
  • Юзаем Boost.
Офтоп: Хотя библиотека Boost по-прежнему является левой библиотекой, она более приемлема и принята сообществом с++ кодеров по всему миру, чем другие библиотеки (например, ноунейм библиотека «Magic Enum» немного дичная), поэтому (среди прочего) эта версия может быть предпочтительнее первой.

Выбирайте умно и под свои нужды, ведь как я говорил, здесь много факторов и «подводных камней». Спасибо за внимание.
 
как вывести на печать значения типа «Перечисления»
Какая странная, высосанная из пальца проблема. Не припомню, чтобы когда-либо с таким сталкивался.
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх