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


88

如果我有这样的枚举

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

然后我要打印到控制台

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

但是我想要的是文本“ ErrorA”,我可以不使用if / switch来做到吗?
您对此有什么解决方案?


我认为我的回答还不错,您介意看看吗?
2014年


Answers:


63

使用地图:

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

12
-1。只是做一个切换情况,而不是使用哈希映射。增加复杂性不是一件好事。
西蒙

8
好点子。下次,我将:)但现在我看到您已经编辑了帖子以添加我想要的功能。做得好!
西蒙

1
#p是什么?如果在第三个示例中使用枚举类而不是枚举,是否可以仅获取枚举字符串而不使用类名?
rh0x

2
#p是预处理器将p进行字符串化。因此调用PROCESS_VAL(ErrorA)将输出:case(ErrorA): s = "ErrorA"; break;
Nashenas '16

我不认为这是最佳解决方案:原因:1)我必须保持两倍enum价值,我认为这是不可行的。2)当我正确理解解决方案时,它仅适用于一个enum
彼得·瓦尔加

28

使用具有匹配值的字符串数组或向量:

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

cout << ErrorTypes[anError];

编辑:以上解决方案适用于枚举是连续的,即从0开始且没有分配的值。它将与问题中的枚举完美配合。

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

cout << ErrorTypes[anError - ErrorA];

4
不幸的是,枚举允许我们为元素分配值。如果您有不连续的枚举,行方法如何工作枚举状态{OK = 0,失败= -1,OutOfMemory = -2,IOError = -1000,ConversionError = -2000}`(因此您以后可以添加IOErrors到-1001-1999范围)
北欧大型机2010年

@Luther:是的,这仅适用于大多数枚举是连续的枚举。如果枚举不连续,则需要使用另一种方法,即映射。但是在连续枚举的情况下,我建议使用这种方法,而不要过于复杂。
Igor Oks

2
因此,如果我的同事将NewValue添加到枚举并且不更新ErrorTypes数组,那么ErrorTypes [NewValue]会产生什么结果?以及如何处理负枚举值?
Nordic Mainframe

2
@Luther:您必须保持ErrorTypes更新。同样,在简单性和通用性之间需要权衡,这取决于对用户而言更重要的事情。负的枚举值有什么问题?
Igor Oks

1
为了提高内存效率,此数组不应该是静态的吗?和常量的安全性?
乔纳森

15

这是一个基于Boost.Preprocessor的示例:

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

2
+1,此解决方案不依赖于上面的lua答案之类的外部工具,而是纯C ++,它遵循DRY原理,并且用户语法可读(如果格式正确。顺便说一句,您不需要反斜杠当使用DEFINE_ENUM(看起来更自然,IMO)时
Fabio Fracassi

3
@Fabio Fracassi:“此解决方案不依赖外部工具” Boost是外部工具-非标准C ++库。此外,它太长了。解决问题应尽可能简单。这个不符合条件...
SigTerm

2
实际上,您可以将大多数代码(实际上除实际定义之外的所有代码)都放入单个标头中。所以这实际上是这里介绍的最短的解决方案。而且对于boost是外部的,是的,但是要比上面的lua脚本用于处理源部分的非语言脚本要少。除了boost如此接近标准之外,每个C ++程序员工具箱中都应该使用boost。当然就是恕我直言
Fabio Fracassi

[我删除了宏调用中不需要的换行符转义。不需要它们:宏调用可以跨越多行。]
James McNellis 2011年

尝试使用它时,宏DEFINE_ENUM给了我错误multiple definition of `format_ProgramStatus(ProgramStatus)'
HelloGoodbye

5

如果您愿意enum在外部文件中列出条目,则可以使用更简单的预处理技巧。

/* 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),则必须让它知道外部文件。


4

这里进行了讨论,可能会有所帮助:是否有一种简单的方法将C ++枚举转换为字符串?

更新: 这是Lua的#sa脚本,它为遇到的每个命名枚举创建一个operator <<。这可能需要一些工作才能使其在不太简单的情况下起作用[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]处于不同或非命名空间范围内的枚举,带有包含komma的初始化表达式的枚举,等等。


在这里注释“ -1”以使张贴者有机会修正他们的答案不是一种习惯吗?只是问..
北欧大型机

2
我认为下面的(来自Philip)的Boost PP解决方案更好,因为使用外部工具是非常昂贵的维护方法。但没有-1,因为答案是其他有效
法比奥弗拉卡西

4
Boost PP也是一个维护问题,因为您需要每个人都讲Boost PP的语言,这很可怕,容易破解(给出通常不可用的错误消息)并且仅具有有限的可用性(lua / python / perl可以从任意语言生成代码)外部数据)。它将增加您的依赖项列表,由于项目政策,甚至可能不允许这样做。同样,它是侵入性的,因为它要求您在DSL中定义枚举。您最喜欢的源代码工具或IDE可能会遇到麻烦。最后但并非最不重要的一点:您不能在扩展中设置断点。
Nordic Mainframe

4

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

个人资料

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

4

这是个好方法

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]

2
如果我的枚举从2000年开始怎么办?此解决方案将不起作用。
Sitesh

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

2

您可以使用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;

4
这张地图怎么样switch(n) { case XXX: return "XXX"; ... }?哪个具有O(1)查找且不需要初始化?还是枚举在运行时有所改变?
北欧大型机,2010年

我同意@Luther Blissett在使用switch语句(或函数指针)上的意见
KedarX 2010年

1
好吧,他可能想要输出“这是我亲爱的朋友路德是错误A或”这是我亲爱的朋友阿德里安是错误B。”而且,使用map可以消除对iostream签名的依赖,因此他可以自由地在iostream签名中使用它。与字符串连接例如代码,串x =“你好” + M [错误a]等
阿德里安里根

我确定std :: map包含很多if和switch。我看这是“我怎么能做到这一点不为我的写作年代和开关”
北欧大型机

我敢肯定,但确实不需要您使用Lua编写脚本来解决问题……
Adrian Regan

1

对于此问题,我执行了如下帮助功能:

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的小型集合更有效。


1

该解决方案不需要您使用任何数据结构或创建其他文件。

基本上,您可以在#define中定义所有枚举值,然后在运算符<<中使用它们。与@jxh的答案非常相似。

最终迭代的ideone链接: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

0

这个怎么样?

    enum class ErrorCodes : int{
          InvalidInput = 0
    };

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

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


0

使用预处理器:

#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()宏,然后使用FOREACH()来#define ERROR_VALUES() (ErrorA, ErrorB, ErrorC)编写访问者。然后尝试通过代码审查:)。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.