有没有一种简单的方法把 C + + 枚举转换成字符串?

假设我们有一些命名的枚举:

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 中的,因此直到现在都不能使用提议的解决方案。

147535 次浏览

这几乎是唯一可以做到的方法(字符串数组也可以)。

问题是,一旦编译了一个 C 程序,枚举的二进制值就是所有使用的,而且名字也不见了。

我倾向于创建一个 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);

将打印“红色”到标准输出。不幸的是,它不适用于变量(因为您将得到变量名打印出来)

答案0的一个问题是枚举二进制值不一定从0开始,也不一定是连续的。

当我需要这个的时候,我通常:

  • 将枚举定义放入我的源代码中
  • 编辑一下就能知道名字了
  • 执行一个宏将名称更改为问题中的 case 子句,但通常只有一行: case foo: 返回“ foo”;
  • 添加开关,默认和其他语法,使其合法

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

颜色:

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.

Suma 的宏解决方案 很不错。不过,您不需要有两个不同的宏。C + + 会很高兴地包含一个头两次。只是忽略了包含警卫。

所以你有一个 foobar。 h 定义公正

ENUM(Foo, 1)
ENUM(Bar, 2)

你可以这样写:

#define ENUMFACTORY_ARGUMENT "foobar.h"
#include "enumfactory.h"

H 将执行2个 #include ENUMFACTORY_ARGUMENT。在第一轮中,它像 Suma 的 DECLARE_ENUM那样展开 ENUM; 在第二轮中,ENUM 像 DEFINE_ENUM那样工作。

只要为 ENUMFACTORY _ ARGUMENT 传入不同的 # Definition’s,也可以多次包含枚举工厂 h

你可能想看看 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>

您可以使用任何您喜欢的语言来提取枚举和枚举值标记并生成所需的代码。

注意,理想情况下,转换函数应该返回一个 康斯特字符 * 。

如果你可以把你的枚举放在不同的头文件中,你也许可以用宏来做类似的事情(哦,这会很难看) :

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

这很难看,但是这是(在预处理器级别)让您仅在代码中的一个位置定义枚举的唯一方法。因此,由于对枚举的修改,您的代码不容易出错。枚举定义和转换函数将始终保持同步。然而,我重复一遍,这是丑陋的:)

下面的 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,其中包含 enums MyEnum 和 MyEnum2的定义:

toto/MyEnum_to_string.h
toto/MyEnum_to_string.cpp
toto/MyEnum2_to_string.h
toto/MyEnum2_to_string.cpp

更有力的解决办法是:

  • 从另一个源构建定义枚举及其操作的所有源。这意味着您将在 XML/YML/whatever 文件中定义枚举,这比 C/C + + 更容易解析。
  • 使用 Avdi 建议的真正的编译器。
  • 使用带或不带模板的预处理宏。

另一个答案是: 在某些上下文中,以非代码格式(如 CSV、 YAML 或 XML 文件)定义枚举是有意义的,然后根据定义生成 C + + 枚举代码和 to-string 代码。这种方法在您的应用程序中可能实用,也可能不实用,但是请记住这一点。

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”。然后您可以找到各种很酷的东西,比如属性、信号、槽和枚举。

来源

我通过使用宏生成的单独并行枚举包装器类来实现这一点,这样做有几个好处:

  • 可以为我没有定义的枚举生成它们(例如: 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;

这个想法就是不使用 EHelpLocation,而是使用 SEHelpLocation; 所有操作都是一样的,但是在枚举变量本身上会得到范围检查和一个‘ Format ()’方法。如果需要格式化独立值,可以使用 CEnumFormatter _ EHelpLocation: : FormatEnum (...)。

希望这对你有帮助。我意识到这也没有解决最初的问题,即一个脚本实际上生成另一个类,但我希望这个结构可以帮助人们解决同样的问题,或者编写这样的脚本。

@ droo: 没有额外的文件:

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

看到这么多方法真有意思,这是我很久以前用过的一个方法:

在文件 myenmap.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;

不是常数,而是方便。

下面是另一种使用 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;
}

这是一款尚未发布的软件,但似乎弗兰克 · 劳布的 BOOST _ ENUM 可以满足这个要求。我喜欢它的地方在于,您可以在类的范围内定义一个枚举,而大多数基于宏的枚举通常不允许您这样做。它位于 Boost Vault,网址是: http://www.boostpro.com/archive/index.php? action = downloadfile & amp; filename = enum _ rev4.6.zip & amp; 自2006年以来,它没有任何发展,所以我不知道它在新的 Boost 版本中编译得如何。 在 libs/test 下查找一个用法示例。

下面是我编写的一个 CLI 程序,它可以轻松地将枚举转换为字符串。 它很容易使用,大约需要5秒钟才能完成(包括 cd 到包含程序的目录,然后运行它,将包含枚举的文件传递给它的时间)。

在此下载: Http://www.mediafire.com/?nttignoozzz

讨论话题: Http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html

使用“—— help”参数运行该程序,以获得如何使用该程序的说明。

我今天刚重新发明了这个轮子,想和你分享一下。

这个实现需要对定义常量的代码进行任何更改,常量可以是枚举或 #define,也可以是其他任何转换为整数的代码——在我的例子中,我用其他符号定义了符号。它还可以很好地处理稀疏值。它甚至允许同一值有多个名称,总是返回第一个名称。唯一的缺点是,它需要您创建一个常量表,例如,当添加新的常量时,常量表可能会变得过时。

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;
}
}
#define stringify( name ) # name


enum MyEnum {
ENUMVAL1
};
...stuff...


stringify(EnumName::ENUMVAL1);  // Returns MyEnum::ENUMVAL1

对该方法的进一步讨论

新手的预处理器指令技巧

不久前,我做了一些技巧,以便在 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()能够将枚举转换为字符串。如果用 boost::bimap替换 kv_storage_t,也可以进行向后转换。 由于 Qt 对象不能作为模板,引入了转换器的公共基类来存储 Qt 对象

上次出现

作为变体,使用简单的 lib > 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 ) );
#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);

这里尝试使用单行宏命令自动获取 enum 上的 < < and > > 流操作符..。

定义:

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

虽然不确定这个方案的局限性... 欢迎提出意见!

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

我有一个难以置信的简单使用宏,这样做在一个完全干的时尚。它包括变量宏和一些简单的解析魔法。开始了:

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

这是对@user3360260的修改

  • MyEnum fromString(const string&)支持
  • 使用 VisualStudio2012编译
  • 枚举是一个实际的 POD 类型(而不仅仅是常量声明) ,因此您可以将它赋值给一个变量。
  • 增加了 C + + “ range”特性(以向量的形式) ,允许在枚举上进行“ 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 是一个 fast has 查找,而转换 from String 是一个缓慢的线性搜索。但是字符串是如此昂贵(以及相关的文件 IO) ,我觉得没有必要优化或使用 bimap。

这可以在 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;
}

这里有一个一文件的解决方案(基于@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;
}

这就是我对 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);

我想把这个发出去,以防有人觉得有用。

在我的示例中,我只需要从单个 .hpp文件为单个 C + + 11枚举生成 ToString()FromString()函数。

我编写了一个 python 脚本,解析包含枚举项的头文件,并在一个新的 .cpp文件中生成函数。

您可以使用 Execute _ process 执行 _ 进程在 CMakeLists.txt 中添加此脚本,或者将其作为 Visual Studio 中的预生成事件。.cpp文件将自动生成,无需在每次添加新枚举项时手动更新它。

Create _ enum _ string. 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)

例如:

错误代码

#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

结果:

错误代码

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

还有一个选择。典型的用例是,您需要 HTTP 谓词的常量以及使用字符串版本值。

例如:

int main () {


VERB a = VERB::GET;
VERB b = VERB::GET;
VERB c = VERB::POST;
VERB d = VERB::PUT;
VERB e = VERB::DELETE;




std::cout << a.toString() << std::endl;


std::cout << a << std::endl;


if ( a == VERB::GET ) {
std::cout << "yes" << std::endl;
}


if ( a == b ) {
std::cout << "yes" << std::endl;
}


if ( a != c ) {
std::cout << "no" << std::endl;
}


}

动词类:

// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {


private:


// private constants
enum Verb {GET_=0, POST_, PUT_, DELETE_};


// private string values
static const std::string theStrings[];


// private value
const Verb value;
const std::string text;


// private constructor
VERB (Verb v) :
value(v), text (theStrings[v])
{
// std::cout << " constructor \n";
}


public:


operator const char * ()  const { return text.c_str(); }


operator const std::string ()  const { return text; }


const std::string toString () const { return text; }


bool operator == (const VERB & other) const { return (*this).value == other.value; }


bool operator != (const VERB & other) const { return ! ( (*this) == other); }


// ---


static const VERB GET;
static const VERB POST;
static const VERB PUT;
static const VERB DELETE;


};


const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};


const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file

对于只有少量元素(一行程序)的枚举,使用复合三元语句可能会比较优雅。表达式的长度也只随着元素数量的增加而近似线性增长。

下面是一个很好的用例:

enum log_level {INFO, WARNING, ERROR};
...
void logger::write(const std::string log, const log_level l) {
...
std::string s = (l == INFO) ? "INFO" :
(l == WARNING) ? "WARNING" :
(l == ERROR) ? "ERROR" : "UNKNOWN";
...
}
...

当然,它只是另一个 switch/if 语句块,但它是一个单行语句。作为一个简洁与简单的问题,它在中间的某个地方相遇。作为一个常量表达式,它也可以很容易地转换成一个内联函数。

我遇到这个问题时,我正在寻找一个解决方案,我自己的问题打印的“单词”的枚举在 C + + 。我回来是为了提供一个简单的解决方案,按照措辞回答提出的问题。所需的全部操作就是用一个向量“镜像”枚举列表。

enum class genre { Fiction, NonFiction, Periodical, Biography, Children };


vector<string>genre_tbl { "Fiction", "NonFiction", "Periodical", "Biography", "Children" };

因为在默认情况下,上面输入的枚举将执行以下操作;

Fiction = 0
NonFiction = 1
Periodical = 2
Biography = 3
Children = 4

这与使枚举到字符串转换非常直接的向量位置相匹配。

string s1 = genre_tbl[int(genre::fiction)];

对于我的问题,我创建了一个名为 Book 的用户定义类,其中一个成员名为 Gen,类型风格。这个程序需要能够打印文字的类型。

class book {...};
ostream& operator<<(ostream& os, genre g) { return os << genre_tbl[int(g)]; }


book b1;
b1.Gen = genre(0)
cout << b1.Gen;

在这种情况下,“小说”将打印到屏幕上。

Jasper Bekkers 的绝妙回答增加更简单的使用方法:

设置一次:

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

您可以使用反射库,如 沉思。您可以注册枚举,然后可以使用 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"

这个问题是,

然而,在所有的问题中,我都找不到好的答案。

在深入研究这个主题之后,我发现了两个很棒的开源解决方案:

(咒语)

  • C + + 11/14/17 独立智能枚举库。它支持 C + + 中智能枚举类所能提供的所有标准功能。
  • 限制: 至少需要 C + + 11。

更好的 Enums

  • 具有清晰语法 的反射编译时枚举库,位于单个头文件中,没有依赖关系。
  • 限制: 基于宏,不能在类中使用。

注意: 我在这里重复这个建议。这个问题有很多流量/视图,确实需要列出上面的解决方案。

这里有点晚了,但是我真的很喜欢这个模式,因为它可以避免拷贝意大利面的错误,并且如果枚举没有映射到一个字符串,它将完全无法编译。它还有非常 constexpr友好的优势,所以它的内联非常好。它也不需要中间类、 switch 语句或运行时值。

// Create a mapping between the enum value and the string
#define MY_ENUM_LIST(DECLARE) \
DECLARE(foo, "This is a foo!") \
DECLARE(bar, "This is a bar!") \
DECLARE(bam, "This is a bam!")


// Define the enum officially
enum class MyEnum {
#define ENUM_ENTRY(NAME, TEXT) NAME, // TEXT expressly not used here
MY_ENUM_LIST(ENUM_ENTRY)
#undef ENUM_ENTRY // Always undef as a good citizen ;)
};


// Create a template function that would fail to compile if called
template <MyEnum KEY> constexpr const char* MyEnumText() {}


// Specialize that bad function with versions that map the enum value to the string declared above
#define ENUM_FUNC(NAME, TEXT) template <> constexpr const char* MyEnumText<MyEnum::NAME>() { return TEXT; }
MY_ENUM_LIST(ENUM_FUNC)
#undef ENUM_FUNC

你使用它的方式很直接。如果您总是在需要字符串的站点硬编码枚举值,那么只需调用 MyEnumText的专用版本:

const auto text{::MyEnumText<MyEnum::foo>()}; // inlines beautifully

如果需要处理动态枚举值,可以添加以下帮助器:

constexpr const char* MyEnumText(MyEnum key) {
switch (key) {
#define ENUM_CASE(NAME, TEXT) case MyEnum::NAME: return MyEnumText<MyEnum::NAME>();
MY_ENUM_LIST(ENUM_CASE)
#undef ENUM_CASE
}
return nullptr;
}

它的调用类似于模板专门化:

const auto text{::MyEnumText(MyEnum::foo)}; // inlines beautifully

或者

const MyEnum e{GetTheEnumValue()};
const auto text{::MyEnumText(e)};