Подтвердить что ты не робот

Перечислить строку в С++ 11

Я понимаю, что этот был задан перед более одного раза на SO, но я не мог найти вопрос, явно ищущий текущее решение этой проблемы с С++ 11, так что мы снова идем..

Можно ли получить строковое значение перечисления с С++ 11?

т.е. есть (теперь) любые встроенные функции в С++ 11, которые позволяют нам получить строковое представление типов перечисления, как в

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

cout << myOS

который будет печатать Linux на консоли?

4b9b3361

Ответ 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 времени компиляции с чистым синтаксисом, в одном заголовочном файле и без зависимостей.
  • Ограничения: основаны на макросах, не могут использоваться внутри класса.