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

Edu*_*ipe 118 c++ string scripting enums

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

enum MyEnum {
      FOO,
      BAR = 0x50
};
Run Code Online (Sandbox Code Playgroud)

我搜索的是一个脚本(任何语言),它扫描我项目中的所有标题,并生成一个标题,每个枚举一个函数.

char* enum_to_string(MyEnum t);
Run Code Online (Sandbox Code Playgroud)

以及类似这样的实现:

char* enum_to_string(MyEnum t){
      switch(t){
         case FOO:
            return "FOO";
         case BAR:
            return "BAR";
         default:
            return "INVALID ENUM";
      }
 }
Run Code Online (Sandbox Code Playgroud)

这个问题确实与typedefed枚举和未命名的C风格枚举有关.有人知道这个吗?

编辑:解决方案不应该修改我的源,除了生成的函数.枚举是在API中,因此使用迄今为止提出的解决方案不是一种选择.

Mar*_*iuk 73

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;
}
Run Code Online (Sandbox Code Playgroud)

colours.def:

X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)
Run Code Online (Sandbox Code Playgroud)

但是,我通常更喜欢以下方法,因此可以稍微调整字符串.

#define X(a, b) a,
#define X(a, b) b,

X(Red, "red")
X(Green, "green")
// etc.
Run Code Online (Sandbox Code Playgroud)

  • 我不确定"最佳"解决方案! (21认同)
  • 漂亮,虽然我不喜欢额外的文件 (11认同)
  • 只需确保您的构建过程不会在每个包含文件之前添加#pragma(一次)... (2认同)
  • 这种解决方案远远优于任何基于 switch case 或数组的解决方案,因为它不会重复名称,从而可以轻松更改枚举。 (2认同)
  • @ikku100 你对`#define X(a, b) #b` 的看法不正确。仅当定义看起来像这样的“X(Red, red)”而不是答案中显示的定义“X(Red, "red")”时才需要这样做 (2认同)

Avd*_*vdi 48

您可能想要查看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>
Run Code Online (Sandbox Code Playgroud)

您可以使用您喜欢的任何语言来提取Enumeration和EnumValue标记并生成所需的代码.

  • +1,GCCXML看起来非常好看!(虽然我差不多了,因为我最初误读了这个建议,使用上面详细的XML语法来编码你的枚举 - 一个充满过度工程的解决方案!) (6认同)

Jas*_*ers 42

@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)
};
Run Code Online (Sandbox Code Playgroud)


gbj*_*anb 33

我倾向于创建一个C数组,其名称与枚举值的顺序和位置相同.

例如.

enum colours { red, green, blue };
const char *colour_names[] = { "red", "green", "blue" };
Run Code Online (Sandbox Code Playgroud)

然后你可以在你想要人类可读值的地方使用数组,例如

colours mycolour = red;
cout << "the colour is" << colour_names[mycolour];
Run Code Online (Sandbox Code Playgroud)

您可以尝试使用字符串化运算符(请参阅预处理器参考中的#),在某些情况下可以执行您想要的操作 - 例如:

#define printword(XX) cout << #XX;
printword(red);
Run Code Online (Sandbox Code Playgroud)

将打印"红色"到标准输出.不幸的是,它不适用于变量(因为你将打印出变量名称)

  • 仅当您不为枚举条目设置特殊数值时才有效. (3认同)

小智 10

我有一个非常简单易用的宏,以完全干燥的方式做到这一点.它涉及可变参数宏和一些简单的解析魔法.开始:

#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;} 
Run Code Online (Sandbox Code Playgroud)

要在代码中使用它,只需执行以下操作:

AWESOME_MAKE_ENUM(Animal,
    DOG,
    CAT,
    HORSE
);
Run Code Online (Sandbox Code Playgroud)


Ron*_*del 8

QT能够拉动(感谢元对象编译器):link


Mar*_*som 7

我今天刚刚重新发明了这个轮子,并且认为我会分享它.

此实现执行要求对定义的常量,它可以是枚举或代码进行任何更改#defineS或其他任何转予整数-在我的情况,我在其他符号的定义的符号.它也适用于稀疏值.它甚至允许多个名称用于相同的值,始终返回第一个名称.唯一的缺点是它需要你创建一个常量表,例如,当添加新的常量时,它可能会变得过时.

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]);
}
Run Code Online (Sandbox Code Playgroud)

您如何使用它的一个示例:

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);
Run Code Online (Sandbox Code Playgroud)

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;
    }
}
Run Code Online (Sandbox Code Playgroud)


ser*_*rge 7

这可以在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;
}
Run Code Online (Sandbox Code Playgroud)

  • 这并没有回答OP的问题:他正在寻找一种方法来**自动生成**一个函数以将枚举的成员名称作为字符串返回。 (2认同)

Ben*_*Ben 6

#define stringify( name ) # name

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

stringify(EnumName::ENUMVAL1);  // Returns MyEnum::ENUMVAL1
Run Code Online (Sandbox Code Playgroud)

进一步讨论这种方法

新手的预处理器指令技巧

  • 实际上这是相当无用的,因为stringify方法是在编译时并且非常直观.如果你说变量中的枚举类型有问题,尝试对变量进行字符串化只会给你变量名,而不是枚举类型名. (4认同)

cpp*_*guy 6

虽然有点晚了,但我真的很喜欢这种模式,因为它可以让您避免复制粘贴错误,并且如果枚举没有映射到字符串,它会直接无法编译。它还具有非常友好的优点,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
Run Code Online (Sandbox Code Playgroud)

您使用它的方式非常简单。如果您始终在需要字符串的站点上对枚举值进行硬编码,则只需调用以下的专用版本MyEnumText

const auto text{::MyEnumText<MyEnum::foo>()}; // inlines beautifully
Run Code Online (Sandbox Code Playgroud)

如果您需要处理动态枚举值,您可以添加此附加帮助程序:

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;
}
Run Code Online (Sandbox Code Playgroud)

其调用方式与模板特化类似:

const auto text{::MyEnumText(MyEnum::foo)}; // inlines beautifully
Run Code Online (Sandbox Code Playgroud)

或者

const MyEnum e{GetTheEnumValue()};
const auto text{::MyEnumText(e)};
Run Code Online (Sandbox Code Playgroud)


Car*_*arl 5

有趣的是看多少种方法.这是我很久以前用过的一个:

在文件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(){};
};
Run Code Online (Sandbox Code Playgroud)

在main.cpp中

#include "myenummap.h"

...
mymap nummap;
std::cout<< nummap[ one ] << std::endl;
Run Code Online (Sandbox Code Playgroud)

它不是常数,但它方便.

这是使用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;
}
Run Code Online (Sandbox Code Playgroud)

  • 继承stl容器不是一个好主意 (10认同)

Fra*_*ace 5

这是一个单文件解决方案(基于@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;
}
Run Code Online (Sandbox Code Playgroud)