Перечислить строку в С++ 11
Я понимаю, что этот был задан перед более одного раза на SO, но я не мог найти вопрос, явно ищущий текущее решение этой проблемы с С++ 11, так что мы снова идем..
Можно ли получить строковое значение перечисления с С++ 11?
т.е. есть (теперь) любые встроенные функции в С++ 11, которые позволяют нам получить строковое представление типов перечисления, как в
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
cout << myOS
который будет печатать Linux
на консоли?
Ответы
Ответ 1
Долговечное и ненужное отсутствие общей функции перечисления в строку на С++ (и C) является болезненным. С++ 11 не обращался к этому, и, насколько я знаю, не будет С++ 14.
Лично я решил бы эту проблему с помощью генерации кода. Препроцессор C является одним из способов - вы можете увидеть некоторые другие ответы, связанные с комментариями здесь. Но на самом деле я предпочитаю просто писать собственное генерирование кода специально для перечислений. Затем он может легко генерировать to_string (char*)
, from_string
, ostream operator<<
, istream operator<<
, is_valid
и при необходимости использовать больше методов. Этот подход может быть очень гибким и мощным, но он обеспечивает абсолютную согласованность во многих перечислениях в проекте и не требует затрат времени исполнения.
Сделайте это с помощью превосходного пакета "mako" на Python или в Lua, если вы в легком весе, или CPP, если вы против зависимостей, или CMake собственных средств для генерации кода. Много способов, но все сводится к одному и тому же: вам нужно сгенерировать код самостоятельно - С++ не сделает этого для вас (к сожалению).
Ответ 2
На мой взгляд, наиболее удобный подход - написать вспомогательную функцию:
const char* get_name(OS_type os) {
switch (os) {
case Linux: return "Linux";
case Apple: return "Apple";
case Windows: return "Windows";
}
}
Это хорошая идея не реализовывать случай "по умолчанию", так как это обеспечит получение предупреждения компилятором, если вы забудете реализовать случай (с правильными настройками компилятора и компилятора).
Ответ 3
Вот простой пример использования пространств имен и структур.
Класс создается для каждого элемента перечисления. В этом примере я выбрал int
как тип для id.
#include <iostream>
using namespace std;
#define ENUMITEM(Id, Name) \
struct Name {\
static constexpr const int id = Id;\
static constexpr const char* name = #Name;\
};
namespace Food {
ENUMITEM(1, Banana)
ENUMITEM(2, Apple)
ENUMITEM(3, Orange)
}
int main() {
cout << Food::Orange::id << ":" << Food::Orange::name << endl;
return 0;
}
Вывод:
3:Orange
== Обновление ==
Использование:
#define STARTENUM() constexpr const int enumStart = __LINE__;
#define ENUMITEM(Name) \
struct Name {\
static constexpr const int id = __LINE__ - enumStart - 1;\
static constexpr const char* name = #Name;\
};
и используя его один раз перед первым использованием ENUMITEM, идентификаторы больше не нужны.
namespace Food {
STARTENUM()
ENUMITEM(Banana)
ENUMITEM(Apple)
ENUMITEM(Orange)
}
Переменная enumStart
доступна только через пространство имен - поэтому можно использовать несколько перечислений.
Ответ 4
Мне нравится взломать с помощью препроцессора C, который я впервые увидел здесь:
http://blogs.msdn.com/b/vcblog/archive/2008/04/30/enums-macros-unicode-and-token-pasting.aspx.
В нем используется оператор ввода-вывода.
// This code defines the enumerated values:
#define MY_ENUM(x) x,
enum Fruit_Type {
MY_ENUM(Banana)
MY_ENUM(Apple)
MY_ENUM(Orange)
};
#undef MY_ENUM
// and this code defines an array of string literals for them:
#define MY_ENUM(x) #x,
const char* const fruit_name[] = {
MY_ENUM(Banana)
MY_ENUM(Apple)
MY_ENUM(Orange)
};
#undef MY_ENUM
// Finally, here is some client code:
std::cout << fruit_name[Banana] << " is enum #" << Banana << "\n";
// In practice, those three "MY_ENUM" macro calls will be inside an #include file.
Честно говоря, это уродливо и. но вы в конечном итоге печатаете свои перечисления ровно один раз в включенном файле, что более удобно.
Кстати, на этой ссылке в блоге MSDN (см. выше) пользователь сделал комментарий с трюком, который делает все это намного красивее, и избегает #includes:
#define Fruits(FOO) \
FOO(Apple) \
FOO(Banana) \
FOO(Orange)
#define DO_DESCRIPTION(e) #e,
#define DO_ENUM(e) e,
char* FruitDescription[] = {
Fruits(DO_DESCRIPTION)
};
enum Fruit_Type {
Fruits(DO_ENUM)
};
// Client code:
std::cout << FruitDescription[Banana] << " is enum #" << Banana << "\n";
(я только заметил, что ответ 0x17de также использует оператор ввода-вывода)
Ответ 5
Вы можете использовать макрос для решения этой проблемы:
#define MAKE_ENUM(name, ...) enum class name { __VA_ARGS__}; \
static std::vector<std::string> Enum_##name##_init(){\
const std::string content = #__VA_ARGS__; \
std::vector<std::string> str;\
size_t len = content.length();\
std::ostringstream temp;\
for(size_t i = 0; i < len; i ++) {\
if(isspace(content[i])) continue;\
else if(content[i] == ',') {\
str.push_back(temp.str());\
temp.str(std::string());}\
else temp<< content[i];}\
str.push_back(temp.str());\
return str;}\
static const std::vector<std::string> Enum_##name##_str_vec = Enum_##name##_init();\
static std::string to_string(name val){\
return Enum_##name##_str_vec[static_cast<size_t>(val)];\
}\
static std::string print_all_##name##_enum(){\
int count = 0;\
std::string ans;\
for(auto& item:Enum_##name##_str_vec)\
ans += std::to_string(count++) + ':' + item + '\n';\
return ans;\
}
Поскольку статическая переменная может быть инициализирована только один раз, так что имя Enum _ ## name ## _ str_vec будет использовать функцию Enum _ ## name ## _ init() для инициализации сначала.
Пример кода выглядит следующим образом:
MAKE_ENUM(Analysis_Time_Type,
UNKNOWN,
REAL_TIME,
CLOSSING_TIME
);
Затем вы можете использовать нижеприведенное предложение для печати значения перечисления:
to_string(Analysis_Time_Type::UNKNOWN)
И используйте предложение ниже, чтобы напечатать все перечисление как строку:
print_all_Analysis_Time_Type_enum()
Ответ 6
Кто-то уже показал эквивалентную опцию с switch
, но я предпочитаю более простой подход if
- if else
- else
:
const char* get_name(OS_type os) {
if (os == Linux) return "Linux";
else if (os == Apple) return "Apple";
else if (os == Windows) return "Windows";
else throw std::runtime_error{"Undefined OS_type"};
}
В зависимости от компилятора он может дать предупреждение, потому что нет возврата для случая else. Просто создайте и верните OS_type::UndefinedOS
или что-то в этом роде.
const char* get_name(OS_type os) {
if (os == Linux) return "Linux";
else if (os == Apple) return "Apple";
else if (os == Windows) return "Windows";
else
{
throw std::runtime_error{"Undefined OS_type"};
return UndefinedOS;
}
}
Ответ 7
Как уже упоминалось, стандартного способа сделать это не существует. Но с небольшой препроцессорной магией (аналогичной второму вкладу AlejoHausner) и некоторой магией шаблона она может быть довольно элегантной.
Включите этот код один раз:
#include <string>
#include <algorithm>
#define ENUM_VALS( name ) name,
#define ENUM_STRINGS( name ) # name,
/** Template function to return the enum value for a given string
* Note: assumes enums are all upper or all lowercase,
* that they are contiguous/default-ordered,
* and that the first value is the default
* @tparam ENUM type of the enum to retrieve
* @tparam ENUMSIZE number of elements in the enum (implicit; need not be passed in)
* @param valStr string version of enum value to convert; may be any capitalization (capitalization may be modified)
* @param enumStrs array of strings corresponding to enum values, assumed to all be in lower/upper case depending upon
* enumsUpper
* @param enumsUpper true if the enum values are in all uppercase, false if in all lowercase (mixed case not supported)
* @return enum value corresponding to valStr, or the first enum value if not found
*/
template <typename ENUM, size_t ENUMSIZE>
static inline ENUM fromString(std::string &valStr, const char *(&enumStrs)[ENUMSIZE], bool enumsUpper = true) {
ENUM e = static_cast< ENUM >(0); // by default, first value
// convert valStr to lower/upper-case
std::transform(valStr.begin(), valStr.end(), valStr.begin(), enumsUpper ? ::toupper : ::tolower);
for (size_t i = 0; i< ENUMSIZE; i++) {
if (valStr == std::string(enumStrs[i])) {
e = static_cast< ENUM >(i);
break;
}
}
return e;
}
Затем определите каждое перечисление следующим образом:
//! Define ColorType enum with array for converting to/from strings
#define ColorTypes(ENUM) \
ENUM(BLACK) \
ENUM(RED) \
ENUM(GREEN) \
ENUM(BLUE)
enum ColorType {
ColorTypes(ENUM_VALS)
};
static const char* colorTypeNames[] = {
ColorTypes(ENUM_STRINGS)
};
Вам нужно только перечислить значения перечисления один раз, а код для его определения достаточно компактен и интуитивно понятен.
Значения обязательно будут пронумерованы по умолчанию (то есть 0,1,2,...). Код fromString()
предполагает, что значения перечисления находятся либо в верхнем регистре, либо в нижнем регистре (для преобразования из строк), который по умолчанию является значением по умолчанию, но вы, конечно, можете изменить способ обработки этих вещей.
Вот как вы получите строковое значение:
ColorType c = ColorType::BLUE;
std::cout << colorTypeNames[c]; // BLUE
Вот как вы устанавливаете перечисление из строкового значения:
ColorType c2 = fromString<ColorType>("Green", colorTypeNames); // == ColorType::GREEN
Ответ 8
Это два отличных подхода,
wise_enum
- Автономная интеллектуальная библиотека enum для C++ 14.11.17. Он поддерживает все стандартные функциональные возможности, которые вы ожидаете от класса smart enum в C++.
- Ограничения: требуется минимум C++ 11.
Лучшие перечисления
- Рефлексивная библиотека enum времени компиляции с чистым синтаксисом, в одном заголовочном файле и без зависимостей.
- Ограничения: основаны на макросах, не могут использоваться внутри класса.