如何使printf显示枚举类型的变量的值?例如:
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
我需要的是
printenum(OS_type, "My OS is %s", myOS);
其中必须显示字符串“ Linux”,而不是整数。
我想,首先我必须创建一个值索引的字符串数组。但是我不知道这是否是最美丽的方式。有可能吗?
如何使printf显示枚举类型的变量的值?例如:
typedef enum {Linux, Apple, Windows} OS_type;
OS_type myOS = Linux;
我需要的是
printenum(OS_type, "My OS is %s", myOS);
其中必须显示字符串“ Linux”,而不是整数。
我想,首先我必须创建一个值索引的字符串数组。但是我不知道这是否是最美丽的方式。有可能吗?
Answers:
确实没有做到这一点的美丽方法。只需设置一个由枚举索引的字符串数组即可。
如果执行大量输出,则可以定义一个操作符<<,该操作符带有一个枚举参数并为您查找。
当然,天真的解决方案是为每个枚举编写一个函数,以执行到字符串的转换:
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]";
}
}
但是,这是维护灾难。借助可以与C和C ++代码一起使用的Boost.Preprocessor库,您可以轻松利用预处理器,并让它为您生成此函数。生成宏如下:
#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 ++特定的语法(例如no typedef enum
)和函数重载,但是将其与C一起使用也很简单。
(Windows)
改为,(Windows, 3)
然后BOOST_PP_SEQ_ENUM
以适当的文字替换BOOST_PP_SEQ_FOR_EACH
。我没有一个方便的例子,但是如果您愿意,我可以写一个例子。
这是预处理器块
#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),
} END_ENUM(Os_type)
致电使用
GetStringOs_type(winblows);
从这里取。多么酷啊 ?:)
我结合了James,Howard和Éder的解决方案,并创建了一个更通用的实现:
完整的代码如下所示(使用“ DEFINE_ENUM_CLASS_WITH_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;
}
这个简单的例子对我有用。希望这可以帮助。
#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";
}
您是否尝试过:
#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()
宏可用于将代码中的任何文本转换为字符串,但只能将括号之间的确切文本转换为字符串。没有变量解引用或宏替换或完成的任何其他形式的事情。
这里有很多很好的答案,但我认为有些人可能会发现我的有用。我喜欢它,因为用于定义宏的接口几乎可以实现。它也很方便,因为您不必包括任何额外的库-所有库都随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
请享用!
假设您的枚举已经定义,则可以创建一个成对的数组:
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 ++中的枚举中获取字符串的最优雅的方法。
bimap
,如果有人想解析名称并将其转换为枚举(例如,从XML文件),则可能要使用Boost 。
这是我的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
.................
}
error: ‘hash’ is not a class template
->#include <functional>
我个人的喜好是尽量减少重复键入和难以理解的宏,并避免将宏定义引入一般的编译器空间。
因此,在头文件中:
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;
}
晚会的另一晚,使用预处理器:
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-macros的众所周知的技术。)
第7行定义了内部宏,以便在第8-11行中填写实际的枚举声明。第12行未定义内部宏(只是为了使编译器警告静音)。
第14行定义了内部宏,以便创建枚举元素名称的字符串版本。然后第15-18行生成一个数组,该数组可以将枚举值转换为相应的字符串。
第21-27行生成将字符串转换为枚举值的函数,或者如果字符串不匹配则返回NULL。
它处理第0个元素的方式有点麻烦。我过去实际上已经解决了这个问题。
我承认这种技术使不想让预处理器本身可以被编程为您编写代码的人感到困扰。我认为它强烈说明了可读性和可维护性之间的区别。代码很难阅读,但是如果枚举有几百个元素,则可以添加,删除或重新排列元素,并且仍然要确保生成的代码没有错误。
#define TEST_1 hello #define TEST_2 world
然后typedef enum { TEST_1, TEST_2 } test_t;
再创建一个使用字符串化宏字符串的查找表:const char* table[]= { STRINGIFY(TEST_1), STRINGIFY(TEST_2), };
已经有多个答案,在类似的解决方案暗示。更具可读性。
这是仅使用C预处理器的Old 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 */
/* A B */
XX( enum_1 , 100)
XX( enum_2 , 100 )
XX( enum_3 , 200 )
XX( enum_4 , 900 )
XX( enum_5 , 500 )
在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;
如果枚举中的值不重复,则工作正常。
将枚举值转换为字符串的示例代码:
enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );
相反的示例代码:
assert( EnumString< FORM >::To( f, str ) );
谢谢詹姆斯的建议。这非常有用,所以我以另一种方式实现了某种贡献。
#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 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的第一个元素。
由于输入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,现在修改James的代码,您将拥有全部功能。
我的实现可能不是最简单的实现,因此,如果您找不到任何简洁的代码,请参阅。
清洁,安全的纯标准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
基本原理
解决核心问题“具有带有相应字符串的枚举常量”时,明智的程序员将提出以下要求:
第一个要求,也许还有第二个要求,可以通过各种凌乱的宏解决方案来满足,例如臭名昭著的“ 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
因为枚举常量实际上是类型int
,而不是test_t
)
这样可以防止类似代码的STRINGIFY(random_stuff)
编译。
#define
,在枚举声明和查找表中添加对该定义的引用。如果在添加这些行时不满意,则该程序将无法编译。我添加到标识符中的数字绝不是强制性的,您也可以编写#define APPLES hello
并#define ORANGES world
跟随其后typedef enum { APPES, ORANGES, TEST_N } test_t;
,依此类推。
我所做的是将我在这里所看到的内容以及该站点上类似问题中的内容组合在一起的。我将其设置为Visual Studio2013。我尚未使用其他编译器对其进行测试。
首先,我定义了一组将完成技巧的宏。
// 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。
我有点迟了,但这是我使用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,所以我的大脑仍然可以这样工作)。如果有人知道为什么这是一个坏主意,请随时指出。
是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,PerspectiveCorrect)
我需要这两个方向的工作,而且我经常将枚举嵌入到一个包含类中,因此我从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。您会看到在定义ComponentStatus :: Status时使用了后者,但是在定义Status时使用了前者。区别很简单。在类内部,我将to / from方法的前缀设置为“ static”,如果不在类中,则使用“ inline”。细微的差异,但有必要。
不幸的是,我认为没有一种避免这样做的干净方法:
const char * valueStr = ComponentStatus::ToString(value);
尽管您可以在类定义之后手动创建一个内联方法,该方法可以简单地链接到该类方法,例如:
inline const char * toString(const ComponentStatus::Status value) { return ComponentStatus::ToString(value); }
我自己的答案,而不是使用boost-使用我自己的方法而没有大量的定义魔术,并且此解决方案的局限性在于无法定义特定的枚举值。
#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
对此还有很多其他答案,但是我认为更好的方法是使用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功能,可以将其简化一些。