如何将枚举类型变量转换为字符串?

如何使 printf 显示属于枚举类型的变量的值? 例如:

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

我需要的是

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

它必须显示一个字符串“ Linux”,而不是一个整数。

我想,首先我必须创建一个值索引的字符串数组。但我不知道这是不是最美妙的方式。有可能吗?

302648 次浏览

使用 std::map<OS_type, std::string>并将 enum 作为键填充,将字符串表示作为值填充,然后您可以执行以下操作:

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

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

正如您猜测的那样,一个好的方法是在枚举值和字符串之间创建一个映射。例如:

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

在 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";
}

这是预处理器块

#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(OsType)
{
DECL_ENUM_ELEMENT(WINBLOWS),
DECL_ENUM_ELEMENT(HACKINTOSH),
} END_ENUM(OsType)

使用电话

GetStringOsType (WINBLOWS) ;

取自 给你。多酷啊? :)

实际上没有什么漂亮的方法可以做到这一点。只需要设置一个由枚举索引的字符串数组。

如果执行大量输出,则可以定义一个运算符 < < ,该运算符接受一个枚举参数并为您执行查找。

假设已经定义了枚举,您可以创建一个对数组:

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"),
};

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

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

现在,你可以用地图了。如果更改了枚举,则必须从数组对[]中添加/删除对。我认为这是 C + + 中从 enum 获取字符串的最优雅的方法。

当然,最初的解决方案是为执行字符串转换的每个枚举编写一个函数:

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 的帮助下。预处理器库,可以与 C 和 C + + 代码一起使用,您可以很容易地利用预处理器并让它为您生成这个函数。生成宏如下:

#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) "]";         \
}                                                                     \
}

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

在 C + + 中,您可以将 ToString函数实现为 operator<<重载,但是我认为需要一个显式的“ ToString”来将值转换为字符串形式会更简单一些。

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

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

虽然这个宏一开始看起来工作量很大,而且 OS_type的定义看起来很陌生,但是请记住,您必须编写这个宏一次,然后才能在每个枚举中使用它。您可以向它添加额外的功能(例如,一个字符串形式的枚举转换)而不会有太多麻烦,并且它完全解决了维护问题,因为您只需要在调用宏时提供一次名称。

然后,可以像正常定义的那样使用枚举:

#include <iostream>


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

本文中的代码片段,从 #include <boost/preprocessor.hpp>行开始,可以按照发布的内容进行编译,以演示解决方案。

这个特殊的解决方案是针对 C + + 的,因为它使用了 C + + 特定的语法(例如,没有 typedef enum)和函数重载,但是使用 C 也可以很容易地实现这一点。

对于 C99,P99中的 P99_DECLARE_ENUM允许您像下面这样简单地声明 enum:

P99_DECLARE_ENUM(color, red, green, blue);

然后使用 color_getname(A)获取具有颜色名称的字符串。

下面是只使用 C 预处理器的 Old Skool 方法(过去在 gcc 中广泛使用)。如果您正在生成离散的数据结构,但是需要保持它们之间的顺序一致,那么这种方法很有用。Tbl 中的条目当然可以扩展到更复杂的内容。

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 */
/*     A        B   */
XX( enum_1 , 100)
XX( enum_2 , 100 )
XX( enum_3 , 200 )
XX( enum_4 , 900 )
XX( enum_5 , 500 )
#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;

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

将枚举值转换为字符串的示例代码:

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

相反的示例代码:

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

你试过这个吗:

#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()宏将代码中的任何文本转换为字符串,但只能是括号之间的确切文本。没有变量解引用或宏替换或任何其他类型的事情做。

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

下面是我的 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)

有点晚了,但这是我的 C + + 11解决方案:

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
.................
}

我自己的偏好是尽量减少重复类型和难以理解的宏,并避免在一般编译器空间中引入宏定义。

因此,在头文件中:

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);
}

注意宏的 # undef。

我的解决方案,不使用助推器:

#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;
}

谢谢詹姆斯的建议。它非常有用,所以我用另一种方式实现了它,以某种方式做出了贡献。

#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 值的 enum 定义,我也有这个需求,所以这里是我的方法:

第一个是内部使用宏,由 FOR _ EACH 使用:

#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) ),

下面是定义宏:

#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,因此我们可以为 enum 成员设置附加值。在 FOR _ EACH 循环中,检查项 TUPLE size,如果大小为2,则将代码展开为 KEY = VALUE,否则只保留 TUPLE 的第一个元素。

因为输入 SEQ 实际上是 TUPLE,所以如果你想定义 STRINGIZE 函数,你可能需要首先预处理输入枚举数,这里有一个宏来完成这项工作:

#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))

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

我的实现可能不是最简单的一个,所以如果您没有找到任何干净的代码,我的参考。

这里有很多好的答案,但是我想有些人会觉得我的答案很有用。我喜欢它是因为用来定义宏的接口非常简单。它还很方便,因为你不需要包含任何额外的库-它都是 C + + 自带的,而且它甚至不需要一个非常晚的版本。我从网上搜集了不同地方的文章,所以我不能把所有的功劳都揽到自己身上,但是我认为它足够独特,值得给出一个新的答案。

首先创建一个头文件... ... 命名为 EnumMacros.h 或类似的东西,并将其放入其中:

// 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++;
}
};
// This will to define an enum that is wrapped in a namespace of the same name along with ToString(), FromString(), and COUNT
#define DECLARE_ENUM(ename, ...) \
namespace ename { \
enum ename { __VA_ARGS__, COUNT }; \
static std::string _Strings[COUNT]; \
static const char* ToString(ename e) { \
if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
return _Strings[e].c_str(); \
} \
static ename FromString(const std::string& strEnum) { \
if (_Strings[0].empty()) { SplitEnumArgs(#__VA_ARGS__, _Strings, COUNT); } \
for (int i = 0; i < COUNT; i++) { if (_Strings[i] == strEnum) { return (ename)i; } } \
return COUNT; \
} \
}

然后,在你的主程序中你可以这样做..。

#include "EnumMacros.h"
DECLARE_ENUM(OsType, Windows, Linux, Apple)


void main() {
OsType::OsType MyOs = OSType::Apple;
printf("The value of '%s' is: %d of %d\n", OsType::ToString(MyOs), (int)OsType::FromString("Apple"), OsType::COUNT);
}

输出为 > > “ Apple”的值是: 2/4

好好享受吧!

另一个晚到的人,使用了预处理器:

 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行是用于定义枚举元素的编辑内容。 (我把它叫做“列表宏”,因为它是一个用来列出事物的宏。@ Lundin 告诉我,这是一种广为人知的技术,叫做 X 宏。)

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

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

第21-27行生成一个函数,该函数将字符串转换为枚举值,如果字符串不匹配任何值,则返回 NULL。

这在处理第0个元素时有点麻烦。 其实我以前也这么干过。

我承认,这种技术让那些不愿意认为预处理器本身可以编程为您编写代码的人感到困扰。 我认为它有力地说明了 可读性可维护性之间的区别。 密码很难读懂, 但是如果枚举有几百个元素,您可以添加、删除或重新排列元素,同时仍然可以确保生成的代码没有错误。

清洁、安全的纯 C 标准溶液:

#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

理由

当解决核心问题“使枚举常量和相应的字符串”时,一个明智的程序员会提出以下要求:

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

第一个要求,也可能是第二个要求,可以通过各种混乱的宏解决方案来实现,比如臭名昭著的“ x 宏”技巧,或者其他形式的宏魔术。这种解决方案的问题在于,它们给您留下了一堆完全不可读的神秘宏——它们不符合上述第三个要求。

这里唯一需要的是一个字符串查找表,我们可以通过使用 enum 变量作为索引来访问它。这样的表必须与枚举直接对应,反之亦然。当其中一个被更新时,另一个也必须被更新,否则它将不工作。


解释代码

假设我们有一个像

typedef enum
{
hello,
world
} test_t;

这可以改为

#define TEST_0 hello
#define TEST_1 world


typedef enum
{
TEST_0,
TEST_1,
} test_t;

这些宏常量的优点是现在可以在其他地方使用,例如生成一个字符串查找表。将预处理常量转换为字符串可以通过“ stringify”宏来完成:

#define STRF(x) #x
#define STRINGIFY(x) STRF(x)


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

就是这样。通过使用 hello,我们得到了值为0的枚举常数。通过使用 test_str[hello],我们得到字符串“ hello”。

为了使枚举和查找表直接对应,我们必须确保它们包含相同数量的项。如果有人维护代码,只更改枚举,而不更改查找表,或者相反,这种方法将无法工作。

解决方案是让枚举告诉您它包含多少项。这里有一个常用的 C 技巧,简单地在结尾添加一个条目,这只是为了告诉枚举有多少条目:

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

现在,我们可以在编译时检查枚举中的项数是否与查找表中的项数一样多,最好使用 C11静态断言:

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

(如果有人坚持使用过时的编译器,在旧版本的 C 标准中也有一些丑陋但功能齐全的方法来创建静态断言。至于 C + + ,它也支持静态断言。)


另外,在 C11中,我们还可以通过改变 stringify 宏来实现更高的类型安全性:

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

(因为枚举常数实际上是 int类型,而不是 test_t类型)

这将阻止像 STRINGIFY(random_stuff)这样的代码进行编译。

我所做的是我在这里看到的和在这个网站类似的问题的组合。这是我做的 Visual Studio 2013。我还没有用其他编译器测试过它。

首先,我定义了一组宏来完成这些技巧。

// 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

接下来定义一个宏,它将创建枚举类和获取字符串的函数。

#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;                                                                                                  \
}

现在,定义一个枚举类型并为其创建字符串变得非常简单:

ENUM(MyEnumType, A, B, C);

可以使用下面的代码行对其进行测试。

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

我相信它是非常干净和容易使用。有一些限制:

  • 不能将值赋给枚举成员。
  • 枚举成员的值用作索引,但这应该没问题,因为所有内容都在单个宏中定义。
  • 不能使用它在类中定义枚举类型。

如果你能解决这个问题,我认为,特别是如何使用它,这是很好的,精益。优点:

  • 很好用。
  • 在运行时不需要拆分字符串。
  • 单独的字符串在编译时可用。
  • 第一组宏可能需要额外的一秒钟,但实际上并没有那么复杂。

解决这个问题的一个干净利落的办法是:

#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>";
}

这个解决方案的好处是它很简单,而且构造函数也可以通过复制和替换很容易地完成。请注意,如果要进行大量转换,并且枚举具有太多可能的值,则此解决方案可能会占用大量 CPU 资源。

我结合了 詹姆斯的霍华德家埃德尔的解决方案,创建了一个更通用的实现:

  • 可以为每个枚举元素选择性地定义 int 值和自定义字符串表示形式
  • 使用“ enum class”

完整的代码写在下面(定义枚举时使用“ DEFINE _ ENUM _ CLASS _ WAS _ ToString _ Method”)(在线演示)。

#include <boost/preprocessor.hpp>
#include <iostream>


// 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_IMPL works in the following way:
//  if (elementTuple.GetSize() == 4) {
//      GENERATE: elementTuple.GetElement(0) = elementTuple.GetElement(2)),
//  } else {
//      GENERATE: elementTuple.GetElement(0),
//  }
// Example 1:
//      CREATE_ENUM_ELEMENT_IMPL((Element1, "Element 1 string repr", 2, _))
//  generates:
//      Element1 = 2,
//
// Example 2:
//      CREATE_ENUM_ELEMENT_IMPL((Element2, _))
//  generates:
//      Element1,
#define CREATE_ENUM_ELEMENT_IMPL(elementTuple)                                          \
BOOST_PP_IF(BOOST_PP_EQUAL(BOOST_PP_TUPLE_SIZE(elementTuple), 4),                       \
BOOST_PP_TUPLE_ELEM(0, elementTuple) = BOOST_PP_TUPLE_ELEM(2, elementTuple),        \
BOOST_PP_TUPLE_ELEM(0, elementTuple)                                                \
),


// we have to add a dummy element at the end of a tuple in order to make
// BOOST_PP_TUPLE_ELEM macro work in case an initial tuple has only one element.
// if we have a tuple (Element1), BOOST_PP_TUPLE_ELEM(2, (Element1)) macro won't compile.
// It requires that a tuple with only one element looked like (Element1,).
// Unfortunately I couldn't find a way to make this transformation, so
// I just use BOOST_PP_TUPLE_PUSH_BACK macro to add a dummy element at the end
// of a tuple, in this case the initial tuple will look like (Element1, _) what
// makes it compatible with BOOST_PP_TUPLE_ELEM macro
#define CREATE_ENUM_ELEMENT(r, data, elementTuple)                                      \
CREATE_ENUM_ELEMENT_IMPL(BOOST_PP_TUPLE_PUSH_BACK(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 ")
(Element7)
)
// 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 ";
//              case Elements::Element7: return "Element7";
//              default: return "[Unknown " "Elements" "]";
//          }
//      }


int main() {
std::cout << ToString(Elements::Element1) << std::endl;
std::cout << ToString(Elements::Element2) << std::endl;
std::cout << ToString(Elements::Element3) << std::endl;
std::cout << ToString(Elements::Element4) << std::endl;
std::cout << ToString(Elements::Element5) << std::endl;
std::cout << ToString(Elements::Element6) << std::endl;
std::cout << ToString(Elements::Element7) << std::endl;


return 0;
}

我有点晚了,但这里是我的解决方案使用 g + + 和只有标准库。我试图尽量减少名称空间污染并消除重新键入枚举名称的任何需要。

头文件“ my _ enum. hpp”是:

#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

您只需要定义一次所有内容,名称空间不应该被污染,所有的计算只完成一次(其余的只是查找)。但是,您不能获得枚举类的类型安全性(它们仍然只是短整数) ,您不能为枚举赋值,您必须在可以定义名称空间的地方定义枚举(例如,全局)。

我不确定它的性能有多好,或者它是不是一个好主意(我在 C + + 之前学过 C,所以我的大脑仍然是那样工作的)。如果有人知道为什么这是个坏主意,尽管指出来。

这个简单的例子对我很有用,希望对你有所帮助。

#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";
}

现在是2017年,但问题仍然存在

还有一种方式:

#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
#pragma once


#include <string>
#include <vector>
#include <sstream>
#include <algorithm>


namespace StringifyEnum
{
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 std::vector<std::string> SplitEnumArgs(const char* szArgs, int     nMax)
{
std::vector<std::string> enums;
std::stringstream ss(szArgs);
std::string strSub;
int nIdx = 0;
while (ss.good() && (nIdx < nMax)) {
getline(ss, strSub, ',');
enums.push_back(StringifyEnum::TrimEnumString(strSub));
++nIdx;
}
return std::move(enums);
}
}


#define DECLARE_ENUM_SEQ(ename, n, ...) \
enum class ename { __VA_ARGS__ }; \
const int MAX_NUMBER_OF_##ename(n); \
static std::vector<std::string> ename##Strings = StringifyEnum::SplitEnumArgs(#__VA_ARGS__, MAX_NUMBER_OF_##ename); \
inline static std::string ename##ToString(ename e) { \
return ename##Strings.at((int)e); \
} \
inline static ename StringTo##ename(const std::string& en) { \
const auto it = std::find(ename##Strings.begin(), ename##Strings.end(), en); \
if (it != ename##Strings.end()) \
return (ename) std::distance(ename##Strings.begin(), it); \
throw std::runtime_error("Could not resolve string enum value");     \
}

这是一个详细说明的类扩展枚举版本... 它不添加任何其他枚举值以外的提供。

用法: DECLARE _ ENUM _ SEQ (CameraMode,(3) ,Fly,FirstPerson,透视更正)

我需要这个方法来实现双向工作,我经常把枚举嵌入到一个包含类中,所以我用 James McNellis 的方法开始了解决方案,在这些答案的顶部,但是我做出了这个解决方案。另请注意,我更喜欢枚举类,而不仅仅是枚举,这使得答案有些复杂。

#define X_DEFINE_ENUMERATION(r, datatype, elem) case datatype::elem : return BOOST_PP_STRINGIZE(elem);


// The data portion of the FOR_EACH should be (variable type)(value)
#define X_DEFINE_ENUMERATION2(r, dataseq, elem) \
if (BOOST_PP_SEQ_ELEM(1, dataseq) == BOOST_PP_STRINGIZE(elem) ) return BOOST_PP_SEQ_ELEM(0, dataseq)::elem;


#define DEFINE_ENUMERATION_MASTER(modifier, name, toFunctionName, enumerators)    \
enum class name {                                                         \
Undefined,                                                            \
BOOST_PP_SEQ_ENUM(enumerators)                                        \
};                                                                        \
\
modifier const char* ToString(const name & v)                               \
{                                                                         \
switch (v)                                                            \
{                                                                     \
BOOST_PP_SEQ_FOR_EACH(                                            \
X_DEFINE_ENUMERATION,                                         \
name,                                                         \
enumerators                                                   \
)                                                                 \
default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]";         \
}                                                                     \
}                                                                         \
\
modifier const name toFunctionName(const std::string & value)               \
{                                                                         \
BOOST_PP_SEQ_FOR_EACH(                                                \
X_DEFINE_ENUMERATION2,                                            \
(name)(value),                                                    \
enumerators                                                       \
)                                                                     \
return name::Undefined;                                               \
}


#define DEFINE_ENUMERATION(name, toFunctionName, enumerators)                 \
DEFINE_ENUMERATION_MASTER(inline, name, toFunctionName, enumerators)


#define DEFINE_ENUMERATION_INSIDE_CLASS(name, toFunctionName, enumerators)                 \
DEFINE_ENUMERATION_MASTER(static, name, toFunctionName, enumerators)

要在类中使用它,可以这样做:

class ComponentStatus {
public:
/** This is a simple bad, iffy, and good status. See other places for greater details. */
DEFINE_ENUMERATION_INSIDE_CLASS(Status, toStatus, (RED)(YELLOW)(GREEN)
}

我还编写了一个 CppUnit 测试,演示了如何使用它:

void
ComponentStatusTest::testSimple() {
ComponentStatus::Status value = ComponentStatus::Status::RED;


const char * valueStr = ComponentStatus::ToString(value);


ComponentStatus::Status convertedValue = ComponentStatus::toStatus(string(valueStr));


CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}


DEFINE_ENUMERATION(Status, toStatus, (RED)(YELLOW)(GREEN))


void
ComponentStatusTest::testOutside() {
Status value = Status::RED;


const char * valueStr = ToString(value);


Status convertedValue = toStatus(string(valueStr));


CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion to a string.", (const char *)"RED", valueStr);
CPPUNIT_ASSERT_EQUAL_MESSAGE("Incorrect conversion back from a string.", convertedValue, value);
}

您必须选择使用哪个宏,DEFINE _ ENUMERATION 还是 DEFINE _ ENUMERATION _ INSIDE _ Class。您将看到我在定义 Component Status: : Status 时使用了后者,但是在定义 Status 时使用了前者。区别很简单。在类中,to/from 方法前缀为“ static”,如果不在类中,则使用“ inline”。细微的差别,但是必要的。

不幸的是,我不认为有一个干净的方法可以避免这样做:

const char * valueStr = ComponentStatus::ToString(value);

虽然您可以在您的类定义之后手动创建一个内联方法,它只是链接到类方法,类似于:

inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }

我自己的答案是,不使用 ost ——使用我自己的方法而不使用重定义魔术,这个解决方案有一个不能定义特定枚举值的限制。

#pragma once
#include <string>


template <class Enum>
class EnumReflect
{
public:
static const char* getEnums() { return ""; }
};


#define DECLARE_ENUM(name, ...)                                         \
enum name { __VA_ARGS__ };                                          \
template <>                                                         \
class EnumReflect<##name> {                                         \
public:                                                             \
static const char* getEnums() { return #__VA_ARGS__; }          \
};


/*
Basic usage:


Declare enumeration:


DECLARE_ENUM( enumName,


enumValue1,
enumValue2,
enumValue3,


// comment
enumValue4
);


Conversion logic:


From enumeration to string:


printf( EnumToString(enumValue3).c_str() );


From string to enumeration:


enumName value;


if( !StringToEnum("enumValue4", value) )
printf("Conversion failed...");


WARNING: At the moment assigning enum value to specific number is not supported.
*/


//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
const char* enums = EnumReflect<T>::getEnums();
const char *token, *next = enums - 1;
int id = (int)t;


do
{
token = next + 1;
if (*token == ' ') token++;
next = strchr(token, ',');
if (!next) next = token + strlen(token);


if (id == 0)
return std::string(token, next);
id--;
} while (*next != 0);


return std::string();
}


//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
const char* enums = EnumReflect<T>::getEnums();
const char *token, *next = enums - 1;
int id = 0;


do
{
token = next + 1;
if (*token == ' ') token++;
next = strchr(token, ',');
if (!next) next = token + strlen(token);


if (strncmp(token, enumName, next - token) == 0)
{
t = (T)id;
return true;
}


id++;
} while (*next != 0);


return false;
}

你可以在 github 上找到最新版本:

Https://github.com/tapika/cppscriptcore/blob/master/solutionprojectmodel/enumreflect.h

我已经添加了一个不支持枚举值的答案,现在添加了支持枚举值分配的支持。像在以前的解决方案,这一个使用最小定义魔术。

下面是头文件:

#pragma once
#include <string>
#include <map>
#include <regex>


template <class Enum>
class EnumReflect
{
public:
static const char* getEnums() { return ""; }
};


//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
static std::map<std::string, int> enum2int;
static std::map<int, std::string> int2enum;


static void EnsureEnumMapReady( const char* enumsInfo )
{
if (*enumsInfo == 0 || enum2int.size() != 0 )
return;


// Should be called once per each enumeration.
std::string senumsInfo(enumsInfo);
std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
std::smatch sm;
int value = 0;


for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
{
string enumName = sm[1].str();
string enumValue = sm[2].str();


if (enumValue.length() != 0)
value = atoi(enumValue.c_str());


enum2int[enumName] = value;
int2enum[value] = enumName;
}
}
};


template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;


template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;




#define DECLARE_ENUM(name, ...)                                         \
enum name { __VA_ARGS__ };                                          \
template <>                                                         \
class EnumReflect<##name>: public EnumReflectBase<##name> {         \
public:                                                             \
static const char* getEnums() { return #__VA_ARGS__; }          \
};








/*
Basic usage:


Declare enumeration:


DECLARE_ENUM( enumName,


enumValue1,
enumValue2,
enumValue3 = 5,


// comment
enumValue4
);


Conversion logic:


From enumeration to string:


printf( EnumToString(enumValue3).c_str() );


From string to enumeration:


enumName value;


if( !StringToEnum("enumValue4", value) )
printf("Conversion failed...");
*/


//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
auto& int2enum = EnumReflect<T>::int2enum;
auto it = int2enum.find(t);


if (it == int2enum.end())
return "";


return it->second;
}


//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
auto& enum2int = EnumReflect<T>::enum2int;
auto it = enum2int.find(enumName);


if (it == enum2int.end())
return false;


t = (T) it->second;
return true;
}

下面是测试应用程序的例子:

DECLARE_ENUM(TestEnum,
ValueOne,
ValueTwo,
ValueThree = 5,
ValueFour = 7
);


DECLARE_ENUM(TestEnum2,
ValueOne2 = -1,
ValueTwo2,
ValueThree2 = -4,
ValueFour2
);


void main(void)
{
string sName1 = EnumToString(ValueOne);
string sName2 = EnumToString(ValueTwo);
string sName3 = EnumToString(ValueThree);
string sName4 = EnumToString(ValueFour);


TestEnum t1, t2, t3, t4, t5 = ValueOne;
bool b1 = StringToEnum(sName1.c_str(), t1);
bool b2 = StringToEnum(sName2.c_str(), t2);
bool b3 = StringToEnum(sName3.c_str(), t3);
bool b4 = StringToEnum(sName4.c_str(), t4);
bool b5 = StringToEnum("Unknown", t5);


string sName2_1 = EnumToString(ValueOne2);
string sName2_2 = EnumToString(ValueTwo2);
string sName2_3 = EnumToString(ValueThree2);
string sName2_4 = EnumToString(ValueFour2);


TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
bool b2_5 = StringToEnum("Unknown", t2_5);

同一个头文件的更新版本将保存在这里:

Https://github.com/tapika/cppreflect/blob/master/cppreflect/enumreflect.h

对于这个问题还有很多其他的答案,但是我认为更好的方法是使用 C + + 17特性,并使用 Constexpr,这样就可以在编译时完成翻译。这是类型安全的,我们不需要干扰宏。见下文:

//enum.hpp
#include <array>
#include <string_view>


namespace Enum
{


template <class ENUM_TYPE, size_t SIZE>
constexpr ENUM_TYPE findKey(const char * value, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
index = (index == -1) ? map.size() : index;
return
(index == 0) ? throw "Value not in map":
(std::string_view(map[index - 1].second) == value) ? map[index- 1].first:
findKey(value, map, index - 1);
};


template <class ENUM_TYPE, size_t SIZE>
constexpr const char * findValue(ENUM_TYPE key, std::array<std::pair<ENUM_TYPE, const char *>, SIZE> map, size_t index = -1)
{
index = (index == -1) ? map.size() : index;
return
(index == 0) ? throw "Key not in map":
(map[index - 1].first == key) ? map[index- 1].second:
findValue(key, map, index - 1);
};


}


//test_enum.hpp
#include "enum.hpp"


namespace TestEnum
{
enum class Fields
{
Test1,
Test2,
Test3,
//This has to be at the end
NUMBER_OF_FIELDS
};


constexpr std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> GetMap()
{
std::array<std::pair<Fields, const char *>, (size_t)Fields::NUMBER_OF_FIELDS> map =
{
{
{Fields::Test1, "Test1"},
{Fields::Test2, "Test2"},
{Fields::Test3, "Test3"},
}
};
return map;
};


constexpr Fields StringToEnum(const char * value)
{
return Enum::findKey(value, GetMap());
}


constexpr const char * EnumToString(Fields key)
{
return Enum::findValue(key, GetMap());
}


}

然后可以很容易地使用它,以便在编译时检测到字符串键错误:

#include "test_enum.hpp"


int main()
{
auto constexpr a = TestEnum::StringToEnum("Test2"); //a = TestEnum::Fields::Test2
auto constexpr b = TestEnum::EnumToString(TestEnum::Fields::Test1); //b = "Test1"
auto constexpr c = TestEnum::StringToEnum("AnyStringNotInTheMap"); //compile time failure
return 0;
}

这段代码比其他一些解决方案更加详细,但是我们可以很容易地在编译时进行 Enum 到 String 的转换和 String 到 Enum 的转换,并检测类型错误。有了未来 C + + 20的一些特性,这可能会简化得更多一些。

就个人而言,我会选择一些简单的方法,并使用一个操作符来完成。

考虑到下列枚举:

enum WeekDay { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };

我们可以创建一个运算符,将结果输出到 std::ostream中。

std::ostream &operator<<(std::ostream &stream, const WeekDay day) {
switch (day) {
case MONDAY:
stream << "Monday";
break;
case TUESDAY:
stream << "Tuesday";
break;
case WEDNESDAY:
stream << "Wednesday";
break;
case THURSDAY:
stream << "Thursday";
break;
case FRIDAY:
stream << "Friday";
break;
case SATURDAY:
stream << "Saturday";
break;
case SUNDAY:
stream << "Sunday";
break;
}


return stream;
}

与本线程中提供的其他方法相比,样板代码确实非常大。尽管如此,它还是具有相当直观和易于使用的优点。

std::cout << "First day of the week is " << WeekDay::Monday << std::endl;

已经有很多好的答案,但是 Magic _ enum值得一看。

它把自己描述成

现代 C + + 中枚举的静态反射(到字符串,从字符串,迭代) ,不需要任何宏或样板代码就可以使用任何枚举类型。

只有头文件的 C + + 17库为枚举提供了静态反射,使用任何枚举类型都不需要任何宏代码或样板代码。

示例用法

enum Color { RED = 2, BLUE = 4, GREEN = 8 };




Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"


std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name);
if (color.has_value()) {
// color.value() -> Color::GREEN
}

另一个无依赖的基于标准编译器的基于预处理器的解决方案是 给你,它将为给定的枚举生成 Constexpr to_string函数(并且不需要对现有枚举进行任何代码更改)。

用法:

#include "generate_to_string.hpp"


enum class test_enum
{
value1,
value2
};




GENERATE_TO_STRING(test_enum, value1, value2) // emits warning if not all values are listed


const char* foo(test_enum v)
{
return to_string(v);
}

从要点到复制粘贴的完整代码:

// workaround for old msvc preprocessor:
// https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly
#define ETS_EXP(x) x


#define ETS_CASE(e, x)                                                         \
case e::x:                                                                 \
return #x;


#define ETS_FE_0(e)
#define ETS_FE_1(e, x) ETS_CASE(e, x)
#define ETS_FE_2(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_1(e, __VA_ARGS__))
#define ETS_FE_3(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_2(e, __VA_ARGS__))
#define ETS_FE_4(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_3(e, __VA_ARGS__))
#define ETS_FE_5(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_4(e, __VA_ARGS__))
#define ETS_FE_6(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_5(e, __VA_ARGS__))
#define ETS_FE_7(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_6(e, __VA_ARGS__))
#define ETS_FE_8(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_7(e, __VA_ARGS__))
#define ETS_FE_9(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_8(e, __VA_ARGS__))
#define ETS_FE_10(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_9(e, __VA_ARGS__))
#define ETS_FE_11(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_10(e, __VA_ARGS__))
#define ETS_FE_12(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_11(e, __VA_ARGS__))
#define ETS_FE_13(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_12(e, __VA_ARGS__))
#define ETS_FE_14(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_13(e, __VA_ARGS__))
#define ETS_FE_15(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_14(e, __VA_ARGS__))
#define ETS_FE_16(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_15(e, __VA_ARGS__))
#define ETS_FE_17(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_16(e, __VA_ARGS__))
#define ETS_FE_18(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_17(e, __VA_ARGS__))
#define ETS_FE_19(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_18(e, __VA_ARGS__))
#define ETS_FE_20(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_19(e, __VA_ARGS__))
#define ETS_FE_21(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_20(e, __VA_ARGS__))
#define ETS_FE_22(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_21(e, __VA_ARGS__))
#define ETS_FE_23(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_22(e, __VA_ARGS__))
#define ETS_FE_24(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_23(e, __VA_ARGS__))
#define ETS_FE_25(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_24(e, __VA_ARGS__))
#define ETS_FE_26(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_25(e, __VA_ARGS__))
#define ETS_FE_27(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_26(e, __VA_ARGS__))
#define ETS_FE_28(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_27(e, __VA_ARGS__))
#define ETS_FE_29(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_28(e, __VA_ARGS__))
#define ETS_FE_30(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_29(e, __VA_ARGS__))
#define ETS_FE_31(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_30(e, __VA_ARGS__))
#define ETS_FE_32(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_31(e, __VA_ARGS__))
#define ETS_FE_33(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_32(e, __VA_ARGS__))
#define ETS_FE_34(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_33(e, __VA_ARGS__))
#define ETS_FE_35(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_34(e, __VA_ARGS__))
#define ETS_FE_36(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_35(e, __VA_ARGS__))
#define ETS_FE_37(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_36(e, __VA_ARGS__))
#define ETS_FE_38(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_37(e, __VA_ARGS__))
#define ETS_FE_39(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_38(e, __VA_ARGS__))
#define ETS_FE_40(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_39(e, __VA_ARGS__))
#define ETS_FE_41(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_40(e, __VA_ARGS__))
#define ETS_FE_42(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_41(e, __VA_ARGS__))
#define ETS_FE_43(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_42(e, __VA_ARGS__))
#define ETS_FE_44(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_43(e, __VA_ARGS__))
#define ETS_FE_45(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_44(e, __VA_ARGS__))
#define ETS_FE_46(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_45(e, __VA_ARGS__))
#define ETS_FE_47(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_46(e, __VA_ARGS__))
#define ETS_FE_48(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_47(e, __VA_ARGS__))
#define ETS_FE_49(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_48(e, __VA_ARGS__))
#define ETS_FE_50(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_49(e, __VA_ARGS__))
#define ETS_FE_51(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_50(e, __VA_ARGS__))
#define ETS_FE_52(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_51(e, __VA_ARGS__))
#define ETS_FE_53(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_52(e, __VA_ARGS__))
#define ETS_FE_54(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_53(e, __VA_ARGS__))
#define ETS_FE_55(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_54(e, __VA_ARGS__))
#define ETS_FE_56(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_55(e, __VA_ARGS__))
#define ETS_FE_57(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_56(e, __VA_ARGS__))
#define ETS_FE_58(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_57(e, __VA_ARGS__))
#define ETS_FE_59(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_58(e, __VA_ARGS__))
#define ETS_FE_60(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_59(e, __VA_ARGS__))
#define ETS_FE_61(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_60(e, __VA_ARGS__))
#define ETS_FE_62(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_61(e, __VA_ARGS__))
#define ETS_FE_63(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_62(e, __VA_ARGS__))
#define ETS_FE_64(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_63(e, __VA_ARGS__))
#define ETS_FE_65(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_64(e, __VA_ARGS__))
#define ETS_FE_66(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_65(e, __VA_ARGS__))
#define ETS_FE_67(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_66(e, __VA_ARGS__))
#define ETS_FE_68(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_67(e, __VA_ARGS__))
#define ETS_FE_69(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_68(e, __VA_ARGS__))
#define ETS_FE_70(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_69(e, __VA_ARGS__))
#define ETS_FE_71(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_70(e, __VA_ARGS__))
#define ETS_FE_72(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_71(e, __VA_ARGS__))
#define ETS_FE_73(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_72(e, __VA_ARGS__))
#define ETS_FE_74(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_73(e, __VA_ARGS__))
#define ETS_FE_75(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_74(e, __VA_ARGS__))
#define ETS_FE_76(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_75(e, __VA_ARGS__))
#define ETS_FE_77(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_76(e, __VA_ARGS__))
#define ETS_FE_78(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_77(e, __VA_ARGS__))
#define ETS_FE_79(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_78(e, __VA_ARGS__))
#define ETS_FE_80(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_79(e, __VA_ARGS__))
#define ETS_FE_81(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_80(e, __VA_ARGS__))
#define ETS_FE_82(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_81(e, __VA_ARGS__))
#define ETS_FE_83(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_82(e, __VA_ARGS__))
#define ETS_FE_84(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_83(e, __VA_ARGS__))
#define ETS_FE_85(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_84(e, __VA_ARGS__))
#define ETS_FE_86(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_85(e, __VA_ARGS__))
#define ETS_FE_87(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_86(e, __VA_ARGS__))
#define ETS_FE_88(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_87(e, __VA_ARGS__))
#define ETS_FE_89(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_88(e, __VA_ARGS__))
#define ETS_FE_90(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_89(e, __VA_ARGS__))
#define ETS_FE_91(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_90(e, __VA_ARGS__))
#define ETS_FE_92(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_91(e, __VA_ARGS__))
#define ETS_FE_93(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_92(e, __VA_ARGS__))
#define ETS_FE_94(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_93(e, __VA_ARGS__))
#define ETS_FE_95(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_94(e, __VA_ARGS__))
#define ETS_FE_96(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_95(e, __VA_ARGS__))
#define ETS_FE_97(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_96(e, __VA_ARGS__))
#define ETS_FE_98(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_97(e, __VA_ARGS__))
#define ETS_FE_99(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_98(e, __VA_ARGS__))
#define ETS_FE_100(e, x, ...) ETS_CASE(e, x) ETS_EXP(ETS_FE_99(e, __VA_ARGS__))


#define ETS_MATCH_ARGS(                                                        \
ign0, ign1, ign2, ign3, ign4, ign5, ign6, ign7, ign8, ign9, ign10, ign11,  \
ign12, ign13, ign14, ign15, ign16, ign17, ign18, ign19, ign20, ign21,      \
ign22, ign23, ign24, ign25, ign26, ign27, ign28, ign29, ign30, ign31,      \
ign32, ign33, ign34, ign35, ign36, ign37, ign38, ign39, ign40, ign41,      \
ign42, ign43, ign44, ign45, ign46, ign47, ign48, ign49, ign50, ign51,      \
ign52, ign53, ign54, ign55, ign56, ign57, ign58, ign59, ign60, ign61,      \
ign62, ign63, ign64, ign65, ign66, ign67, ign68, ign69, ign70, ign71,      \
ign72, ign73, ign74, ign75, ign76, ign77, ign78, ign79, ign80, ign81,      \
ign82, ign83, ign84, ign85, ign86, ign87, ign88, ign89, ign90, ign91,      \
ign92, ign93, ign94, ign95, ign96, ign97, ign98, ign99, ign100, name, ...) \
name


#define ETS_FOR_EACH(e, ...)                                                   \
ETS_EXP(ETS_MATCH_ARGS(                                                    \
_0, __VA_ARGS__, ETS_FE_100, ETS_FE_99, ETS_FE_98, ETS_FE_97,          \
ETS_FE_96, ETS_FE_95, ETS_FE_94, ETS_FE_93, ETS_FE_92, ETS_FE_91,      \
ETS_FE_90, ETS_FE_89, ETS_FE_88, ETS_FE_87, ETS_FE_86, ETS_FE_85,      \
ETS_FE_84, ETS_FE_83, ETS_FE_82, ETS_FE_81, ETS_FE_80, ETS_FE_79,      \
ETS_FE_78, ETS_FE_77, ETS_FE_76, ETS_FE_75, ETS_FE_74, ETS_FE_73,      \
ETS_FE_72, ETS_FE_71, ETS_FE_70, ETS_FE_69, ETS_FE_68, ETS_FE_67,      \
ETS_FE_66, ETS_FE_65, ETS_FE_64, ETS_FE_63, ETS_FE_62, ETS_FE_61,      \
ETS_FE_60, ETS_FE_59, ETS_FE_58, ETS_FE_57, ETS_FE_56, ETS_FE_55,      \
ETS_FE_54, ETS_FE_53, ETS_FE_52, ETS_FE_51, ETS_FE_50, ETS_FE_49,      \
ETS_FE_48, ETS_FE_47, ETS_FE_46, ETS_FE_45, ETS_FE_44, ETS_FE_43,      \
ETS_FE_42, ETS_FE_41, ETS_FE_40, ETS_FE_39, ETS_FE_38, ETS_FE_37,      \
ETS_FE_36, ETS_FE_35, ETS_FE_34, ETS_FE_33, ETS_FE_32, ETS_FE_31,      \
ETS_FE_30, ETS_FE_29, ETS_FE_28, ETS_FE_27, ETS_FE_26, ETS_FE_25,      \
ETS_FE_24, ETS_FE_23, ETS_FE_22, ETS_FE_21, ETS_FE_20, ETS_FE_19,      \
ETS_FE_18, ETS_FE_17, ETS_FE_16, ETS_FE_15, ETS_FE_14, ETS_FE_13,      \
ETS_FE_12, ETS_FE_11, ETS_FE_10, ETS_FE_9, ETS_FE_8, ETS_FE_7,         \
ETS_FE_6, ETS_FE_5, ETS_FE_4, ETS_FE_3, ETS_FE_2, ETS_FE_1,            \
ETS_FE_0)(e, __VA_ARGS__))


#define GENERATE_TO_STRING(enum_type, ...)                                     \
constexpr const char* to_string(enum_type val)                             \
{                                                                          \
switch (val)                                                           \
{                                                                      \
ETS_FOR_EACH(enum_type, __VA_ARGS__)                               \
}                                                                      \
return "<unknown>";                                                    \
}

我们希望统治一切的 C + + 内置反射解决方案最终能进入 C + + 26(现在我们知道这不会发生在 C + + 23身上)。

在@Reno 的回答的基础上,这里有一个实例,

#include <stdio.h>


//debug macro, keep it defined or undefine
#define DEBUG
//#undef DEBUG


#ifndef DEBUG
#define DECL_ENUM_ELEMENT( element ) element
#define BEGIN_ENUM( ENUM_NAME ) typedef enum
#define END_ENUM( ENUM_NAME )  ENUM_NAME;
#else
#define DECL_ENUM_ELEMENT( element ) #element
#define BEGIN_ENUM( ENUM_NAME ) const char* gs_##ENUM_NAME [] =
#define END_ENUM( ENUM_NAME ); \
int gs_##ENUM_NAME##size = sizeof(gs_##ENUM_NAME)/sizeof(gs_##ENUM_NAME[0]); \
const char* MatchEnumToString##ENUM_NAME(int  index) { \
if (index > (gs_##ENUM_NAME##size - 1) || index < 0) \
{ \
return "ERR: invalid"; \
}  \
else \
return gs_##ENUM_NAME [index]; \
}
#endif


BEGIN_ENUM(Days)
{
DECL_ENUM_ELEMENT(sunday),
DECL_ENUM_ELEMENT(monday),
DECL_ENUM_ELEMENT(tuesday),
DECL_ENUM_ELEMENT(wednesday),
DECL_ENUM_ELEMENT(thursday),
DECL_ENUM_ELEMENT(friday),
DECL_ENUM_ELEMENT(saturday)
}
END_ENUM(Days)


BEGIN_ENUM(fruit)
{
DECL_ENUM_ELEMENT(apple),
DECL_ENUM_ELEMENT(orange),
DECL_ENUM_ELEMENT(mango)
}
END_ENUM(fruit)


void match_etos( int index )
{
#ifdef DEBUG
printf("Day is %s ,", MatchEnumToStringDays(index) );
printf("Fruit is %s\n", MatchEnumToStringfruit(index) );
#else
printf("disabled match_etos, index: %d\n", index);
#endif
}


int main()
{
match_etos(0);
match_etos(1);
match_etos(2);
match_etos(3);
match_etos(4);
match_etos(5);
match_etos(6);
match_etos(-43);
#ifdef DEBUG
printf("gs_Dayssize %d, gs_fruitsize %d\n", gs_Dayssize, gs_fruitsize);
#endif
return 0;
}

用。编译上面的例子

g++ <savedfilename>.cpp
./a.out

定义 DEBUG 时的输出,

Day is sunday ,Fruit is apple
Day is monday ,Fruit is orange
Day is tuesday ,Fruit is mango
Day is wednesday ,Fruit is ERR: invalid
Day is thursday ,Fruit is ERR: invalid
Day is friday ,Fruit is ERR: invalid
Day is saturday ,Fruit is ERR: invalid
Day is ERR: invalid ,Fruit is ERR: invalid
gs_Dayssize 7, gs_fruitsize 3

当没有定义 DEBUG 时,

disabled match_etos, index: 0
disabled match_etos, index: 1
disabled match_etos, index: 2
disabled match_etos, index: 3
disabled match_etos, index: 4
disabled match_etos, index: 5
disabled match_etos, index: 6
disabled match_etos, index: -43

如果定义了 DEBUG 宏,它将创建一个数组,如果未定义的原始枚举将在预处理后创建。