如何将枚举typesvariables转换为string?

如何使printf显示variables的枚举types的值? 例如:

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

而我需要的是类似的东西

 printenum(OS_type, "My OS is %s", myOS); 

其中必须显示一个string“Linux”,而不是一个整数。

我想,首先我必须创build一个价值索引的string数组。 但是我不知道这是否是最美丽的方式。 有没有可能?

真的没有美丽的方式做到这一点。 只需build立由枚举索引的string数组。

如果你做了很多的输出,你可以定义一个运算符<<,它接受一个枚举参数并为你做查找。

天真的解决scheme当然是为每个执行转换为string的枚举编写一个函数:

 enum OS_type { Linux, Apple, Windows }; inline const char* ToString(OS_type v) { switch (v) { case Linux: return "Linux"; case Apple: return "Apple"; case Windows: return "Windows"; default: return "[Unknown OS_type]"; } } 

但是,这是一场维护灾难。 在Boost.Preprocessor库的帮助下,可以同时使用C和C ++代码,您可以轻松利用预处理器并让它为您生成这个函数。 代macros如下:

 #include <boost/preprocessor.hpp> #define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem) \ case elem : return BOOST_PP_STRINGIZE(elem); #define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \ enum name { \ BOOST_PP_SEQ_ENUM(enumerators) \ }; \ \ inline const char* ToString(name v) \ { \ switch (v) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE, \ name, \ enumerators \ ) \ default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \ } \ } 

第一个macros(以X_开头)在第二个macros内部使用。 第二个macros首先生成枚举,然后生成一个ToString函数,该函数接受该types的对象并将该枚举器的名称作为string返回(此实现由于显而易见的原因要求枚举器映射到唯一值)。

在C ++中,您可以将ToString函数实现为一个operator<< overload,但是我认为需要一个明确的“ ToString ”将值转换为stringforms会更简洁一些。

作为使用示例,您的OS_type枚举将被定义如下:

 DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows)) 

虽然macros观起初看起来有很多工作, OS_type的定义看起来相当陌生,但请记住,您必须编写macros一次,然后您可以将其用于每个枚举。 您可以添加额外的function(例如,一个stringforms来枚举转换),而不会有太多麻烦,并且它完全解决了维护问题,因为您只需要在调用macros时提供一次名称。

枚举可以像使用正常定义一样使用:

 #include <iostream> int main() { OS_type t = Windows; std::cout << ToString(t) << " " << ToString(Apple) << std::endl; } 

这篇文章中的代码片段,从#include <boost/preprocessor.hpp>一行开始,可以编译为已发布,以演示解决scheme。

这个特殊的解决scheme是针对C ++的,因为它使用特定于C ++的语法(例如,没有typedef enum )和函数重载,但是直接使用C也是可以的。

这是预处理器块

 #ifndef GENERATE_ENUM_STRINGS #define DECL_ENUM_ELEMENT( element ) element #define BEGIN_ENUM( ENUM_NAME ) typedef enum tag##ENUM_NAME #define END_ENUM( ENUM_NAME ) ENUM_NAME; \ char* GetString##ENUM_NAME(enum tag##ENUM_NAME index); #else #define DECL_ENUM_ELEMENT( element ) #element #define BEGIN_ENUM( ENUM_NAME ) char* gs_##ENUM_NAME [] = #define END_ENUM( ENUM_NAME ) ; char* GetString##ENUM_NAME(enum \ tag##ENUM_NAME index){ return gs_##ENUM_NAME [index]; } #endif 

枚举定义

 BEGIN_ENUM(Os_type) { DECL_ENUM_ELEMENT(winblows), DECL_ENUM_ELEMENT(hackintosh), } 

呼叫使用

 GetStringOs_type(winblows); 

从这里采取。 多么酷啊 ? 🙂

C枚举的问题是,它不是它自己的types,就像在C ++中一样。 C中的枚举是将标识符映射到整数值的一种方法。 只是。 这就是为什么枚举值可以与整数值互换的原因。

正如你猜对的,一个好的方法是在枚举值和一个string之间build立一个映射关系。 例如:

 char * OS_type_label[] = { "Linux", "Apple", "Windows" }; 

使用std::map<OS_type, std::string>并使用枚举值作为键值和string表示值作为值,然后可以执行以下操作:

 printf("My OS is %s", enumMap[myOS].c_str()); std::cout << enumMap[myOS] ; 

假设你的枚举已经被定义了,你可以创build一个数组对:

 std::pair<QTask::TASK, QString> pairs [] = { std::pair<OS_type, string>(Linux, "Linux"), std::pair<OS_type, string>(Windows, "Windows"), std::pair<OS_type, string>(Apple, "Apple"), }; 

现在,你可以创build一个地图:

 std::map<OS_type, std::string> stdmap(pairs, pairs + sizeof(pairs) / sizeof(pairs[0])); 

现在,你可以使用地图。 如果你的枚举被改变,你必须添加/删除数组对[]。 我认为这是从C ++中获得一个string的最优雅的方式。

对于C99, P99中有P99_DECLARE_ENUM ,可以让你像这样声明enum

 P99_DECLARE_ENUM(color, red, green, blue); 

然后使用color_getname(A)获得一个带有颜色名称的string。

你试过这个:

 #define stringify( name ) # name enum enMyErrorValue { ERROR_INVALIDINPUT = 0, ERROR_NULLINPUT, ERROR_INPUTTOOMUCH, ERROR_IAMBUSY }; const char* enMyErrorValueNames[] = { stringify( ERROR_INVALIDINPUT ), stringify( ERROR_NULLINPUT ), stringify( ERROR_INPUTTOOMUCH ), stringify( ERROR_IAMBUSY ) }; void vPrintError( enMyErrorValue enError ) { cout << enMyErrorValueNames[ enError ] << endl; } int main() { vPrintError((enMyErrorValue)1); } 

stringify()macros可用于将代码中的任何文本转换为string,但只能使用括号中的精确文本。 没有可变的解引用或macrosreplace或任何其他types的事情。

http://www.cplusplus.com/forum/general/2949/

这个简单的例子为我工作。 希望这可以帮助。

 #include <iostream> #include <string> #define ENUM_TO_STR(ENUM) std::string(#ENUM) enum DIRECTION{NORTH, SOUTH, WEST, EAST}; int main() { std::cout << "Hello, " << ENUM_TO_STR(NORTH) << "!\n"; std::cout << "Hello, " << ENUM_TO_STR(SOUTH) << "!\n"; std::cout << "Hello, " << ENUM_TO_STR(EAST) << "!\n"; std::cout << "Hello, " << ENUM_TO_STR(WEST) << "!\n"; } 

这是我的C ++代码:

 /* * File: main.cpp * Author: y2k1234 * * Created on June 14, 2013, 9:50 AM */ #include <cstdlib> #include <stdio.h> using namespace std; #define MESSAGE_LIST(OPERATOR) \ OPERATOR(MSG_A), \ OPERATOR(MSG_B), \ OPERATOR(MSG_C) #define GET_LIST_VALUE_OPERATOR(msg) ERROR_##msg##_VALUE #define GET_LIST_SRTING_OPERATOR(msg) "ERROR_"#msg"_NAME" enum ErrorMessagesEnum { MESSAGE_LIST(GET_LIST_VALUE_OPERATOR) }; static const char* ErrorMessagesName[] = { MESSAGE_LIST(GET_LIST_SRTING_OPERATOR) }; int main(int argc, char** argv) { int totalMessages = sizeof(ErrorMessagesName)/4; for (int i = 0; i < totalMessages; i++) { if (i == ERROR_MSG_A_VALUE) { printf ("ERROR_MSG_A_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]); } else if (i == ERROR_MSG_B_VALUE) { printf ("ERROR_MSG_B_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]); } else if (i == ERROR_MSG_C_VALUE) { printf ("ERROR_MSG_C_VALUE => [%d]=[%s]\n", i, ErrorMessagesName[i]); } else { printf ("??? => [%d]=[%s]\n", i, ErrorMessagesName[i]); } } return 0; } Output: ERROR_MSG_A_VALUE => [0]=[ERROR_MSG_A_NAME] ERROR_MSG_B_VALUE => [1]=[ERROR_MSG_B_NAME] ERROR_MSG_C_VALUE => [2]=[ERROR_MSG_C_NAME] RUN SUCCESSFUL (total time: 126ms) 

我的解决scheme,不使用提升:

 #ifndef EN2STR_HXX_ #define EN2STR_HXX_ #define MAKE_STRING_1(str ) #str #define MAKE_STRING_2(str, ...) #str, MAKE_STRING_1(__VA_ARGS__) #define MAKE_STRING_3(str, ...) #str, MAKE_STRING_2(__VA_ARGS__) #define MAKE_STRING_4(str, ...) #str, MAKE_STRING_3(__VA_ARGS__) #define MAKE_STRING_5(str, ...) #str, MAKE_STRING_4(__VA_ARGS__) #define MAKE_STRING_6(str, ...) #str, MAKE_STRING_5(__VA_ARGS__) #define MAKE_STRING_7(str, ...) #str, MAKE_STRING_6(__VA_ARGS__) #define MAKE_STRING_8(str, ...) #str, MAKE_STRING_7(__VA_ARGS__) #define PRIMITIVE_CAT(a, b) a##b #define MAKE_STRING(N, ...) PRIMITIVE_CAT(MAKE_STRING_, N) (__VA_ARGS__) #define PP_RSEQ_N() 8,7,6,5,4,3,2,1,0 #define PP_ARG_N(_1,_2,_3,_4,_5,_6,_7,_8,N,...) N #define PP_NARG_(...) PP_ARG_N(__VA_ARGS__) #define PP_NARG( ...) PP_NARG_(__VA_ARGS__,PP_RSEQ_N()) #define MAKE_ENUM(NAME, ...) enum NAME { __VA_ARGS__ }; \ struct NAME##_str { \ static const char * get(const NAME et) { \ static const char* NAME##Str[] = { \ MAKE_STRING(PP_NARG(__VA_ARGS__), __VA_ARGS__) }; \ return NAME##Str[et]; \ } \ }; #endif /* EN2STR_HXX_ */ 

这里是如何使用它

 int main() { MAKE_ENUM(pippo, pp1, pp2, pp3,a,s,d); pippo c = d; cout << pippo_str::get(c) << "\n"; return 0; } 

这里有很多很好的答案,但是我认为有些人可能会觉得我很有用。 我喜欢它,因为您用来定义macros的界面非常简单。 这也很方便,因为您不必包含任何额外的库 – 它全都附带了C ++,甚至不需要非常迟的版本。 我在网上把各个地方的东西都拿出来了,所以我不能把所有的东西都拿出来,但是我认为这是足够独特的,可以给出一个新的答案。

首先创build一个头文件…将其称为EnumMacros.h或类似的东西,并把它放在它:

 #pragma once #include <string> #include <sstream> // Search and remove whitespace from both ends of the string static std::string TrimEnumString(const std::string &s) { std::string::const_iterator it = s.begin(); while (it != s.end() && isspace(*it)) { it++; } std::string::const_reverse_iterator rit = s.rbegin(); while (rit.base() != it && isspace(*rit)) { rit++; } return std::string(it, rit.base()); } static void SplitEnumArgs(const char* szArgs, std::string Array[], int nMax) { std::stringstream ss(szArgs); std::string strSub; int nIdx = 0; while (ss.good() && (nIdx < nMax)) { getline(ss, strSub, ','); Array[nIdx] = TrimEnumString(strSub); nIdx++; } }; #define DECLARE_ENUM(ename, ...) \ enum ename { __VA_ARGS__, MAX_NUMBER_OF_##ename }; \ static std::string ename##Strings[MAX_NUMBER_OF_##ename]; \ static const char* ename##ToString(ename e) { \ if (ename##Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, ename##Strings, MAX_NUMBER_OF_##ename); } \ return ename##Strings[e].c_str(); \ } \ static ename StringTo##ename(const char* szEnum) { \ for (int i = 0; i < MAX_NUMBER_OF_##ename; i++) { if (ename##Strings[i] == szEnum) { return (ename)i; } } \ return MAX_NUMBER_OF_##ename; \ } 

那么,在你的主程序中,你可以做到这一点…

 #include "EnumMacros.h" DECLARE_ENUM(OS_type, Windows, Linux, Apple) void main() { cout << OS_typeToString(Windows) << endl; if (StringToOS_type("Linux") == Linux) { cout << "yay!" << endl; } cout << "Max: " << MAX_NUMBER_OF_OS_type << endl; } 

请享用!

下面是使用C预处理器的旧Skool方法(以前在gcc中广泛使用)。 如果您正在生成离散的数据结构,但需要保持它们之间的顺序一致,则这很有用。 mylist.tbl中的条目当然可以扩展到更复杂的东西。

TEST.CPP:

 enum { #undef XX #define XX(name, ignore) name , #include "mylist.tbl" LAST_ENUM }; char * enum_names [] = { #undef XX #define XX(name, ignore) #name , #include "mylist.tbl" "LAST_ENUM" }; 

然后mylist.tbl:

 /* A = enum */ /* B = some associated value */ /* AB */ XX( enum_1 , 100) XX( enum_2 , 100 ) XX( enum_3 , 200 ) XX( enum_4 , 900 ) XX( enum_5 , 500 ) 

晚会有点迟,但这里是我的C ++ 11解决scheme:

 namespace std { template<> struct hash<enum_one> { std::size_t operator()(const enum_one & e) const { return static_cast<std::size_t>(e); } }; template<> struct hash<enum_two> { //repeat for each enum type std::size_t operator()(const enum_two & e) const { return static_cast<std::size_t>(e); } }; } const std::string & enum_name(const enum_one & e) { static const std::unordered_map<enum_one, const std::string> names = { #define v_name(n) {enum_one::n, std::string(#n)} v_name(value1), v_name(value2), v_name(value3) #undef v_name }; return names.at(e); } const std::string & enum_name(const enum_two & e) { //repeat for each enum type ................. } 

我自己的select是最小化重复键入和难以理解的macros,并避免将macros定义引入到通用编译器空间中。

所以,在头文件中:

 enum Level{ /** * zero reserved for internal use */ verbose = 1, trace, debug, info, warn, fatal }; static Level readLevel(const char *); 

而cpp的实现是:

  Logger::Level Logger::readLevel(const char *in) { # define MATCH(x) if (strcmp(in,#x) ==0) return x; MATCH(verbose); MATCH(trace); MATCH(debug); MATCH(info); MATCH(warn); MATCH(fatal); # undef MATCH std::string s("No match for logging level "); s += in; throw new std::domain_error(s); } 

注意到macros的#undef,只要我们完成了它。

另一个晚会,使用预处理器:

  1 #define MY_ENUM_LIST \ 2 DEFINE_ENUM_ELEMENT(First) \ 3 DEFINE_ENUM_ELEMENT(Second) \ 4 DEFINE_ENUM_ELEMENT(Third) \ 5 6 //-------------------------------------- 7 #define DEFINE_ENUM_ELEMENT(name) , name 8 enum MyEnum { 9 Zeroth = 0 10 MY_ENUM_LIST 11 }; 12 #undef DEFINE_ENUM_ELEMENT 13 14 #define DEFINE_ENUM_ELEMENT(name) , #name 15 const char* MyEnumToString[] = { 16 "Zeroth" 17 MY_ENUM_LIST 18 }; 19 #undef DEFINE_ENUM_ELEMENT 20 21 #define DEFINE_ENUM_ELEMENT(name) else if (strcmp(s, #name)==0) return name; 22 enum MyEnum StringToMyEnum(const char* s){ 23 if (strcmp(s, "Zeroth")==0) return Zeroth; 24 MY_ENUM_LIST 25 return NULL; 26 } 27 #undef DEFINE_ENUM_ELEMENT 

(我只是把行号放在一起,所以说起来更容易)。第1-4行是你编辑定义枚举元素的东西。 (我称之为“列表macros”,因为它是一个macros列表,它列出了一些东西。@Lundin告诉我这些是一个众所周知的叫做X-macros的技巧。

第7行定义内部macros,以便填写第8-11行中的实际枚举声明。 第12行取消定义内部macros(只是为了消除编译器警告)。

第14行定义了内部macros,以便创build一个string版本的枚举元素名称。 然后第15-18行生成一个数组,可以将枚举值转换为相应的string。

第21-27行生成一个将string转换为枚举值的函数,如果string不匹配,则返回NULL。

这在处理第0个元素的方式上有点麻烦。 实际上我曾经在那里工作过。

我承认这种技术困扰那些不想认为预处理器本身可以编程为您编写代码的人。 我认为这强烈地说明了可读性可维护性之间的区别。 代码难以阅读,但如果枚举有几百个元素,您可以添加,删除或重新排列元素,并确保生成的代码没有错误。

我把詹姆斯的 霍华德和埃德尔的解决scheme结合起来,创造出更通用的实现:

  • 可以为每个枚举元素定义int值和自定义string表示
  • 使用“枚举类”

完整的代码写在下面(使用“DEFINE_ENUM_CLASS_WITH_ToString_METHOD”来定义一个枚举)。

 #include <boost/preprocessor.hpp> // ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ implementation is taken from: // http://lists.boost.org/boost-users/2012/09/76055.php // // This macro do the following: // input: // (Element1, "Element 1 string repr", 2) (Element2) (Element3, "Element 3 string repr") // output: // ((Element1, "Element 1 string repr", 2)) ((Element2)) ((Element3, "Element 3 string repr")) #define HELPER1(...) ((__VA_ARGS__)) HELPER2 #define HELPER2(...) ((__VA_ARGS__)) HELPER1 #define HELPER1_END #define HELPER2_END #define ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(sequence) BOOST_PP_CAT(HELPER1 sequence,_END) // CREATE_ENUM_ELEMENT works in the following way: // if (elementTuple.GetSize() == 3) { // GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)), // } else { // GENERATE: elementTuple.GetElement(0), // } // Example 1: // CREATE_ENUM_ELEMENT(_, _, (Element1, "Element 1 string repr", 2)) // generates: // Element1 = 2, // // Example 2: // CREATE_ENUM_ELEMENT(_, _, (Element2)) // generates: // Element1, #define CREATE_ENUM_ELEMENT(r, data, elementTuple) \ BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 3), \ BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple), \ BOOST_PP_TUPLE_ELEM(0, elementTuple) \ ), #define DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, element) \ case enumName::element : return BOOST_PP_STRINGIZE(element); #define DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, element, stringRepresentation) \ case enumName::element : return stringRepresentation; // GENERATE_CASE_FOR_SWITCH macro generates case for switch operator. // Algorithm of working is the following // if (elementTuple.GetSize() == 1) { // DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, elementTuple.GetElement(0)) // } else { // DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, elementTuple.GetElement(0), elementTuple.GetElement(1)) // } // // Example 1: // GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element1, "Element 1 string repr", 2)) // generates: // case EnumName::Element1 : return "Element 1 string repr"; // // Example 2: // GENERATE_CASE_FOR_SWITCH(_, EnumName, (Element2)) // generates: // case EnumName::Element2 : return "Element2"; #define GENERATE_CASE_FOR_SWITCH(r, enumName, elementTuple) \ BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 1), \ DEFINE_CASE_HAVING_ONLY_ENUM_ELEMENT_NAME(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple)), \ DEFINE_CASE_HAVING_STRING_REPRESENTATION_FOR_ENUM_ELEMENT(enumName, BOOST_PP_TUPLE_ELEM(0, elementTuple), BOOST_PP_TUPLE_ELEM(1, elementTuple)) \ ) // DEFINE_ENUM_CLASS_WITH_ToString_METHOD final macro witch do the job #define DEFINE_ENUM_CLASS_WITH_ToString_METHOD(enumName, enumElements) \ enum class enumName { \ BOOST_PP_SEQ_FOR_EACH( \ CREATE_ENUM_ELEMENT, \ 0, \ ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements) \ ) \ }; \ inline const char* ToString(const enumName element) { \ switch (element) { \ BOOST_PP_SEQ_FOR_EACH( \ GENERATE_CASE_FOR_SWITCH, \ enumName, \ ADD_PARENTHESES_FOR_EACH_TUPLE_IN_SEQ(enumElements) \ ) \ default: return "[Unknown " BOOST_PP_STRINGIZE(enumName) "]"; \ } \ } DEFINE_ENUM_CLASS_WITH_ToString_METHOD(Elements, (Element1) (Element2, "string representation for Element2 ") (Element3, "Element3 string representation", 1000) (Element4, "Element 4 string repr") (Element5, "Element5", 1005) (Element6, "Element6 ") ) // Generates the following: // enum class Elements { // Element1, Element2, Element3 = 1000, Element4, Element5 = 1005, Element6, // }; // inline const char* ToString(const Elements element) { // switch (element) { // case Elements::Element1: return "Element1"; // case Elements::Element2: return "string representation for Element2 "; // case Elements::Element3: return "Element3 string representation"; // case Elements::Element4: return "Element 4 string repr"; // case Elements::Element5: return "Element5"; // case Elements::Element6: return "Element6 "; // default: return "[Unknown " "Elements" "]"; // } // } 

在c ++中是这样的:

 enum OS_type{Linux, Apple, Windows}; std::string ToString( const OS_type v ) { const std::map< OS_type, std::string > lut = boost::assign::map_list_of( Linux, "Linux" )(Apple, "Apple )( Windows,"Windows"); std::map< OS_type, std::string >::const_iterator it = lut.find( v ); if ( lut.end() != it ) return it->second; return "NOT FOUND"; } 
 #include <EnumString.h> 

http://www.codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C和之后;

 enum FORM { F_NONE = 0, F_BOX, F_CUBE, F_SPHERE, }; 

 Begin_Enum_String( FORM ) { Enum_String( F_NONE ); Enum_String( F_BOX ); Enum_String( F_CUBE ); Enum_String( F_SPHERE ); } End_Enum_String; 

如果枚举中的值不重复,则可以正常工作。

将枚举值转换为string的示例代码:

 enum FORM f = ... const std::string& str = EnumString< FORM >::From( f ); 

示例代码正好相反:

 assert( EnumString< FORM >::To( f, str ) ); 

感谢詹姆斯的build议。 这是非常有用的,所以我实施了另一种方式,以某种方式作出贡献。

 #include <iostream> #include <boost/preprocessor.hpp> using namespace std; #define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem) \ case data::elem : return BOOST_PP_STRINGIZE(elem); #define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF(r, data, elem) \ if (BOOST_PP_SEQ_TAIL(data) == \ BOOST_PP_STRINGIZE(elem)) return \ static_cast<int>(BOOST_PP_SEQ_HEAD(data)::elem); else #define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \ enum class name { \ BOOST_PP_SEQ_ENUM(enumerators) \ }; \ \ inline const char* ToString(name v) \ { \ switch (v) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE, \ name, \ enumerators \ ) \ default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \ } \ } \ \ inline int ToEnum(std::string s) \ { \ BOOST_PP_SEQ_FOR_EACH( \ X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOENUM_IF, \ (name)(s), \ enumerators \ ) \ return -1; \ } DEFINE_ENUM_WITH_STRING_CONVERSIONS(OS_type, (Linux)(Apple)(Windows)); int main(void) { OS_type t = OS_type::Windows; cout << ToString(t) << " " << ToString(OS_type::Apple) << " " << ToString(OS_type::Linux) << endl; cout << ToEnum("Windows") << " " << ToEnum("Apple") << " " << ToEnum("Linux") << endl; return 0; } 

为了扩展James的答案,有人想要一些示例代码来支持枚举定义int值,我也有这个要求,所以这是我的方式:

第一个是FOR_EACH使用的内部使用macros:

 #define DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE(r, data, elem) \ BOOST_PP_IF( \ BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elem), 2), \ BOOST_PP_TUPLE_ELEM(0, elem) = BOOST_PP_TUPLE_ELEM(1, elem), \ BOOST_PP_TUPLE_ELEM(0, elem) ), 

而且,这里是定义macros:

 #define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \ enum name { \ BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_WITH_STRING_CONVERSIONS_EXPAND_VALUE, \ 0, enumerators) }; 

所以当使用它时,你可能喜欢这样写:

 DEFINE_ENUM_WITH_STRING_CONVERSIONS(MyEnum, ((FIRST, 1)) ((SECOND)) ((MAX, SECOND)) ) 

这将扩大到:

 enum MyEnum { FIRST = 1, SECOND, MAX = SECOND, }; 

基本的想法是定义一个SEQ,其中每个元素都是TUPLE,所以我们可以为枚举成员添加附加值。 在FOR_EACH循环中,检查项目TUPLE的大小,如果大小为2,则将代码展开为KEY = VALUE,否则只保留TUPLE的第一个元素。

由于inputSEQ实际上是TUPLE,因此如果要定义STRINGIZE函数,则可能需要先对input枚举数进行预处理,以下是执行此工作的macros:

 #define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM(r, data, elem) \ BOOST_PP_TUPLE_ELEM(0, elem), #define DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ(enumerators) \ BOOST_PP_SEQ_SUBSEQ( \ BOOST_PP_TUPLE_TO_SEQ( \ (BOOST_PP_SEQ_FOR_EACH( \ DEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM, 0, enumerators) \ )), \ 0, \ BOOST_PP_SEQ_SIZE(enumerators)) 

macrosDEFINE_ENUM_WITH_STRING_CONVERSIONS_FIRST_ELEM_SEQ将只保留每个TUPLE中的第一个元素,然后转换为SEQ,现在修改James的代码,您将拥有全部权力。

我的实现可能不是最简单的,所以如果你没有find任何干净的代码,我的参考。

清洁,安全的纯标准解决schemeC:

 #include <stdio.h> #define STRF(x) #x #define STRINGIFY(x) STRF(x) /* list of enum constants */ #define TEST_0 hello #define TEST_1 world typedef enum { TEST_0, TEST_1, TEST_N } test_t; const char* test_str[]= { STRINGIFY(TEST_0), STRINGIFY(TEST_1), }; int main() { _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, "Incorrect number of items in enum or look-up table"); printf("%d %s\n", hello, test_str[hello]); printf("%d %s\n", world, test_str[world]); test_t x = world; printf("%d %s\n", x, test_str[x]); return 0; } 

产量

 0 hello 1 world 1 world 

合理

当解决核心问题“具有相应的string的枚举常量”时,一个明智的程序员将提出以下要求:

  • 避免代码重复(“干”原则)。
  • 即使在枚举中添加或删除项目,代码也必须是可伸缩的,可维护的和安全的。
  • 所有的代码应该是高质量的:易于阅读,易于维护。

第一个要求,也许还有第二个,可以用各种凌乱的macros观解决scheme来实现,例如臭名昭​​着的“xmacros”技巧或其他forms的macros魔法。 这样的解决scheme的问题是,他们给你一个完全难以理解的神秘macros – 他们不符合上述第三个要求。

这里唯一需要的是实际上有一个string查找表,我们可以通过使用enumvariables作为索引来访问它。 这样的表格必须直接对应于枚举,反之亦然。 当其中一个更新,另一个也必须更新,否则将无法正常工作。


代码的解释

假设我们有一个枚举类似

 typedef enum { hello, world } test_t; 

这可以更改为

 #define TEST_0 hello #define TEST_1 world typedef enum { TEST_0, TEST_1, } test_t; 

With the advantage that these macro constants can now be used elsewhere, to for example generate a string look-up table. Converting a pre-processor constant to a string can be done with a "stringify" macro:

 #define STRF(x) #x #define STRINGIFY(x) STRF(x) const char* test_str[]= { STRINGIFY(TEST_0), STRINGIFY(TEST_1), }; 

就是这样。 By using hello , we get the enum constant with value 0. By using test_str[hello] we get the string "hello".

To make the enum and look-up table correspond directly, we have to ensure that they contain the very same amount of items. If someone would maintain the code and only change the enum, and not the look-up table, or vice versa, this method won't work.

The solution is to have the enum to tell you how many items it contains. There is a commonly-used C trick for this, simply add an item at the end, which only fills the purpose of telling how many items the enum has:

 typedef enum { TEST_0, TEST_1, TEST_N // will have value 2, there are 2 enum constants in this enum } test_t; 

Now we can check at compile time that the number of items in the enum is as many as the number of items in the look-up table, preferably with a C11 static assert:

 _Static_assert(sizeof test_str / sizeof *test_str == TEST_N, "Incorrect number of items in enum or look-up table"); 

(There are ugly but fully-functional ways to create static asserts in older versions of the C standard too, if someone insists on using dinosaur compilers. As for C++, it supports static asserts too.)


As a side note, in C11 we can also achieve higher type safety by changing the stringify macro:

 #define STRINGIFY(x) _Generic((x), int : STRF(x)) 

( int because enumeration constants are actually of type int , not test_t )

This will prevent code like STRINGIFY(random_stuff) from compiling.

What I made is a combination of what I have seen here and in similar questions on this site. I made this is Visual Studio 2013. I have not tested it with other compilers.

First of all I define a set of macros that will do the tricks.

 // concatenation macros #define CONCAT_(A, B) A ## B #define CONCAT(A, B) CONCAT_(A, B) // generic expansion and stringification macros #define EXPAND(X) X #define STRINGIFY(ARG) #ARG #define EXPANDSTRING(ARG) STRINGIFY(ARG) // number of arguments macros #define NUM_ARGS_(X100, X99, X98, X97, X96, X95, X94, X93, X92, X91, X90, X89, X88, X87, X86, X85, X84, X83, X82, X81, X80, X79, X78, X77, X76, X75, X74, X73, X72, X71, X70, X69, X68, X67, X66, X65, X64, X63, X62, X61, X60, X59, X58, X57, X56, X55, X54, X53, X52, X51, X50, X49, X48, X47, X46, X45, X44, X43, X42, X41, X40, X39, X38, X37, X36, X35, X34, X33, X32, X31, X30, X29, X28, X27, X26, X25, X24, X23, X22, X21, X20, X19, X18, X17, X16, X15, X14, X13, X12, X11, X10, X9, X8, X7, X6, X5, X4, X3, X2, X1, N, ...) N #define NUM_ARGS(...) EXPAND(NUM_ARGS_(__VA_ARGS__, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1)) // argument extraction macros #define FIRST_ARG(ARG, ...) ARG #define REST_ARGS(ARG, ...) __VA_ARGS__ // arguments to strings macros #define ARGS_STR__(N, ...) ARGS_STR_##N(__VA_ARGS__) #define ARGS_STR_(N, ...) ARGS_STR__(N, __VA_ARGS__) #define ARGS_STR(...) ARGS_STR_(NUM_ARGS(__VA_ARGS__), __VA_ARGS__) #define ARGS_STR_1(ARG) EXPANDSTRING(ARG) #define ARGS_STR_2(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_1(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_3(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_2(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_4(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_3(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_5(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_4(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_6(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_5(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_7(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_6(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_8(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_7(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_9(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_8(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_10(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_9(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_11(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_10(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_12(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_11(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_13(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_12(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_14(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_13(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_15(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_14(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_16(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_15(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_17(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_16(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_18(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_17(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_19(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_18(EXPAND(REST_ARGS(__VA_ARGS__))) #define ARGS_STR_20(...) EXPANDSTRING(FIRST_ARG(__VA_ARGS__)), ARGS_STR_19(EXPAND(REST_ARGS(__VA_ARGS__))) // expand until _100 or as much as you need 

Next define a single macro that will create the enum class and the functions to get the strings.

 #define ENUM(NAME, ...) \ enum class NAME \ { \ __VA_ARGS__ \ }; \ \ static const std::array<std::string, NUM_ARGS(__VA_ARGS__)> CONCAT(NAME, Strings) = { ARGS_STR(__VA_ARGS__) }; \ \ inline const std::string& ToString(NAME value) \ { \ return CONCAT(NAME, Strings)[static_cast<std::underlying_type<NAME>::type>(value)]; \ } \ \ inline std::ostream& operator<<(std::ostream& os, NAME value) \ { \ os << ToString(value); \ return os; \ } 

Now defining an enum type and have strings for it becomes really easy. 所有你需要做的是:

 ENUM(MyEnumType, A, B, C); 

The following lines can be used to test it.

 int main() { std::cout << MyEnumTypeStrings.size() << std::endl; std::cout << ToString(MyEnumType::A) << std::endl; std::cout << ToString(MyEnumType::B) << std::endl; std::cout << ToString(MyEnumType::C) << std::endl; std::cout << MyEnumType::A << std::endl; std::cout << MyEnumType::B << std::endl; std::cout << MyEnumType::C << std::endl; auto myVar = MyEnumType::A; std::cout << myVar << std::endl; myVar = MyEnumType::B; std::cout << myVar << std::endl; myVar = MyEnumType::C; std::cout << myVar << std::endl; return 0; } 

这将输出:

 3 A B C A B C A B C 

I believe it is very clean and easy to use. There are some limitations:

  • You cannot assign values to the enum members.
  • The enum member's values are used as index, but that should be fine, because everything is defined in a single macro.
  • You cannot use it to define an enum type inside a class.

If you can work around this. I think, especially how to use it, this is nice and lean. 优点:

  • 使用方便。
  • No string splitting at runtime required.
  • Separate strings are available at compile time.
  • 易于阅读。 The first set of macros may need an extra second, but aren't really that complicated.

A clean solution to this problem would be:

 #define RETURN_STR(val, e) {if (val == e) {return #e;}} std::string conv_dxgi_format_to_string(int value) { RETURN_STR(value, DXGI_FORMAT_UNKNOWN); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_TYPELESS); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_FLOAT); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_UINT); RETURN_STR(value, DXGI_FORMAT_R32G32B32A32_SINT); RETURN_STR(value, DXGI_FORMAT_R32G32B32_TYPELESS); RETURN_STR(value, DXGI_FORMAT_R32G32B32_FLOAT); /* ... */ return "<UNKNOWN>"; } 

The good thing about this solution is that it is simple and also constructing the function can be done easily via copy and replace. Note that if you are going to do a lot of conversions and your enum has too many possible values, this solution might become CPU intensive.

I'm a bit late but here's my solution using g++ and only standard libraries. I've tried to minimise namespace pollution and remove any need to re-typing enum names.

The header file "my_enum.hpp" is:

 #include <cstring> namespace ENUM_HELPERS{ int replace_commas_and_spaces_with_null(char* string){ int i, N; N = strlen(string); for(i=0; i<N; ++i){ if( isspace(string[i]) || string[i] == ','){ string[i]='\0'; } } return(N); } int count_words_null_delim(char* string, int tot_N){ int i; int j=0; char last = '\0'; for(i=0;i<tot_N;++i){ if((last == '\0') && (string[i]!='\0')){ ++j; } last = string[i]; } return(j); } int get_null_word_offsets(char* string, int tot_N, int current_w){ int i; int j=0; char last = '\0'; for(i=0; i<tot_N; ++i){ if((last=='\0') && (string[i]!='\0')){ if(j == current_w){ return(i); } ++j; } last = string[i]; } return(tot_N); //null value for offset } int find_offsets(int* offsets, char* string, int tot_N, int N_words){ int i; for(i=0; i<N_words; ++i){ offsets[i] = get_null_word_offsets(string, tot_N, i); } return(0); } } #define MAKE_ENUM(NAME, ...) \ namespace NAME{ \ enum ENUM {__VA_ARGS__}; \ char name_holder[] = #__VA_ARGS__; \ int name_holder_N = \ ENUM_HELPERS::replace_commas_and_spaces_with_null(name_holder); \ int N = \ ENUM_HELPERS::count_words_null_delim( \ name_holder, name_holder_N); \ int offsets[] = {__VA_ARGS__}; \ int ZERO = \ ENUM_HELPERS::find_offsets( \ offsets, name_holder, name_holder_N, N); \ char* tostring(int i){ \ return(&name_holder[offsets[i]]); \ } \ } 

使用示例:

 #include <cstdio> #include "my_enum.hpp" MAKE_ENUM(Planets, MERCURY, VENUS, EARTH, MARS) int main(int argc, char** argv){ Planets::ENUM a_planet = Planets::EARTH; printf("%s\n", Planets::tostring(Planets::MERCURY)); printf("%s\n", Planets::tostring(a_planet)); } 

这将输出:

 MERCURY EARTH 

You only have to define everything once, your namespace shouldn't be polluted, and all of the computation is only done once (the rest is just lookups). However, you don't get the type-safety of enum classes (they are still just short integers), you cannot assign values to the enums, you have to define enums somewhere you can define namespaces (eg globally).

I'm not sure how good the performance on this is, or if it's a good idea (I learnt C before C++ so my brain still works that way). If anyone knows why this is a bad idea feel free to point it out.

It's 2017 but the question is still alive

Yet another way:

 #include <iostream> #define ERROR_VALUES \ ERROR_VALUE(NO_ERROR, 0, "OK") \ ERROR_VALUE(FILE_NOT_FOUND, 1, "Not found") \ ERROR_VALUE(LABEL_UNINITIALISED, 2, "Uninitialized usage") enum Error { #define ERROR_VALUE(NAME, VALUE, TEXT) NAME = VALUE, ERROR_VALUES #undef ERROR_VALUE }; inline std::ostream& operator<<(std::ostream& os, Error err) { int errVal = static_cast<int>(err); switch (err) { #define ERROR_VALUE(NAME, VALUE, TEXT) case NAME: return os << "[" << errVal << "]" << #NAME << ", " << TEXT; ERROR_VALUES #undef ERROR_VALUE default: // If the error value isn't found (shouldn't happen) return os << errVal; } } int main() { std::cout << "Error: " << NO_ERROR << std::endl; std::cout << "Error: " << FILE_NOT_FOUND << std::endl; std::cout << "Error: " << LABEL_UNINITIALISED << std::endl; return 0; } 

输出:

 Error: [0]NO_ERROR, OK Error: [1]FILE_NOT_FOUND, Not found Error: [2]LABEL_UNINITIALISED, Uninitialized usage 
 enum class COLOR { RED, BLUE }; class Test { public: string getColorStr( COLOR ecolor ) { return _typeStr.find( ecolor )->second; } private: static map<COLOR,string> _typeStr; }; //于海洋map<COLOR,string> Test:: _typeStr = { { COLOR::RED, "red"}, { COLOR::BLUE, "blue"} }; int main() { Test t; std::cout << t.getColorStr( COLOR::BLUE ) << std::endl; }