如何轻松地将c ++枚举映射到string
我在使用一些库头文件中有一堆枚举types,我想有一种方法将枚举值转换为用户string – 反之亦然。
RTTI不会为我做,因为'用户string'需要比枚举更可读。
一个暴力解决scheme将是一堆这样的function,但我觉得这有点太C了。
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"); } }
我有一个直觉,认为有一个使用模板的优雅的解决scheme,但我不能完全把我的头。
更新:感谢您的build议 – 我应该明确指出,枚举是在第三方库头中定义的,所以我不想改变它们的定义。
我现在的直觉就是避免使用模板,做这样的事情:
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);
如果你想把枚举名字本身作为string,请看这篇文章 。 否则, std::map<MyEnum, char const*>
将很好地工作。 (没有意义将string文字复制到地图上的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&)
。 (从operator()
返回*this
允许operator()
的链接,如std::ostream
的operator<<
)
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可以提供相同的function。
MSalters解决scheme是一个很好的解决scheme,但基本上重新实现了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; }
自动生成另一个表单。
资源:
enum { VALUE1, /* value 1 */ VALUE2, /* value 2 */ };
产生:
const char* enum2str[] = { "value 1", /* VALUE1 */ "value 2", /* VALUE2 */ };
如果枚举值很大,那么生成的表单可以使用Constantinbuild议的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; }
我build议使用Xmacros的组合是最好的解决scheme和以下模板函数:
借用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)
我记得在StackOverflow的其他地方回答了这个问题。 在这里重复。 基本上它是一个基于可变macros的解决scheme,使用非常简单:
#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;
我使用以下重现的解决scheme:
#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; }
如果你想获取MyEnum
variables的string表示,那么模板将不会剪切它。 模板可以专门用于编译时已知的整数值。
但是,如果这是你想要的然后尝试:
#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
是重复的。
我已经多次需要这个function来debugging/分析其他代码。 为此,我编写了一个Perl脚本,它生成一个带有几个重载toString
方法的类。 每个toString
方法都将Enum
作为参数,并返回const char*
。
当然,脚本不会为枚举本身parsingC ++,而是使用ctags来生成符号表。
Perl脚本在这里: http : //heinitz-it.de/download/enum2string/enum2string.pl.html
你的回答激励我自己写一些macros。 我的要求如下:
-
只写一次枚举的每个值,所以没有双重列表来维护
-
不要将枚举值保存在一个单独的文件中,这个文件稍后会被包含,所以我可以在任何我想要的地方写入它
-
不要取代枚举本身,我仍然想要定义枚举types,但除此之外,我想能够将每个枚举名称映射到相应的string(不影响遗留代码)
-
search应该是快速的,所以最好是没有开关的情况下,为那些巨大的枚举
这段代码创build了一些经典的枚举值。 此外,它创build为std :: map将每个枚举值映射到它的名称(即地图[E_SUNDAY] =“E_SUNDAY”等)
好的,现在是代码:
EnumUtilsImpl.h :
map<int, string> & operator , (map<int, string> & dest, const pair<int, string> & keyValue) { dest[keyValue.first] = keyValue.second; return dest; } #define ADD_TO_MAP(name, value) pair<int, string>(name, #name)
EnumUtils.h //这是你想要包含的文件,只要你需要做这个东西,你将使用它的macros:
#include "EnumUtilsImpl.h" #define ADD_TO_ENUM(name, value) \ name value #define MAKE_ENUM_MAP_GLOBAL(values, mapName) \ int __makeMap##mapName() {mapName, values(ADD_TO_MAP); return 0;} \ int __makeMapTmp##mapName = __makeMap##mapName(); #define MAKE_ENUM_MAP(values, mapName) \ mapName, values(ADD_TO_MAP);
MyProjectCodeFile.h //这是一个如何使用它创build自定义枚举的例子:
#include "EnumUtils.h* #define MyEnumValues(ADD) \ ADD(val1, ), \ ADD(val2, ), \ ADD(val3, = 100), \ ADD(val4, ) enum MyEnum { MyEnumValues(ADD_TO_ENUM) }; map<int, string> MyEnumStrings; // this is how you initialize it outside any function MAKE_ENUM_MAP_GLOBAL(MyEnumValues, MyEnumStrings); void MyInitializationMethod() { // or you can initialize it inside one of your functions/methods MAKE_ENUM_MAP(MyEnumValues, MyEnumStrings); }
干杯。
我会试图有一张地图 – 并将其embedded到枚举中。
设置m [MyEnum.VAL1] =“值1”;
一切都完成了。
我只是想用macros来展示这个可能的优雅的解决scheme。 这并不能解决问题,但我认为这是重新考虑问题的好方法。
#define MY_LIST(X) X(value1), X(value2), X(value3) enum eMyEnum { MY_LIST(PLAIN) }; const char *szMyEnum[] = { MY_LIST(STRINGY) }; int main(int argc, char *argv[]) { std::cout << szMyEnum[value1] << value1 <<" " << szMyEnum[value2] << value2 << std::endl; return 0; }
—-编辑—-
经过一些networking研究和一些自己的研究,我来到了以下解决scheme:
//this is the enum definition #define COLOR_LIST(X) \ X( RED ,=21) \ X( GREEN ) \ X( BLUE ) \ X( PURPLE , =242) \ X( ORANGE ) \ X( YELLOW ) //these are the macros #define enumfunc(enums,value) enums, #define enumfunc2(enums,value) enums value, #define ENUM2SWITCHCASE(enums) case(enums): return #enums; #define AUTOENUM(enumname,listname) enum enumname{listname(enumfunc2)}; #define ENUM2STRTABLE(funname,listname) char* funname(int val) {switch(val) {listname(ENUM2SWITCHCASE) default: return "undef";}} #define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int values[] = {listname(enumfunc)};int N = sizeof(values)/sizeof(int);ENUM2STRTABLE(enum2str,listname)}; //here the enum and the string enum map table are generated AUTOENUM(testenum,COLOR_LIST) ENUM2STRTABLE(testfunenum,COLOR_LIST) ENUM2STRUCTINFO(colorinfo,COLOR_LIST)//colorinfo structur {int values[]; int N; char * enum2str(int);} //debug macros #define str(a) #a #define xstr(a) str(a) int main( int argc, char** argv ) { testenum x = YELLOW; std::cout << testfunenum(GREEN) << " " << testfunenum(PURPLE) << PURPLE << " " << testfunenum(x); for (int i=0;i< colorinfo::N;i++) std::cout << std::endl << colorinfo::values[i] << " "<< colorinfo::enum2str(colorinfo::values[i]); return EXIT_SUCCESS; }
我只是想发布,也许有人可以find这个解决scheme有用。 不需要模板类,不需要c ++ 11,也不需要提升,所以这也可以用于简单的C.
—- EDIT2 —-
当使用2个以上的枚举(编译器问题)时,信息表可能会产生一些问题。 以下解决方法工作:
#define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int spacename##_##values[] = {listname(enumfunc)};int spacename##_##N = sizeof(spacename##_##values)/sizeof(int);ENUM2STRTABLE(spacename##_##enum2str,listname)};
这里是一个尝试获取<<和>>stream操作符自动使用一行macros命令自动枚举…
定义:
#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; }
不知道这个计划的限制,但…欢迎您的意见!
typedef enum { ERR_CODE_OK = 0, ERR_CODE_SNAP, ERR_CODE_NUM } ERR_CODE; const char* g_err_msg[ERR_CODE_NUM] = { /* ERR_CODE_OK */ "OK", /* ERR_CODE_SNAP */ "Oh, snap!", };
以上是我简单的解决scheme。 它的一个好处是控制消息数组大小的“NUM”,它也可以防止出现边界访问(如果你明智地使用它)。
你也可以定义一个函数来得到string:
const char* get_err_msg(ERR_CODE code) { return g_err_msg[code]; }
除了我的解决scheme之外,我发现下面的一个很有趣。 它通常解决了上述同步问题。
幻灯片在这里: http : //www.slideshare.net/arunksaha/touchless-enum-tostring-28684724
代码在这里: https : //github.com/arunksaha/enum_to_string
在标题中:
enum EFooOptions { FooOptionsA = 0, EFooOptionsMin = 0, FooOptionsB, FooOptionsC, FooOptionsD EFooOptionsMax }; extern const wchar* FOO_OPTIONS[EFooOptionsMax];
在.cpp文件中:
const wchar* FOO_OPTIONS[] = { L"One", L"Two", L"Three", L"Four" };
警告:不要处理错误的数组索引。 :)但是你可以很容易地添加一个函数来validation从数组中获取string之前的枚举。
我最近与供应商库(Fincad)有同样的问题。 幸运的是,供应商为所有枚举提供了xml doucumentation。 我最终为每个枚举types生成一个映射,并为每个枚举提供一个查找函数。 这种技术还允许您截取枚举范围之外的查找。
我确信swig可以为你做类似的事情,但是我很乐意提供用ruby编写的代码生成工具。
以下是代码示例:
std::map<std::string, switches::FCSW2::type> init_FCSW2_map() { std::map<std::string, switches::FCSW2::type> ans; ans["Act365Fixed"] = FCSW2::Act365Fixed; ans["actual/365 (fixed)"] = FCSW2::Act365Fixed; ans["Act360"] = FCSW2::Act360; ans["actual/360"] = FCSW2::Act360; ans["Act365Act"] = FCSW2::Act365Act; ans["actual/365 (actual)"] = FCSW2::Act365Act; ans["ISDA30360"] = FCSW2::ISDA30360; ans["30/360 (ISDA)"] = FCSW2::ISDA30360; ans["ISMA30E360"] = FCSW2::ISMA30E360; ans["30E/360 (30/360 ISMA)"] = FCSW2::ISMA30E360; return ans; } switches::FCSW2::type FCSW2_lookup(const char* fincad_switch) { static std::map<std::string, switches::FCSW2::type> switch_map = init_FCSW2_map(); std::map<std::string, switches::FCSW2::type>::iterator it = switch_map.find(fincad_switch); if(it != switch_map.end()) { return it->second; } else { throw FCSwitchLookupError("Bad Match: FCSW2"); } }
似乎你想要另一种方式(枚举string,而不是string枚举),但这应该是微不足道的扭转。
-Whit
看下面的语法是否适合你:
// WeekEnd enumeration enum WeekEnd { Sunday = 1, Saturday = 7 }; // String support for WeekEnd Begin_Enum_String( WeekEnd ) { Enum_String( Sunday ); Enum_String( Saturday ); } End_Enum_String; // Convert from WeekEnd to string const std::string &str = EnumString<WeekEnd>::From( Saturday ); // str should now be "Saturday" // Convert from string to WeekEnd WeekEnd w; EnumString<WeekEnd>::To( w, "Sunday" ); // w should now be Sunday
如果是这样,那么你可能想看看这篇文章:
http://www.gamedev.net/reference/snippets/features/cppstringizing/
enum MyEnum { VAL1, VAL2,VAL3 }; #define StringMyEnum(e) ({__unused MyEnum _e = e;std::string(#e);}) #define CStringMyEnum(e) ({__unused MyEnum _e = e;#e;})
__unused是GCC / LLVM中的一个属性,那么你可以这样使用:
std::string s = StringMyEnum(VAL1); const char *c = CStringMyEnum(VAL1);