Пожалуй одна из старых и геморойных проблем, которая сваливалась на головы С++ кодеров, это как вывести на печать значения типа «Перечисления». Звучит возможно слишком утрирующее, но все же, многие С++ могли иметь с этим дело, в том или ином контексте проблемы\таски\проекта.
Забегая на перед, дать точного ответа на этот вопрос у нас тоже не выйдет. Как и всегда, слишком много факторов, ограничений, потребностей от которых зависит успех этих методов. Даже ваш Трижды любимый компилятор и тот повинен в этом)
Данная статья носит ознакомительный характер, и может походить как один из способов достижения цели, которая будет перед вами стоять при работе с перечислениями и строками.
Magic Enum - это библиотека-заголовков, которая предоставляет статическую рефлексию перечислениям.
Вы можете преобразовывать «из» и «в» строки, и вы можете перебирать значения перечисления. Она добавляет функцию «enum_cast».
GitHub
Минуса:
Constexpr – толковый инструменты, который позволяет определять статические вещи. Когда его используют в качестве возвращающего значения функции, он предоставляет нам сразу «опробовать» возвращаемое значения сразу в режиме компиляции.
В этом коде я сразу заюзал исключение по дефолту.
Динамическая версия
Дело в том, что несколько возвратов в функции constexpr – это стандарт C ++ 14. До C ++ 14 вы можете удалить спецификатор constexpr, чтобы написать динамическую версию этой функции.
ОФТОП: До C ++ 11 можно было удалить спецификатор класса enum и вместо этого использовать простое перечисление.
Минуса:
Использование специальной «функции-защиты» от исключений
Статическая версия
Порой мы выбираем код, который не рябит в глазах, и приятно читабельный. Лишние пробрасывания и прочие throw могут излишне усложнить читабельность. Вы можете написать безопасную для исключений функцию по «умолчанию» case.
Только теперь нужно следить за «error», когда нужно добавить элемент.
Динамическая версия
Опять же, динамическая версия без constexpr:
ОФТОП: До C ++ 11 можно было удалить спецификатор класса enum и вместо этого использовать простое перечисление.
Минуса:
Статическая версия
Динамическая версия
Очень похоже на статический, но если вам это нужно в версии до C ++ 11, вам придется избавиться от спецификатора constexpr. Кроме того, поскольку это версия до C ++ 11, у вас не может быть класса enum, вместо этого вам придется использовать простое перечисление.
Минуса:
Здесь PROCESS_ONE_ELEMENT «преобразует» элемент в его строковую версию (вызывая BOOST_PP_STRINGIZE), а BOOST_PP_SEQ_FOR_EACH_I выполняет итерацию по каждому элементу __VA_ARGS__ (который является пакетом параметров всего макроса).
Динамическая версия
Опять же, это очень похожая версия статической, но без constexpr или других спецификаторов C ++ 11.
Минуса:
Выбирайте умно и под свои нужды, ведь как я говорил, здесь много факторов и «подводных камней». Спасибо за внимание.
Забегая на перед, дать точного ответа на этот вопрос у нас тоже не выйдет. Как и всегда, слишком много факторов, ограничений, потребностей от которых зависит успех этих методов. Даже ваш Трижды любимый компилятор и тот повинен в этом)
Данная статья носит ознакомительный характер, и может походить как один из способов достижения цели, которая будет перед вами стоять при работе с перечислениями и строками.
Библиотека 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;
}
Динамическая версия
Опять же, это очень похожая версия статической, но без 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.
Выбирайте умно и под свои нужды, ведь как я говорил, здесь много факторов и «подводных камней». Спасибо за внимание.