有没有一种简单的方法可以将 C++ 枚举转换为字符串?
Posted
技术标签:
【中文标题】有没有一种简单的方法可以将 C++ 枚举转换为字符串?【英文标题】:Is there a simple way to convert C++ enum to string? 【发布时间】:2010-09-17 03:02:12 【问题描述】:假设我们有一些命名的枚举:
enum MyEnum
FOO,
BAR = 0x50
;
我搜索的是一个脚本(任何语言),它扫描我项目中的所有标题并生成一个标题,每个枚举都有一个函数。
char* enum_to_string(MyEnum t);
还有一个类似这样的实现:
char* enum_to_string(MyEnum t)
switch(t)
case FOO:
return "FOO";
case BAR:
return "BAR";
default:
return "INVALID ENUM";
问题在于类型定义的枚举和未命名的 C 风格枚举。有人知道吗?
编辑:解决方案不应该修改我的源代码,除了生成的函数。枚举位于 API 中,因此使用迄今为止提出的解决方案不是一种选择。
【问题讨论】:
关于基于宏的工厂移入***.com/questions/147267/…的答案 - 问题更新后,此处不再相关。 Easy way to use variables of enum types as string in C?的可能重复 【参考方案1】:X 宏是最好的解决方案。示例:
#include <iostream>
enum Colours
# define X(a) a,
# include "colours.def"
# undef X
ColoursCount
;
char const* const colours_str[] =
# define X(a) #a,
# include "colours.def"
# undef X
0
;
std::ostream& operator<<(std::ostream& os, enum Colours c)
if (c >= ColoursCount || c < 0) return os << "???";
return os << colours_str[c];
int main()
std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
colors.def:
X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)
但是,我通常更喜欢以下方法,以便可以稍微调整字符串。
#define X(a, b) a,
#define X(a, b) b,
X(Red, "red")
X(Green, "green")
// etc.
【讨论】:
漂亮,虽然我不喜欢多余的文件 只要确保你的构建过程没有在每个包含文件之前添加#pragma( once )... 我不确定“最佳”解决方案! 这个解决方案比任何基于 switch 案例或数组的解决方案都要好得多,因为它不会重复名称,从而可以轻松更改枚举。 @ikku100 你对#define X(a, b) #b
的看法不正确。仅当定义类似于 X(Red, red)
而不是答案中显示的定义 X(Red, "red")
时才需要这样做【参考方案2】:
您可能想查看GCCXML。
在您的示例代码上运行 GCCXML 会产生:
<GCC_XML>
<Namespace id="_1" name="::" members="_3 " mangled="_Z2::"/>
<Namespace id="_2" name="std" context="_1" members="" mangled="_Z3std"/>
<Enumeration id="_3" name="MyEnum" context="_1" location="f0:1" file="f0" line="1">
<EnumValue name="FOO" init="0"/>
<EnumValue name="BAR" init="80"/>
</Enumeration>
<File id="f0" name="my_enum.h"/>
</GCC_XML>
您可以使用任何您喜欢的语言提取 Enumeration 和 EnumValue 标签并生成您想要的代码。
【讨论】:
太棒了!用一个简单的 python 脚本作为一个魅力。谢谢。 +1,GCCXML 看起来很不错! (虽然我几乎 -1ed,因为我最初将其误读为使用上述详细 XML 语法对您的枚举进行编码的建议——这种解决方案带有过度工程的味道!) 您可以发布 python 脚本的任何更改吗?【参考方案3】:@hydroo:没有额外的文件:
#define SOME_ENUM(DO) \
DO(Foo) \
DO(Bar) \
DO(Baz)
#define MAKE_ENUM(VAR) VAR,
enum MetaSyntacticVariable
SOME_ENUM(MAKE_ENUM)
;
#define MAKE_STRINGS(VAR) #VAR,
const char* const MetaSyntacticVariableNames[] =
SOME_ENUM(MAKE_STRINGS)
;
【讨论】:
我喜欢这个解决方案。不过,如果将 SOME_UNION 和 MAKE_UNION 分别称为 SOME_ENUM 和 MAKE_ENUM 会更清楚。 这是一个很好的解决方案。我拥有我所处理过的最易维护的 C++ 资源管理器。 我必须感谢您提供这个简单的解决方案 :-) - 我确实对其进行了一些修改,通过创建一个方法static const char* getNameByEnum(MetaSyntacticVariable e) /*code to return the static string*/
让 MetaSyntacticVariableNames[]
成为类声明的一部分跨度>
绝妙的答案!我通过将 MAKE_ENUM 和 MAKE_STRINGS 组合到一个宏中进一步简化了它,使整个过程更加简单。如果有人感兴趣,我在这个线程中添加了一个带有该代码的答案。
有人能解释一下这个解决方案发生了什么吗?我还是不明白。例如,如果我在main
函数之外有enum class MyClr R, G, B;
,那么我应该在上面的代码中修改什么,我应该把它放在哪里?我将我的枚举类放在最顶部,然后将SOME_ENUM
s 分别替换为MyClr
和Foo
、Bar
、Baz
和R
、G
、B
。但是,在main
中,当我尝试运行MyClr clr= MyClr::R; cout << MAKE_STRINGS(clr);
时,出现错误:error: expected primary-expression before ‘<<’ token cout << MAKE_STRINGS(clr); ^~
【参考方案4】:
我倾向于创建一个 C 数组,其名称的顺序和位置与枚举值相同。
例如。
enum colours red, green, blue ;
const char *colour_names[] = "red", "green", "blue" ;
然后您可以在需要人类可读值的地方使用该数组,例如
colours mycolour = red;
cout << "the colour is" << colour_names[mycolour];
在某些情况下,您可以尝试使用字符串化运算符(请参阅预处理器参考中的 #),它会执行您想要的操作 - 例如:
#define printword(XX) cout << #XX;
printword(red);
将“红色”打印到标准输出。不幸的是,它不适用于变量(因为您会打印出变量名称)
【讨论】:
最后一个警告(不适用于变量)是一个很大的缺点,但无论如何 +1。 仅当您不为枚举条目设置特殊数值时才有效。【参考方案5】:我有一个非常简单易用的宏,它以完全 DRY 的方式执行此操作。它涉及可变参数宏和一些简单的解析魔法。如下:
#define AWESOME_MAKE_ENUM(name, ...) enum class name __VA_ARGS__, __COUNT; \
inline std::ostream& operator<<(std::ostream& os, name value) \
std::string enumName = #name; \
std::string str = #__VA_ARGS__; \
int len = str.length(); \
std::vector<std::string> strings; \
std::ostringstream temp; \
for(int i = 0; i < len; i ++) \
if(isspace(str[i])) continue; \
else if(str[i] == ',') \
strings.push_back(temp.str()); \
temp.str(std::string());\
\
else temp<< str[i]; \
\
strings.push_back(temp.str()); \
os << enumName << "::" << strings[static_cast<int>(value)]; \
return os;
要在您的代码中使用它,只需执行以下操作:
AWESOME_MAKE_ENUM(Animal,
DOG,
CAT,
HORSE
);
【讨论】:
使用强类型枚举(枚举类)的好主意。这是一个演示:cpp.sh/4ife 这是否适用于外部定义的枚举/符号。例如,操作系统定义或库定义的符号在编号中有间隙? 非常好,但如果放在类中则无法编译(我不知道为什么)。 我无法在 VS2015 中编译它。我收到一个警告和一个错误:警告:多行注释 [-Wcomment] #define MAKE_ENUM(name, ...) enum class name VA_ARGS, __COUNT error: strray '#' in程序 std*: 字符串 enumName = #name 很好,但它似乎只适用于诊断。顺便说一句,适当的原则(如“DRY”):cout << Animal::CAT
仍然应该只打印1
(而不是Animal::CAT
),根据最不意外的原则。 :)【参考方案6】:
我今天刚刚重新发明了这个***,并认为我会分享它。
此实现确实不需要需要对定义常量的代码进行任何更改,这些代码可以是枚举或#define
s 或其他任何转化为整数的东西 - 在我的例子中,我定义了符号在其他符号方面。它也适用于稀疏值。它甚至允许同一个值有多个名称,总是返回第一个。唯一的缺点是它需要您制作一个常量表,例如,当添加新的常量时,它可能会过时。
struct IdAndName
int id;
const char * name;
bool operator<(const IdAndName &rhs) const return id < rhs.id;
;
#define ID_AND_NAME(x) x, #x
const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end)
if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id)
std::stable_sort(table_begin, table_end);
IdAndName searchee = id, NULL ;
IdAndName *p = std::lower_bound(table_begin, table_end, searchee);
return (p == table_end || p->id != id) ? NULL : p->name;
template<int N>
const char * IdToName(int id, IdAndName (&table)[N])
return IdToName(id, &table[0], &table[N]);
如何使用它的示例:
static IdAndName WindowsErrorTable[] =
ID_AND_NAME(INT_MAX), // flag value to indicate unsorted table
ID_AND_NAME(NO_ERROR),
ID_AND_NAME(ERROR_INVALID_FUNCTION),
ID_AND_NAME(ERROR_FILE_NOT_FOUND),
ID_AND_NAME(ERROR_PATH_NOT_FOUND),
ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES),
ID_AND_NAME(ERROR_ACCESS_DENIED),
ID_AND_NAME(ERROR_INVALID_HANDLE),
ID_AND_NAME(ERROR_ARENA_TRASHED),
ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY),
ID_AND_NAME(ERROR_INVALID_BLOCK),
ID_AND_NAME(ERROR_BAD_ENVIRONMENT),
ID_AND_NAME(ERROR_BAD_FORMAT),
ID_AND_NAME(ERROR_INVALID_ACCESS),
ID_AND_NAME(ERROR_INVALID_DATA),
ID_AND_NAME(ERROR_INVALID_DRIVE),
ID_AND_NAME(ERROR_CURRENT_DIRECTORY),
ID_AND_NAME(ERROR_NOT_SAME_DEVICE),
ID_AND_NAME(ERROR_NO_MORE_FILES)
;
const char * error_name = IdToName(GetLastError(), WindowsErrorTable);
IdToName
函数依赖std::lower_bound
进行快速查找,这需要对表进行排序。如果表中的前两个条目乱序,该函数会自动对其进行排序。
编辑:一条评论让我想到了使用相同原理的另一种方式。宏简化了大switch
语句的生成。
#define ID_AND_NAME(x) case x: return #x
const char * WindowsErrorToName(int id)
switch(id)
ID_AND_NAME(ERROR_INVALID_FUNCTION);
ID_AND_NAME(ERROR_FILE_NOT_FOUND);
ID_AND_NAME(ERROR_PATH_NOT_FOUND);
ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES);
ID_AND_NAME(ERROR_ACCESS_DENIED);
ID_AND_NAME(ERROR_INVALID_HANDLE);
ID_AND_NAME(ERROR_ARENA_TRASHED);
ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY);
ID_AND_NAME(ERROR_INVALID_BLOCK);
ID_AND_NAME(ERROR_BAD_ENVIRONMENT);
ID_AND_NAME(ERROR_BAD_FORMAT);
ID_AND_NAME(ERROR_INVALID_ACCESS);
ID_AND_NAME(ERROR_INVALID_DATA);
ID_AND_NAME(ERROR_INVALID_DRIVE);
ID_AND_NAME(ERROR_CURRENT_DIRECTORY);
ID_AND_NAME(ERROR_NOT_SAME_DEVICE);
ID_AND_NAME(ERROR_NO_MORE_FILES);
default: return NULL;
【讨论】:
很好的解决方案。但对我来说,我更喜欢switch and case
,因为它简单易懂。【参考方案7】:
QT 能够提取(感谢元对象编译器):
QNetworkReply::NetworkError error;
error = fetchStuff();
if (error != QNetworkReply::NoError)
QString errorValue;
QMetaObject meta = QNetworkReply::staticMetaObject;
for (int i=0; i < meta.enumeratorCount(); ++i)
QMetaEnum m = meta.enumerator(i);
if (m.name() == QLatin1String("NetworkError"))
errorValue = QLatin1String(m.valueToKey(error));
break;
QMessageBox box(QMessageBox::Information, "Failed to fetch",
"Fetching stuff failed with error '%1`").arg(errorValue),
QMessageBox::Ok);
box.exec();
return 1;
在 Qt 中,每个具有 Q_OBJECT 宏的类都会自动拥有 QMetaObject 类型的静态成员“staticMetaObject”。然后,您可以找到各种很酷的东西,例如属性、信号、插槽和枚举。
Source
【讨论】:
【参考方案8】:这可以在 C++11 中完成
#include <map>
enum MyEnum AA, BB, CC, DD ;
static std::map< MyEnum, const char * > info =
AA, "This is an apple",
BB, "This is a book",
CC, "This is a coffee",
DD, "This is a door"
;
void main()
std::cout << info[AA] << endl
<< info[BB] << endl
<< info[CC] << endl
<< info[DD] << endl;
【讨论】:
这并没有回答 OP 的问题:他正在寻找一种方法来自动生成一个函数以将枚举成员的名称作为字符串返回。【参考方案9】:#define stringify( name ) # name
enum MyEnum
ENUMVAL1
;
...stuff...
stringify(EnumName::ENUMVAL1); // Returns MyEnum::ENUMVAL1
Further discussion on this method
Preprocessor directive tricks for newcomers
【讨论】:
其实这没什么用,因为 stringify 方法是在编译时,而且是非常字面的。如果您说在变量中存在有问题的枚举类型,则尝试对变量进行字符串化只会给您变量名称,而不是枚举类型名称。【参考方案10】:有趣的是查看方式的数量。这是我很久以前使用的一个:
在文件 myenummap.h 中:
#include <map>
#include <string>
enum test one, two, three, five=5, six, seven ;
struct mymap : std::map<unsigned int, std::string>
mymap()
this->operator[]( one ) = "ONE";
this->operator[]( two ) = "TWO";
this->operator[]( three ) = "THREE";
this->operator[]( five ) = "FIVE";
this->operator[]( six ) = "SIX";
this->operator[]( seven ) = "SEVEN";
;
~mymap();
;
在 main.cpp 中
#include "myenummap.h"
...
mymap nummap;
std::cout<< nummap[ one ] << std::endl;
它不是 const,但它很方便。
这是使用 C++11 功能的另一种方式。这是 const,不继承 STL 容器并且更整洁:
#include <vector>
#include <string>
#include <algorithm>
#include <iostream>
//These stay together and must be modified together
enum test one, two, three, five=5, six, seven ;
std::string enum_to_str(test const& e)
typedef std::pair<int,std::string> mapping;
auto m = [](test const& e,std::string const& s)return mapping(static_cast<int>(e),s);;
std::vector<mapping> const nummap =
m(one,"one"),
m(two,"two"),
m(three,"three"),
m(five,"five"),
m(six,"six"),
m(seven,"seven"),
;
for(auto i : nummap)
if(i.first==static_cast<int>(e))
return i.second;
return "";
int main()
// std::cout<< enum_to_str( 46 ) << std::endl; //compilation will fail
std::cout<< "Invalid enum to string : [" << enum_to_str( test(46) ) << "]"<<std::endl; //returns an empty string
std::cout<< "Enumval five to string : ["<< enum_to_str( five ) << "] "<< std::endl; //works
return 0;
【讨论】:
完全合法。我一直这样做。 很好的解决方案。这是 c++,所以使用 stl map 是可以的。【参考方案11】:#include <stdarg.h>
#include <algorithm>
#include <string>
#include <vector>
#include <sstream>
#include <map>
#define SMART_ENUM(EnumName, ...) \
class EnumName \
\
private: \
static std::map<int, std::string> nameMap; \
public: \
enum __VA_ARGS__; \
private: \
static std::map<int, std::string> initMap() \
\
using namespace std; \
\
int val = 0; \
string buf_1, buf_2, str = #__VA_ARGS__; \
replace(str.begin(), str.end(), '=', ' '); \
stringstream stream(str); \
vector<string> strings; \
while (getline(stream, buf_1, ',')) \
strings.push_back(buf_1); \
map<int, string> tmp; \
for(vector<string>::iterator it = strings.begin(); \
it != strings.end(); \
++it) \
\
buf_1.clear(); buf_2.clear(); \
stringstream localStream(*it); \
localStream>> buf_1 >> buf_2; \
if(buf_2.size() > 0) \
val = atoi(buf_2.c_str()); \
tmp[val++] = buf_1; \
\
return tmp; \
\
public: \
static std::string toString(int aInt) \
\
return nameMap[aInt]; \
\
; \
std::map<int, std::string> \
EnumName::nameMap = EnumName::initMap();
用法:
SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
cout<<MyEnum::toString(MyEnum::TWO);
cout<<MyEnum::toString(10);
【讨论】:
我喜欢您的 API,但不幸的是,您的 SmartEnum 实际上并没有创建枚举“类型”。你不能做MyEnum x = MyEnum::TWO;
。我已经发布了我对您课程的编辑以支持这一点。【参考方案12】:
Suma's macro solution 很好。不过,您不需要有两个不同的宏。 C++ 很乐意包含一个标题两次。只需省略包含守卫。
所以你会有一个 foobar.h 定义只是
ENUM(Foo, 1)
ENUM(Bar, 2)
你会像这样包含它:
#define ENUMFACTORY_ARGUMENT "foobar.h"
#include "enumfactory.h"
enumfactory.h 将执行 2 个#include ENUMFACTORY_ARGUMENT
s。第一轮像Suma的DECLARE_ENUM
一样扩展ENUM;在第二轮中,ENUM 的工作方式类似于 DEFINE_ENUM
。
您也可以多次包含 enumfactory.h,只要您为 ENUMFACTORY_ARGUMENT 传入不同的#define
【讨论】:
似乎 suma 移动了答案here。您可能希望在答案中包含该链接。我只是偶然发现了评论,而没有苏马斯回答这个是毫无意义的【参考方案13】:请注意,理想情况下,您的转换函数应该返回一个 const char *。
如果你有能力将你的枚举放在它们单独的头文件中,你也许可以用宏做这样的事情(哦,这会很难看):
#include "enum_def.h"
#include "colour.h"
#include "enum_conv.h"
#include "colour.h"
enum_def.h 所在的位置:
#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) enum NAME
#define ENUM_ADD(NAME, VALUE) NAME = VALUE,
#define ENUM_END ;
而 enum_conv.h 有:
#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) const char *##NAME##_to_string(NAME val) switch (val)
#define ENUM_ADD(NAME, VALUE) case NAME: return #NAME;
#define ENUM_END default: return "Invalid value";
最后,color.h 有:
ENUM_START(colour)
ENUM_ADD(red, 0xff0000)
ENUM_ADD(green, 0x00ff00)
ENUM_ADD(blue, 0x0000ff)
ENUM_END
你可以使用转换函数为:
printf("%s", colour_to_string(colour::red));
这很难看,但它是唯一的方法(在预处理器级别),可以让您在代码中的单个位置定义枚举。因此,由于对枚举的修改,您的代码不容易出错。您的枚举定义和转换函数将始终保持同步。但是,我再说一遍,这很丑:)
【讨论】:
【参考方案14】:另一个答案:在某些情况下,以非代码格式定义枚举是有意义的,例如 CSV、YAML 或 XML 文件,然后从定义。这种方法在您的应用程序中可能实用也可能不实用,但请牢记这一点。
【讨论】:
【参考方案15】:这是对@user3360260 答案的修改。它具有以下新功能
MyEnum fromString(const string&)
支持
使用 VisualStudio 2012 编译
枚举是一个实际的 POD 类型(不仅仅是 const 声明),因此您可以将它分配给一个变量。
添加了 C++“范围”功能(以向量的形式)以允许对枚举进行“foreach”迭代
用法:
SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
MyEnum foo = MyEnum::TWO;
cout << MyEnum::toString(foo); // static method
cout << foo.toString(); // member method
cout << MyEnum::toString(MyEnum::TWO);
cout << MyEnum::toString(10);
MyEnum foo = myEnum::fromString("TWO");
// C++11 iteration over all values
for( auto x : MyEnum::allValues() )
cout << x.toString() << endl;
这是代码
#define SMART_ENUM(EnumName, ...) \
class EnumName \
\
public: \
EnumName() : value(0) \
EnumName(int x) : value(x) \
public: \
enum __VA_ARGS__; \
private: \
static void initMap(std::map<int, std::string>& tmp) \
\
using namespace std; \
\
int val = 0; \
string buf_1, buf_2, str = #__VA_ARGS__; \
replace(str.begin(), str.end(), '=', ' '); \
stringstream stream(str); \
vector<string> strings; \
while (getline(stream, buf_1, ',')) \
strings.push_back(buf_1); \
for(vector<string>::iterator it = strings.begin(); \
it != strings.end(); \
++it) \
\
buf_1.clear(); buf_2.clear(); \
stringstream localStream(*it); \
localStream>> buf_1 >> buf_2; \
if(buf_2.size() > 0) \
val = atoi(buf_2.c_str()); \
tmp[val++] = buf_1; \
\
\
int value; \
public: \
operator int () const return value; \
std::string toString(void) const \
return toString(value); \
\
static std::string toString(int aInt) \
\
return nameMap()[aInt]; \
\
static EnumName fromString(const std::string& s) \
\
auto it = find_if(nameMap().begin(), nameMap().end(), [s](const std::pair<int,std::string>& p) \
return p.second == s; \
); \
if (it == nameMap().end()) \
/*value not found*/ \
throw EnumName::Exception(); \
else \
return EnumName(it->first); \
\
\
class Exception : public std::exception ; \
static std::map<int,std::string>& nameMap() \
static std::map<int,std::string> nameMap0; \
if (nameMap0.size() ==0) initMap(nameMap0); \
return nameMap0; \
\
static std::vector<EnumName> allValues() \
std::vector<EnumName> x __VA_ARGS__ ; \
return x; \
\
bool operator<(const EnumName a) const return (int)*this < (int)a; \
;
请注意,toString 的转换是一种快速的 has 查找,而 fromString 的转换是一种缓慢的线性搜索。但是无论如何字符串都非常昂贵(以及相关的文件 IO),我觉得没有必要优化或使用 bimap。
【讨论】:
你和user3360260有一个很好的解决方案。为什么不使用多图呢?【参考方案16】:这是一个单一文件的解决方案(基于@Marcin 的优雅回答:
#include <iostream>
#define ENUM_TXT \
X(Red) \
X(Green) \
X(Blue) \
X(Cyan) \
X(Yellow) \
X(Magenta) \
enum Colours
# define X(a) a,
ENUM_TXT
# undef X
ColoursCount
;
char const* const colours_str[] =
# define X(a) #a,
ENUM_TXT
# undef X
0
;
std::ostream& operator<<(std::ostream& os, enum Colours c)
if (c >= ColoursCount || c < 0) return os << "???";
return os << colours_str[c] << std::endl;
int main()
std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
【讨论】:
【参考方案17】:我使用宏生成的单独的并排枚举包装类来执行此操作。有几个优点:
可以为我未定义的枚举生成它们(例如:OS 平台标头枚举) 可以将范围检查合并到包装类中 可以使用位域枚举进行“更智能”的格式化当然,缺点是我需要在格式化程序类中复制枚举值,而且我没有任何脚本来生成它们。不过,除此之外,它似乎工作得很好。
这是我的代码库中的枚举示例,没有实现宏和模板的所有框架代码,但您可以理解:
enum EHelpLocation
HELP_LOCATION_UNKNOWN = 0,
HELP_LOCAL_FILE = 1,
HELP_html_ONLINE = 2,
;
class CEnumFormatter_EHelpLocation : public CEnumDefaultFormatter< EHelpLocation >
public:
static inline CString FormatEnum( EHelpLocation eValue )
switch ( eValue )
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCATION_UNKNOWN );
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCAL_FILE );
ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_HTML_ONLINE );
default:
return FormatAsNumber( eValue );
;
DECLARE_RANGE_CHECK_CLASS( EHelpLocation, CRangeInfoSequential< HELP_HTML_ONLINE > );
typedef ESmartEnum< EHelpLocation, HELP_LOCATION_UNKNOWN, CEnumFormatter_EHelpLocation, CRangeInfo_EHelpLocation > SEHelpLocation;
然后的想法是使用 SEHelpLocation 而不是使用 EHelpLocation;一切都一样,但是您可以在枚举变量本身上进行范围检查和“Format()”方法。如果需要格式化独立值,可以使用 CEnumFormatter_EHelpLocation::FormatEnum(...)。
希望这会有所帮助。我意识到这也没有解决关于实际生成另一个类的脚本的原始问题,但我希望该结构可以帮助尝试解决相同问题或编写这样的脚本的人。
【讨论】:
【参考方案18】:这是未发布的软件,但 Frank Laub 的 BOOST_ENUM 似乎符合要求。我喜欢它的部分是你可以在一个类的范围内定义一个枚举,大多数基于宏的枚举通常不允许你这样做。它位于 Boost Vault 中:http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=& 它自 2006 年以来没有任何发展,所以我不知道它与新的 Boost 版本的编译效果如何。 查看 libs/test 下的使用示例。
【讨论】:
【参考方案19】:这是我使用 BOOST 的解决方案:
#include <boost/preprocessor.hpp>
#define X_STR_ENUM_TOSTRING_CASE(r, data, elem) \
case elem : return BOOST_PP_STRINGIZE(elem);
#define X_ENUM_STR_TOENUM_IF(r, data, elem) \
else if(data == BOOST_PP_STRINGIZE(elem)) return elem;
#define STR_ENUM(name, enumerators) \
enum name \
BOOST_PP_SEQ_ENUM(enumerators) \
; \
\
inline const QString enumToStr(name v) \
\
switch (v) \
\
BOOST_PP_SEQ_FOR_EACH( \
X_STR_ENUM_TOSTRING_CASE, \
name, \
enumerators \
) \
\
default: \
return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \
\
\
\
template <typename T> \
inline const T strToEnum(QString v); \
\
template <> \
inline const name strToEnum(QString v) \
\
if(v=="") \
throw std::runtime_error("Empty enum value"); \
\
BOOST_PP_SEQ_FOR_EACH( \
X_ENUM_STR_TOENUM_IF, \
v, \
enumerators \
) \
\
else \
throw std::runtime_error( \
QString("[Unknown value %1 for enum %2]") \
.arg(v) \
.arg(BOOST_PP_STRINGIZE(name)) \
.toStdString().c_str()); \
要创建枚举,请声明:
STR_ENUM
(
SERVICE_RELOAD,
(reload_log)
(reload_settings)
(reload_qxml_server)
)
对于转化:
SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
QString serviceReloadStr = enumToStr(reload_log);
【讨论】:
【参考方案20】:我想发布这个以防有人发现它有用。
就我而言,我只需要从单个 .hpp
文件中为单个 C++11 枚举生成 ToString()
和 FromString()
函数。
我编写了一个 python 脚本来解析包含枚举项的头文件并在一个新的.cpp
文件中生成函数。
您可以使用 execute_process 在 CMakeLists.txt 中添加此脚本,或作为 Visual Studio 中的预构建事件。 .cpp
文件会自动生成,无需每次添加新枚举项时手动更新。
generate_enum_strings.py
# This script is used to generate strings from C++ enums
import re
import sys
import os
fileName = sys.argv[1]
enumName = os.path.basename(os.path.splitext(fileName)[0])
with open(fileName, 'r') as f:
content = f.read().replace('\n', '')
searchResult = re.search('enum(.*)\(.*?)\;', content)
tokens = searchResult.group(2)
tokens = tokens.split(',')
tokens = map(str.strip, tokens)
tokens = map(lambda token: re.search('([a-zA-Z0-9_]*)', token).group(1), tokens)
textOut = ''
textOut += '\n#include "' + enumName + '.hpp"\n\n'
textOut += 'namespace myns\n'
textOut += '\n'
textOut += ' std::string ToString(ErrorCode errorCode)\n'
textOut += ' \n'
textOut += ' switch (errorCode)\n'
textOut += ' \n'
for token in tokens:
textOut += ' case ' + enumName + '::' + token + ':\n'
textOut += ' return "' + token + '";\n'
textOut += ' default:\n'
textOut += ' return "Last";\n'
textOut += ' \n'
textOut += ' \n'
textOut += '\n'
textOut += ' ' + enumName + ' FromString(const std::string &errorCode)\n'
textOut += ' \n'
textOut += ' if ("' + tokens[0] + '" == errorCode)\n'
textOut += ' \n'
textOut += ' return ' + enumName + '::' + tokens[0] + ';\n'
textOut += ' \n'
for token in tokens[1:]:
textOut += ' else if("' + token + '" == errorCode)\n'
textOut += ' \n'
textOut += ' return ' + enumName + '::' + token + ';\n'
textOut += ' \n'
textOut += '\n'
textOut += ' return ' + enumName + '::Last;\n'
textOut += ' \n'
textOut += '\n'
fileOut = open(enumName + '.cpp', 'w')
fileOut.write(textOut)
例子:
ErrorCode.hpp
#pragma once
#include <string>
#include <cstdint>
namespace myns
enum class ErrorCode : uint32_t
OK = 0,
OutOfSpace,
ConnectionFailure,
InvalidJson,
DatabaseFailure,
HttpError,
FileSystemError,
FailedToEncrypt,
FailedToDecrypt,
EndOfFile,
FailedToOpenFileForRead,
FailedToOpenFileForWrite,
FailedToLaunchProcess,
Last
;
std::string ToString(ErrorCode errorCode);
ErrorCode FromString(const std::string &errorCode);
运行python generate_enum_strings.py ErrorCode.hpp
结果:
ErrorCode.cpp
#include "ErrorCode.hpp"
namespace myns
std::string ToString(ErrorCode errorCode)
switch (errorCode)
case ErrorCode::OK:
return "OK";
case ErrorCode::OutOfSpace:
return "OutOfSpace";
case ErrorCode::ConnectionFailure:
return "ConnectionFailure";
case ErrorCode::InvalidJson:
return "InvalidJson";
case ErrorCode::DatabaseFailure:
return "DatabaseFailure";
case ErrorCode::HttpError:
return "HttpError";
case ErrorCode::FileSystemError:
return "FileSystemError";
case ErrorCode::FailedToEncrypt:
return "FailedToEncrypt";
case ErrorCode::FailedToDecrypt:
return "FailedToDecrypt";
case ErrorCode::EndOfFile:
return "EndOfFile";
case ErrorCode::FailedToOpenFileForRead:
return "FailedToOpenFileForRead";
case ErrorCode::FailedToOpenFileForWrite:
return "FailedToOpenFileForWrite";
case ErrorCode::FailedToLaunchProcess:
return "FailedToLaunchProcess";
case ErrorCode::Last:
return "Last";
default:
return "Last";
ErrorCode FromString(const std::string &errorCode)
if ("OK" == errorCode)
return ErrorCode::OK;
else if("OutOfSpace" == errorCode)
return ErrorCode::OutOfSpace;
else if("ConnectionFailure" == errorCode)
return ErrorCode::ConnectionFailure;
else if("InvalidJson" == errorCode)
return ErrorCode::InvalidJson;
else if("DatabaseFailure" == errorCode)
return ErrorCode::DatabaseFailure;
else if("HttpError" == errorCode)
return ErrorCode::HttpError;
else if("FileSystemError" == errorCode)
return ErrorCode::FileSystemError;
else if("FailedToEncrypt" == errorCode)
return ErrorCode::FailedToEncrypt;
else if("FailedToDecrypt" == errorCode)
return ErrorCode::FailedToDecrypt;
else if("EndOfFile" == errorCode)
return ErrorCode::EndOfFile;
else if("FailedToOpenFileForRead" == errorCode)
return ErrorCode::FailedToOpenFileForRead;
else if("FailedToOpenFileForWrite" == errorCode)
return ErrorCode::FailedToOpenFileForWrite;
else if("FailedToLaunchProcess" == errorCode)
return ErrorCode::FailedToLaunchProcess;
else if("Last" == errorCode)
return ErrorCode::Last;
return ErrorCode::Last;
【讨论】:
这是一个在线生成器:th-thielemann.de/tools/cpp-enum-to-string.html【参考方案21】:使Jasper Bekkers' fantastic answer 的使用更加简单:
设置一次:
#define MAKE_ENUM(VAR) VAR,
#define MAKE_STRINGS(VAR) #VAR,
#define MAKE_ENUM_AND_STRINGS(source, enumName, enumStringName) \
enum enumName \
source(MAKE_ENUM) \
;\
const char* const enumStringName[] = \
source(MAKE_STRINGS) \
;
那么,为了使用:
#define SOME_ENUM(DO) \
DO(Foo) \
DO(Bar) \
DO(Baz)
...
MAKE_ENUM_AND_STRINGS(SOME_ENUM, someEnum, someEnumNames)
【讨论】:
【参考方案22】:您可以使用反射库,例如 Ponder。您注册枚举,然后您可以使用 API 来回转换它们。
enum class MyEnum
Zero = 0,
One = 1,
Two = 2
;
ponder::Enum::declare<MyEnum>()
.value("Zero", MyEnum::Zero)
.value("One", MyEnum::One)
.value("Two", MyEnum::Two);
ponder::EnumObject zero(MyEnum::Zero);
zero.name(); // -> "Zero"
【讨论】:
【参考方案23】:答案 0 的一个问题是枚举二进制值不一定从 0 开始,也不一定是连续的。
当我需要这个时,我通常:
将枚举定义拉入我的源代码 编辑它以仅获取名称 做一个宏把名字改成问题中的case子句,虽然通常在一行:case foo: return "foo"; 添加开关、默认等语法使其合法【讨论】:
【参考方案24】:以下 ruby 脚本尝试解析标头并在原始标头旁边构建所需的源。
#! /usr/bin/env ruby
# Let's "parse" the headers
# Note that using a regular expression is rather fragile
# and may break on some inputs
GLOBS = [
"toto/*.h",
"tutu/*.h",
"tutu/*.hxx"
]
enums =
GLOBS.each |glob|
Dir[glob].each |header|
enums[header] = File.open(header, 'rb') |f|
f.read
.scan(/enum\s+(\w+)\s+\\s*([^]+?)\s*\/m).collect |enum_name, enum_key_and_values|
[
enum_name, enum_key_and_values.split(/\s*,\s*/).collect |enum_key_and_value|
enum_key_and_value.split(/\s*=\s*/).first
]
# Now we build a .h and .cpp alongside the parsed headers
# using the template engine provided with ruby
require 'erb'
template_h = ERB.new <<-EOS
#ifndef <%= enum_name %>_to_string_h_
#define <%= enum_name %>_to_string_h_ 1
#include "<%= header %>"
char* enum_to_string(<%= enum_name %> e);
#endif
EOS
template_cpp = ERB.new <<-EOS
#include "<%= enum_name %>_to_string.h"
char* enum_to_string(<%= enum_name %> e)
switch (e)
<% enum_keys.each do |enum_key| %>
case <%= enum_key %>: return "<%= enum_key %>";<% end %>
default: return "INVALID <%= enum_name %> VALUE";
EOS
enums.each |header, enum_name_and_keys|
enum_name_and_keys.each |enum_name, enum_keys|
File.open("#File.dirname(header)/#enum_name_to_string.h", 'wb') |built_h|
built_h.write(template_h.result(binding))
File.open("#File.dirname(header)/#enum_name_to_string.cpp", 'wb') |built_cpp|
built_cpp.write(template_cpp.result(binding))
使用正则表达式会使这个“解析器”非常脆弱,它可能无法优雅地处理您的特定标头。
假设您有一个头文件 toto/a.h,其中包含枚举 MyEnum 和 MyEnum2 的定义。该脚本将构建:
toto/MyEnum_to_string.h
toto/MyEnum_to_string.cpp
toto/MyEnum2_to_string.h
toto/MyEnum2_to_string.cpp
更强大的解决方案是:
从其他来源构建定义枚举及其操作的所有来源。这意味着您将在 XML/YML/任何比 C/C++ 更容易解析的文件中定义枚举。 使用 Avdi 建议的真实编译器。 使用带或不带模板的预处理器宏。【讨论】:
【参考方案25】:这几乎是唯一可以完成的方法(字符串数组也可以工作)。
问题是,一旦 C 程序被编译,枚举的二进制值就全部用完了,名字就没有了。
【讨论】:
【参考方案26】:这是我编写的一个 CLI 程序,用于轻松将枚举转换为字符串。 它易于使用,大约需要 5 秒才能完成(包括 cd 到包含程序的目录,然后运行它,将包含枚举的文件传递给它的时间)。
在此处下载: http://www.mediafire.com/?nttignoozzz
关于它的讨论主题在这里: http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html
使用“--help”参数运行程序以获得如何使用它的描述。
【讨论】:
您能否将它放在某个存储库(github、google 代码或 bitbucket)上并在此处发布链接,而不是 mediafire?我会帮助想要理解它的人:)【参考方案27】:不久前,我做了一些技巧,让枚举在 QComboBox 中正确显示,并将枚举和字符串表示形式定义为一个语句
#pragma once
#include <boost/unordered_map.hpp>
namespace enumeration
struct enumerator_base : boost::noncopyable
typedef
boost::unordered_map<int, std::wstring>
kv_storage_t;
typedef
kv_storage_t::value_type
kv_type;
kv_storage_t const & kv() const
return storage_;
LPCWSTR name(int i) const
kv_storage_t::const_iterator it = storage_.find(i);
if(it != storage_.end())
return it->second.c_str();
return L"empty";
protected:
kv_storage_t storage_;
;
template<class T>
struct enumerator;
template<class D>
struct enum_singleton : enumerator_base
static enumerator_base const & instance()
static D inst;
return inst;
;
#define QENUM_ENTRY(K, V, N) K, N storage_.insert(std::make_pair((int)K, V));
#define QBEGIN_ENUM(NAME, C) \
enum NAME \
\
C \
\
; \
\
#define QEND_ENUM(NAME) \
; \
namespace enumeration \
\
template<> \
struct enumerator<NAME>\
: enum_singleton< enumerator<NAME> >\
\
enumerator() \
//usage
/*
QBEGIN_ENUM(test_t,
QENUM_ENTRY(test_entry_1, L"number uno",
QENUM_ENTRY(test_entry_2, L"number dos",
QENUM_ENTRY(test_entry_3, L"number tres",
QEND_ENUM(test_t)))))
*/
现在enumeration::enum_singleton<your_enum>::instance()
能够将枚举转换为字符串。如果您将kv_storage_t
替换为boost::bimap
,您还可以进行反向转换。
引入converter的通用基类将其存储在Qt对象中,因为Qt对象不能是模板
Previous appearance
【讨论】:
【参考方案28】:作为变体,使用简单的库 > http://codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C
在代码中
#include <EnumString.h>
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 ) );
【讨论】:
【参考方案29】:这里尝试仅使用一行宏命令自动获取枚举上的 > 流运算符...
定义:
#include <string>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>
#define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__)
#define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__)
#define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__)
#define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__)
#define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__)
#define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__)
#define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__)
#define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__)
#define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__)
#define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__)
#define MAKE_STRING10_(str) #str
#define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__)
#define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__)
#define MAKE_ENUM_(attribute, name, ...) name __VA_ARGS__ ; \
attribute std::istream& operator>>(std::istream& is, name& e) \
const char* name##Str[] = MAKE_STRING(__VA_ARGS__) ; \
std::string str; \
std::istream& r = is >> str; \
const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \
const std::vector<std::string> enumStr(name##Str, name##Str + len); \
const std::vector<std::string>::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \
if (it != enumStr.end())\
e = name(it - enumStr.begin()); \
else \
throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \
return r; \
; \
attribute std::ostream& operator<<(std::ostream& os, const name& e) \
const char* name##Str[] = MAKE_STRING(__VA_ARGS__) ; \
return (os << name##Str[e]); \
用法:
// Declare global enum
enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43);
class Essai
public:
// Declare enum inside class
enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4);
;
int main()
std::cout << Essai::Item1 << std::endl;
Essai::Test ddd = Essai::Item1;
std::cout << ddd << std::endl;
std::istringstream strm("Item2");
strm >> ddd;
std::cout << (int) ddd << std::endl;
std::cout << ddd << std::endl;
虽然不确定此方案的局限性...欢迎使用 cmets!
【讨论】:
【参考方案30】:#include <iostream>
#include <map>
#define IDMAP(x) (x,#x)
std::map<int , std::string> enToStr;
class mapEnumtoString
public:
mapEnumtoString()
mapEnumtoString& operator()(int i,std::string str)
enToStr[i] = str;
return *this;
public:
std::string operator [] (int i)
return enToStr[i];
;
mapEnumtoString k;
mapEnumtoString& init()
return k;
int main()
init()
IDMAP(1)
IDMAP(2)
IDMAP(3)
IDMAP(4)
IDMAP(5);
std::cout<<enToStr[1];
std::cout<<enToStr[2];
std::cout<<enToStr[3];
std::cout<<enToStr[4];
std::cout<<enToStr[5];
【讨论】:
请解释为什么这是答案。以上是关于有没有一种简单的方法可以将 C++ 枚举转换为字符串?的主要内容,如果未能解决你的问题,请参考以下文章
C++中如何将一个字符串(string类型的)映射(转换)到枚举值(enum)
如何使用 MongoDB C++ 驱动程序将字符串转换为 BSON?
有没有一种简单的方法可以将 MySQL 数据转换为 Title Case?