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

139

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

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

问题出在typedef枚举和未命名的C风格枚举上。有没有人对此有解决方案?
编辑:解决方案不应该修改我的源代码,除了生成的函数。这些枚举在API中,因此目前提出的解决方案都不可行。

关于基于宏的工厂的答案已经移动到https://dev59.com/-XVC5IYBdhLWcg3w51ry - 在问题更新后,它在这里不再相关。 - Suma
35个回答

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(){};
};

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

1
这是完全合法的。我经常这样做。 - Jonathan Graehl
好的解决方案。这是C++,所以使用STL map是可以的。 - Adam Bruss

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

4

为了让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)

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

1
我喜欢你的API,但不幸的是,你的SmartEnum实际上并没有创建一个枚举“类型”。你不能这样做 MyEnum x = MyEnum::TWO;。我已经发布了我编辑过的类来支持这一点。 - Mark Lakata

4

Suma的宏解决方案很不错。您不需要有两个不同的宏,因为C++可以愉快地包含头文件两次。只需省略include保护即可。

因此,您将拥有一个定义为

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

你可以像这样包含它:

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

enumfactory.h将会进行两次#include ENUMFACTORY_ARGUMENT。第一轮,它会像Suma的DECLARE_ENUM一样扩展ENUM;第二轮,ENUM会像DEFINE_ENUM一样工作。

你也可以多次包含enumfactory.h,只要为ENUMFACTORY_ARGUMENT传入不同的#define即可。


1
似乎 Suma 把答案搬到了这里。你可能想在你的回答中包含这个链接。我只是偶然发现了这条评论,如果没有 Suma 的答案,这个问题就毫无意义了。 - 463035818_is_not_a_number

3

我想发布这篇文章,以便有人发现它有用。

在我的情况下,我只需要为单个C++11枚举类型生成ToString()FromString()函数,这些函数在单个.hpp文件中。

我编写了一个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)

Example:

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

2
这是一个在线生成器:http://th-thielemann.de/tools/cpp-enum-to-string.html。 - Th. Thielemann
@Alexandru Irimiea - 我在Python 3.8中运行这个Python脚本时遇到了一个错误:TypeError: 'map' object is not subscriptable。 - Investing TS

3
另一种方法:在某些情况下,将枚举定义为非代码格式(如CSV、YAML或XML文件),然后从定义中生成C++枚举代码和to-string代码是有意义的。这种方法可能在您的应用程序中可行,但需要注意。

3
请注意,您的转换函数最好返回一个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));

这很丑陋,但它是(在预处理器级别上)让你仅在代码的一个地方定义枚举的唯一方法。因此,由于对枚举的修改而导致的错误不会出现在你的代码中。你的枚举定义和转换函数将始终保持同步。但是,我要重申,这很丑陋 :)


3
这是对@user3360260答案的修改,新增以下功能:
  • 支持MyEnum fromString(const string&) 函数
  • 编译器兼容VisualStudio 2012
  • 枚举类型是一个实际的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转换是快速的哈希查找,而fromString转换是缓慢的线性搜索。但是,字符串本身非常昂贵(以及相关的文件IO),因此我并不觉得需要进行优化或使用双向映射。

你和 user3360260 有一个很好的解决方案。为什么不使用 multimap 呢? - Vincent

2
您可以使用反射库,例如 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"

你必须声明每个成员?真是浪费时间! - Andrew

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接