C + + : 将枚举值打印为文本

如果我有这样的 enum:

enum Errors {
ErrorA = 0,
ErrorB,
ErrorC,
};

然后我想把它打印出来放到控制台:

Errors anError = ErrorA;
std::cout << anError; // 0 will be printed

但我想要的是文本 "ErrorA"。不使用 if/switch我能做到吗?那你有什么解决办法?

244680 次浏览

Use an array or vector of strings with matching values:

char *ErrorTypes[] =
{
"errorA",
"errorB",
"errorC"
};


cout << ErrorTypes[anError];

EDIT: The solution above is applicable when the enum is contiguous, i.e. starts from 0 and there are no assigned values. It will work perfectly with the enum in the question.

要进一步证明枚举不是从0开始的情况,请使用:

cout << ErrorTypes[anError - ErrorA];

你可以使用一个 stl 地图容器... 。

typedef map<Errors, string> ErrorMap;


ErrorMap m;
m.insert(ErrorMap::value_type(ErrorA, "ErrorA"));
m.insert(ErrorMap::value_type(ErrorB, "ErrorB"));
m.insert(ErrorMap::value_type(ErrorC, "ErrorC"));


Errors error = ErrorA;


cout << m[error] << endl;

这里有一个可能有帮助的讨论: 有没有一种简单的方法把 C + + 枚举转换成字符串?

更新: 下面是 Lua 的脚本,它为遇到的每个命名枚举创建一个运算符 < < 。这可能需要一些工作来使它适用于不那么简单的情况[1] :

function make_enum_printers(s)
for n,body in string.gmatch(s,'enum%s+([%w_]+)%s*(%b{})') do
print('ostream& operator<<(ostream &o,'..n..' n) { switch(n){')
for k in string.gmatch(body,"([%w_]+)[^,]*") do
print('  case '..k..': return o<<"'..k..'";')
end
print('  default: return o<<"(invalid value)"; }}')
end
end


local f=io.open(arg[1],"r")
local s=f:read('*a')
make_enum_printers(s)

考虑到这一点:

enum Errors
{ErrorA=0, ErrorB, ErrorC};


enum Sec {
X=1,Y=X,foo_bar=X+1,Z
};

它产生:

ostream& operator<<(ostream &o,Errors n) { switch(n){
case ErrorA: return o<<"ErrorA";
case ErrorB: return o<<"ErrorB";
case ErrorC: return o<<"ErrorC";
default: return o<<"(invalid value)"; }}
ostream& operator<<(ostream &o,Sec n) { switch(n){
case X: return o<<"X";
case Y: return o<<"Y";
case foo_bar: return o<<"foo_bar";
case Z: return o<<"Z";
default: return o<<"(invalid value)"; }}

所以这对你来说可能是个好的开始。

[1]在不同或非命名空间范围内的枚举,带有包含逗号的初始化器表达式的枚举,等等。

使用地图:

#include <iostream>
#include <map>
#include <string>


enum Errors {ErrorA=0, ErrorB, ErrorC};


std::ostream& operator<<(std::ostream& out, const Errors value){
static std::map<Errors, std::string> strings;
if (strings.size() == 0){
#define INSERT_ELEMENT(p) strings[p] = #p
INSERT_ELEMENT(ErrorA);
INSERT_ELEMENT(ErrorB);
INSERT_ELEMENT(ErrorC);
#undef INSERT_ELEMENT
}


return out << strings[value];
}


int main(int argc, char** argv){
std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
return 0;
}

使用具有线性搜索的结构阵列:

#include <iostream>
#include <string>


enum Errors {ErrorA=0, ErrorB, ErrorC};


std::ostream& operator<<(std::ostream& out, const Errors value){
#define MAPENTRY(p) {p, #p}
const struct MapEntry{
Errors value;
const char* str;
} entries[] = {
MAPENTRY(ErrorA),
MAPENTRY(ErrorB),
MAPENTRY(ErrorC),
{ErrorA, 0}//doesn't matter what is used instead of ErrorA here...
};
#undef MAPENTRY
const char* s = 0;
for (const MapEntry* i = entries; i->str; i++){
if (i->value == value){
s = i->str;
break;
}
}


return out << s;
}


int main(int argc, char** argv){
std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
return 0;
}

使用开关/箱子:

#include <iostream>
#include <string>


enum Errors {ErrorA=0, ErrorB, ErrorC};


std::ostream& operator<<(std::ostream& out, const Errors value){
const char* s = 0;
#define PROCESS_VAL(p) case(p): s = #p; break;
switch(value){
PROCESS_VAL(ErrorA);
PROCESS_VAL(ErrorB);
PROCESS_VAL(ErrorC);
}
#undef PROCESS_VAL


return out << s;
}


int main(int argc, char** argv){
std::cout << ErrorA << std::endl << ErrorB << std::endl << ErrorC << std::endl;
return 0;
}

对于这个问题,我做了这样一个帮助函数:

const char* name(Id id) {
struct Entry {
Id id;
const char* name;
};
static const Entry entries[] = {
{ ErrorA, "ErrorA" },
{ ErrorB, "ErrorB" },
{ 0, 0 }
}
for (int it = 0; it < gui::SiCount; ++it) {
if (entries[it].id == id) {
return entries[it].name;
}
}
return 0;
}

对于这样的小型集合,线性搜索通常比 std::map更有效。

下面是一个基于 Boost。预处理器的示例:

#include <iostream>


#include <boost/preprocessor/punctuation/comma.hpp>
#include <boost/preprocessor/control/iif.hpp>
#include <boost/preprocessor/comparison/equal.hpp>
#include <boost/preprocessor/stringize.hpp>
#include <boost/preprocessor/seq/for_each.hpp>
#include <boost/preprocessor/seq/size.hpp>
#include <boost/preprocessor/seq/seq.hpp>




#define DEFINE_ENUM(name, values)                               \
enum name {                                                   \
BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_VALUE, , values)          \
};                                                            \
inline const char* format_##name(name val) {                  \
switch (val) {                                              \
BOOST_PP_SEQ_FOR_EACH(DEFINE_ENUM_FORMAT, , values)       \
default:                                                    \
return 0;                                               \
}                                                           \
}


#define DEFINE_ENUM_VALUE(r, data, elem)                        \
BOOST_PP_SEQ_HEAD(elem)                                       \
BOOST_PP_IIF(BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE(elem), 2),      \
= BOOST_PP_SEQ_TAIL(elem), )                     \
BOOST_PP_COMMA()


#define DEFINE_ENUM_FORMAT(r, data, elem)             \
case BOOST_PP_SEQ_HEAD(elem):                       \
return BOOST_PP_STRINGIZE(BOOST_PP_SEQ_HEAD(elem));




DEFINE_ENUM(Errors,
((ErrorA)(0))
((ErrorB))
((ErrorC)))


int main() {
std::cout << format_Errors(ErrorB) << std::endl;
}

每当我定义一个枚举时,我都使用一个字符串数组:

侧写

#pragma once


struct Profile
{
enum Value
{
Profile1,
Profile2,
};


struct StringValueImplementation
{
const wchar_t* operator[](const Profile::Value profile)
{
switch (profile)
{
case Profile::Profile1: return L"Profile1";
case Profile::Profile2: return L"Profile2";
default: ASSERT(false); return NULL;
}
}
};


static StringValueImplementation StringValue;
};

Profile.cpp

#include "Profile.h"


Profile::StringValueImplementation Profile::StringValue;

You can use a simpler pre-processor trick if you are willing to list your enum entries in an external file.

/* file: errors.def */
/* syntax: ERROR_DEF(name, value) */
ERROR_DEF(ErrorA, 0x1)
ERROR_DEF(ErrorB, 0x2)
ERROR_DEF(ErrorC, 0x4)

然后在源文件中,您将该文件视为一个包含文件,但是您需要定义 ERROR_DEF要执行的操作。

enum Errors {
#define ERROR_DEF(x,y) x = y,
#include "errors.def"
#undef ERROR_DEF
};


static inline std::ostream & operator << (std::ostream &o, Errors e) {
switch (e) {
#define ERROR_DEF(x,y) case y: return o << #x"[" << y << "]";
#include "errors.def"
#undef ERROR_DEF
default: return o << "unknown[" << e << "]";
}
}

如果您使用某种源浏览工具(如 cscope) ,则必须让它知道外部文件。

这个怎么样?

    enum class ErrorCodes : int{
InvalidInput = 0
};


std::cout << ((int)error == 0 ? "InvalidInput" : "") << std::endl;

等等。.我知道这是一个非常人为的例子,但是我认为它在适用和需要的地方有应用程序,而且肯定比为它编写脚本要短。

#include <iostream>
using std::cout;
using std::endl;


enum TEnum
{
EOne,
ETwo,
EThree,
ELast
};


#define VAR_NAME_HELPER(name) #name
#define VAR_NAME(x) VAR_NAME_HELPER(x)


#define CHECK_STATE_STR(x) case(x):return VAR_NAME(x);


const char *State2Str(const TEnum state)
{
switch(state)
{
CHECK_STATE_STR(EOne);
CHECK_STATE_STR(ETwo);
CHECK_STATE_STR(EThree);
CHECK_STATE_STR(ELast);
default:
return "Invalid";
}
}


int main()
{
int myInt=12345;
cout << VAR_NAME(EOne) " " << VAR_NAME(myInt) << endl;


for(int i = -1; i < 5;   i)
cout << i << " " << State2Str((TEnum)i) << endl;
return 0;
}

此解决方案不要求您使用任何数据结构或创建不同的文件。

Basically, you define all your enum values in a #define, then use them in the operator <<. Very similar to @jxh's answer.

ideone link for final iteration: http://ideone.com/hQTKQp

完整代码:

#include <iostream>


#define ERROR_VALUES ERROR_VALUE(NO_ERROR)\
ERROR_VALUE(FILE_NOT_FOUND)\
ERROR_VALUE(LABEL_UNINITIALISED)


enum class Error
{
#define ERROR_VALUE(NAME) NAME,
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) case Error::NAME: return os << "[" << errVal << "]" #NAME;
ERROR_VALUES
#undef ERROR_VALUE
default:
// If the error value isn't found (shouldn't happen)
return os << errVal;
}
}


int main() {
std::cout << "Error: " << Error::NO_ERROR << std::endl;
std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
return 0;
}

产出:

Error: [0]NO_ERROR
Error: [1]FILE_NOT_FOUND
Error: [2]LABEL_UNINITIALISED

这样做的一个好处是,如果您认为需要,还可以为每个错误指定自己的自定义消息:

#include <iostream>


#define ERROR_VALUES ERROR_VALUE(NO_ERROR, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, "A component tried to the label before it was initialised")


enum class Error
{
#define ERROR_VALUE(NAME,DESCR) NAME,
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,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
ERROR_VALUES
#undef ERROR_VALUE
default:
return os << errVal;
}
}


int main() {
std::cout << "Error: " << Error::NO_ERROR << std::endl;
std::cout << "Error: " << Error::FILE_NOT_FOUND << std::endl;
std::cout << "Error: " << Error::LABEL_UNINITIALISED << std::endl;
return 0;
}

产出:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised

如果您希望使错误代码/描述非常具有描述性,那么您可能不希望它们出现在生产构建中。关闭它们,只打印出数值很容易:

inline std::ostream& operator<<(std::ostream& os, Error err)
{
int errVal = static_cast<int>(err);
switch (err)
{
#ifndef PRODUCTION_BUILD // Don't print out names in production builds
#define ERROR_VALUE(NAME,DESCR) case Error::NAME: return os << "[" << errVal << "]" #NAME <<"; " << DESCR;
ERROR_VALUES
#undef ERROR_VALUE
#endif
default:
return os << errVal;
}
}

产出:

Error: 0
Error: 1
Error: 2

如果是这种情况,那么查找错误号525将是一个 PITA。我们可以像下面这样手动指定初始枚举中的数字:

#define ERROR_VALUES ERROR_VALUE(NO_ERROR, 0, "Everything is fine")\
ERROR_VALUE(FILE_NOT_FOUND, 1, "File is not found")\
ERROR_VALUE(LABEL_UNINITIALISED, 2, "A component tried to the label before it was initialised")\
ERROR_VALUE(UKNOWN_ERROR, -1, "Uh oh")


enum class Error
{
#define ERROR_VALUE(NAME,VALUE,DESCR) NAME=VALUE,
ERROR_VALUES
#undef ERROR_VALUE
};


inline std::ostream& operator<<(std::ostream& os, Error err)
{
int errVal = static_cast<int>(err);
switch (err)
{
#ifndef PRODUCTION_BUILD // Don't print out names in production builds
#define ERROR_VALUE(NAME,VALUE,DESCR) case Error::NAME: return os << "[" #VALUE  "]" #NAME <<"; " << DESCR;
ERROR_VALUES
#undef ERROR_VALUE
#endif
default:
return os <<errVal;
}
}
ERROR_VALUES
#undef ERROR_VALUE
#endif
default:
{
// If the error value isn't found (shouldn't happen)
return os << static_cast<int>(err);
break;
}
}
}

产出:

Error: [0]NO_ERROR; Everything is fine
Error: [1]FILE_NOT_FOUND; File is not found
Error: [2]LABEL_UNINITIALISED; A component tried to the label before it was initialised
Error: [-1]UKNOWN_ERROR; Uh oh

这是个好办法,

enum Rank { ACE = 1, DEUCE, TREY, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING };

用字符数组数组打印它

const char* rank_txt[] = {"Ace", "Deuce", "Trey", "Four", "Five", "Six", "Seven", "Eight", "Nine", "Ten", "Jack", "Four", "King" } ;

像这样

std::cout << rank_txt[m_rank - 1]

使用预处理器:

#define VISIT_ERROR(FIRST, MIDDLE, LAST) \
FIRST(ErrorA) MIDDLE(ErrorB) /* MIDDLE(ErrorB2) */ LAST(ErrorC)


enum Errors
{
#define ENUMFIRST_ERROR(E)  E=0,
#define ENUMMIDDLE_ERROR(E) E,
#define ENUMLAST_ERROR(E)   E
VISIT_ERROR(ENUMFIRST_ERROR, ENUMMIDDLE_ERROR, ENUMLAST_ERROR)
// you might undefine the 3 macros defined above
};


std::string toString(Error e)
{
switch(e)
{
#define CASERETURN_ERROR(E)  case E: return #E;
VISIT_ERROR(CASERETURN_ERROR, CASERETURN_ERROR, CASERETURN_ERROR)
// you might undefine the above macro.
// note that this will produce compile-time error for synonyms in enum;
// handle those, if you have any, in a distinct macro


default:
throw my_favourite_exception();
}
}

这种方法的优点是: 还是很容易理解的 它允许各种访问(不仅仅是字符串)

如果你愿意放弃第一个,制作一个 FOREACH ()宏,然后 #define ERROR_VALUES() (ErrorA, ErrorB, ErrorC)和写你的访问者在 FOREACH ()方面。然后尝试通过代码审查:)。