Как легко отобразить перечисления c ++ в строки - PullRequest
110 голосов
/ 16 октября 2008

У меня есть несколько типов enum в некоторых заголовочных файлах библиотеки, которые я использую, и я хочу иметь способ преобразования значений enum в пользовательские строки - и наоборот.

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

Решение "грубой силы" - это набор функций, подобных этой, но я чувствую, что это слишком C-like.

enum MyEnum {VAL1, VAL2,VAL3};

String getStringFromEnum(MyEnum e)
{
  switch e
  {
  case VAL1: return "Value 1";
  case VAL2: return "Value 2";
  case VAL1: return "Value 3";
  default: throw Exception("Bad MyEnum");
  }
}

У меня есть ощущение, что есть элегантное решение с использованием шаблонов, но я пока не могу разобраться с этим.

ОБНОВЛЕНИЕ: Спасибо за предложения - я должен был пояснить, что перечисления определены в стороннем заголовке библиотеки, поэтому я не хочу менять их определение.

Теперь у меня возникает желание избегать шаблонов и делать что-то вроде этого:

char * MyGetValue(int v, char *tmp); // implementation is trivial

#define ENUM_MAP(type, strings) char * getStringValue(const type &T) \
 { \
 return MyGetValue((int)T, strings); \
 }

; enum eee {AA,BB,CC}; - exists in library header file 
; enum fff {DD,GG,HH}; 

ENUM_MAP(eee,"AA|BB|CC")
ENUM_MAP(fff,"DD|GG|HH")

// To use...

    eee e;
    fff f;
    std::cout<< getStringValue(e);
    std::cout<< getStringValue(f);

Ответы [ 18 ]

57 голосов
/ 16 октября 2008

Если вы хотите, чтобы перечисление именовало себя как строки, см. этот пост . В противном случае std::map<MyEnum, char const*> будет работать хорошо. (Нет смысла копировать строковые литералы в std :: strings на карте)

Для дополнительного синтаксического сахара, вот как написать класс map_init. Цель состоит в том, чтобы позволить

std::map<MyEnum, const char*> MyMap;
map_init(MyMap)
    (eValue1, "A")
    (eValue2, "B")
    (eValue3, "C")
;

Функция template <typename T> map_init(T&) возвращает map_init_helper<T>. map_init_helper<T> хранит T & и определяет тривиальный map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&). (Возврат *this из operator() допускает цепочку operator(), как operator<< в std::ostream с)

template<typename T> struct map_init_helper
{
    T& data;
    map_init_helper(T& d) : data(d) {}
    map_init_helper& operator() (typename T::key_type const& key, typename T::mapped_type const& value)
    {
        data[key] = value;
        return *this;
    }
};

template<typename T> map_init_helper<T> map_init(T& item)
{
    return map_init_helper<T>(item);
}

Поскольку функция и вспомогательный класс являются шаблонными, вы можете использовать их для любой карты или структуры, подобной карте. То есть он также может добавлять записи в std::unordered_map

Если вам не нравится писать эти помощники, boost :: assign предлагает те же функциональные возможности из коробки.

30 голосов
/ 04 декабря 2008

Решение MSalters - хорошее решение, но в основном оно реализует boost::assign::map_list_of. Если у вас есть повышение, вы можете использовать его напрямую:

#include <boost/assign/list_of.hpp>
#include <boost/unordered_map.hpp>
#include <iostream>

using boost::assign::map_list_of;

enum eee { AA,BB,CC };

const boost::unordered_map<eee,const char*> eeeToString = map_list_of
    (AA, "AA")
    (BB, "BB")
    (CC, "CC");

int main()
{
    std::cout << " enum AA = " << eeeToString.at(AA) << std::endl;
    return 0;
}
19 голосов
/ 16 октября 2008

Автоматически генерировать одну форму из другой.

Источник:

enum {
  VALUE1, /* value 1 */
  VALUE2, /* value 2 */
};

Сформирован:

const char* enum2str[] = {
  "value 1", /* VALUE1 */
  "value 2", /* VALUE2 */
};

Если значения перечисления велики, то сгенерированная форма может использовать unordered_map <> или шаблоны, как это предложил Константин.

Источник:

enum State{
  state0 = 0, /* state 0 */
  state1 = 1, /* state 1 */
  state2 = 2, /* state 2 */
  state3 = 4, /* state 3 */

  state16 = 0x10000, /* state 16 */
};

Сформирован:

template <State n> struct enum2str { static const char * const value; };
template <State n> const char * const enum2str<n>::value = "error";

template <> struct enum2str<state0> { static const char * const value; };
const char * const enum2str<state0>::value = "state 0";

Пример:

#include <iostream>

int main()
{
  std::cout << enum2str<state16>::value << std::endl;
  return 0;
}
11 голосов
/ 01 мая 2014

Я помню, что отвечал на это в другом месте на StackOverflow. Повторяя это здесь. В основном это решение, основанное на вариационных макросах, и довольно простое в использовании:

#define AWESOME_MAKE_ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT}; \
inline std::ostream& operator<<(std::ostream& os, name value) { \
std::string enumName = #name; \
std::string str = #__VA_ARGS__; \
int len = str.length(); \
std::vector<std::string> strings; \
std::ostringstream temp; \
for(int i = 0; i < len; i ++) { \
if(isspace(str[i])) continue; \
        else if(str[i] == ',') { \
        strings.push_back(temp.str()); \
        temp.str(std::string());\
        } \
        else temp<< str[i]; \
} \
strings.push_back(temp.str()); \
os << enumName << "::" << strings[static_cast<int>(value)]; \
return os;} 

Чтобы использовать его в своем коде, просто выполните:

AWESOME_MAKE_ENUM(Animal,
    DOG,
    CAT,
    HORSE
);
auto dog = Animal::DOG;
std::cout<<dog;
10 голосов
/ 26 ноября 2008

Я предлагаю сочетание из X-макросы являются лучшим решением и следующие функции шаблона:

Чтобы взять взаймы marcinkoziukmyopenidcom и продлен

enum Colours {
#   define X(a) a,
#   include "colours.def"
#   undef X
    ColoursCount
};

char const* const colours_str[] = {
#   define X(a) #a,
#   include "colours.def"
#   undef X
    0
};

template <class T> T str2enum( const char* );
template <class T> const char* enum2str( T );

#define STR2ENUM(TYPE,ARRAY) \
template <> \
TYPE str2enum<TYPE>( const char* str ) \
    { \
    for( int i = 0; i < (sizeof(ARRAY)/sizeof(ARRAY[0])); i++ ) \
        if( !strcmp( ARRAY[i], str ) ) \
            return TYPE(i); \
    return TYPE(0); \
    }

#define ENUM2STR(TYPE,ARRAY) \
template <> \
const char* enum2str<TYPE>( TYPE v ) \
    { \
    return ARRAY[v]; \
    }

#define ENUMANDSTR(TYPE,ARRAY)\
    STR2ENUM(TYPE,ARRAY) \
    ENUM2STR(TYPE,ARRAY)

ENUMANDSTR(Colours,colours_str)

colour.def

X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)
4 голосов
/ 16 октября 2008

Если вы хотите получить строковые представления MyEnum переменных , то шаблоны не будут обрезать его. Шаблон может быть специализирован на интегральных значениях, известных во время компиляции.

Однако, если вы этого хотите, попробуйте:

#include <iostream>

enum MyEnum { VAL1, VAL2 };

template<MyEnum n> struct StrMyEnum {
    static char const* name() { return "Unknown"; }
};

#define STRENUM(val, str) \
  template<> struct StrMyEnum<val> { \
    static char const* name() { return str; }};

STRENUM(VAL1, "Value 1");
STRENUM(VAL2, "Value 2");

int main() {
  std::cout << StrMyEnum<VAL2>::name();
}

Это многословно, но обнаружит ошибки, подобные той, которую вы допустили - ваш case VAL1 дублирован.

3 голосов
/ 10 апреля 2015

Я использую это решение, которое я воспроизвожу ниже:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}
2 голосов
/ 16 октября 2008

Я хотел бы иметь карту m и встраивать ее в перечисление.

настройка с m [MyEnum.VAL1] = "Значение 1";

и все сделано.

2 голосов
/ 21 марта 2010

Мне несколько раз требовался этот функционал для отладки / анализа кода от других. Для этого я написал Perl-скрипт, который генерирует класс с несколькими перегруженными toString методами. Каждый метод toString принимает Enum в качестве аргумента и возвращает const char*.

Конечно, скрипт не анализирует C ++ для перечислений, но использует ctags для генерации таблицы символов.

Здесь скрипт Perl: http://heinitz -it.de / скачать / enum2string / enum2string.pl.html

2 голосов
/ 07 марта 2014

Вот попытка автоматически получить операторы << и >> в enum с помощью однострочной макрокоманды ...

Определения:

#include <string>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>

#define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__)
#define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__)
#define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__)
#define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__)
#define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__)
#define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__)
#define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__)
#define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__)
#define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__)
#define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__)
#define MAKE_STRING10_(str) #str

#define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__)
#define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__)

#define MAKE_ENUM_(attribute, name, ...) name { __VA_ARGS__ }; \
    attribute std::istream& operator>>(std::istream& is, name& e) { \
        const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
        std::string str; \
        std::istream& r = is >> str; \
        const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \
        const std::vector<std::string> enumStr(name##Str, name##Str + len); \
        const std::vector<std::string>::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \
        if (it != enumStr.end())\
            e = name(it - enumStr.begin()); \
        else \
            throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \
        return r; \
    }; \
    attribute std::ostream& operator<<(std::ostream& os, const name& e) { \
        const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
        return (os << name##Str[e]); \
    }

Использование:

// Declare global enum
enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43);

class Essai {
public:
    // Declare enum inside class
    enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4);

};

int main() {
    std::cout << Essai::Item1 << std::endl;

    Essai::Test ddd = Essai::Item1;
    std::cout << ddd << std::endl;

    std::istringstream strm("Item2");
    strm >> ddd;

    std::cout << (int) ddd << std::endl;
    std::cout << ddd << std::endl;
}

Не уверен насчет ограничений этой схемы, хотя ... комментарии приветствуются!

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