ChatGPT解决这个技术问题 Extra ChatGPT

如何轻松地将 C++ 枚举映射到字符串

我正在使用的一些库头文件中有一堆枚举类型,我想要一种将枚举值转换为用户字符串的方法 - 反之亦然。

RTTI 不会为我这样做,因为“用户字符串”需要比枚举更具可读性。

蛮力解决方案将是一堆这样的函数,但我觉得这有点太像 C 了。

enum MyEnum {VAL1, VAL2,VAL3};

String getStringFromEnum(MyEnum e)
{
  switch e
  {
  case VAL1: return "Value 1";
  case VAL2: return "Value 2";
  case VAL1: return "Value 3";
  default: throw Exception("Bad MyEnum");
  }
}

我有一种直觉,认为有一个使用模板的优雅解决方案,但我还不能完全理解它。

更新:感谢您的建议-我应该明确说明枚举是在第三方库标头中定义的,所以我不想更改它们的定义。

我现在的直觉是避免使用模板并执行以下操作:

char * MyGetValue(int v, char *tmp); // implementation is trivial

#define ENUM_MAP(type, strings) char * getStringValue(const type &T) \
 { \
 return MyGetValue((int)T, strings); \
 }

; enum eee {AA,BB,CC}; - exists in library header file 
; enum fff {DD,GG,HH}; 

ENUM_MAP(eee,"AA|BB|CC")
ENUM_MAP(fff,"DD|GG|HH")

// To use...

    eee e;
    fff f;
    std::cout<< getStringValue(e);
    std::cout<< getStringValue(f);
可能他们都没有解决 op 的问题,上面的所有解决方案都已经由 op 尝试过,他需要像反射这样的东西

C
Community

如果您希望枚举名称本身作为字符串,请参阅 this post。否则,std::map<MyEnum, char const*> 会很好地工作。 (将字符串文字复制到地图中的 std::strings 毫无意义)

对于额外的语法糖,这里是如何编写一个 map_init 类。目标是允许

std::map<MyEnum, const char*> MyMap;
map_init(MyMap)
    (eValue1, "A")
    (eValue2, "B")
    (eValue3, "C")
;

函数 template <typename T> map_init(T&) 返回一个 map_init_helper<T>map_init_helper<T> 存储 T&,并定义平凡的 map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&)。 (从 operator() 返回 *this 允许链接 operator(),如 std::ostream 上的 operator<<

template<typename T> struct map_init_helper
{
    T& data;
    map_init_helper(T& d) : data(d) {}
    map_init_helper& operator() (typename T::key_type const& key, typename T::mapped_type const& value)
    {
        data[key] = value;
        return *this;
    }
};

template<typename T> map_init_helper<T> map_init(T& item)
{
    return map_init_helper<T>(item);
}

由于函数和助手类是模板化的,因此您可以将它们用于任何地图或类似地图的结构。即它还可以向 std::unordered_map 添加条目

如果您不喜欢编写这些帮助程序,boost::assign 提供了开箱即用的相同功能。


你引用另一个问题是对的。人们应该在发布之前查看“相关问题”...
@xtofl:这里显示的“相关问题”与我发布问题时列出的相关问题完全不同!
@MSalters, std::map 是一种处理实现的有用方法,但我正在寻找一些减少可能需要的样板代码的方法。
@MSalters,如果能够接受 operator[] 的多个参数,那就太好了。但可悲的是,人们无法做到这一点。 x[a, b] 评估为 x[b] 。 (a, b) 表达式使用逗号运算符。所以它相当于你的代码中的 ["A"]["B"]["C"] 。您可以将其更改为 [eValue1]["A"][eValu..
函数调用运算符也是一个不错的选择: map_init(MyMap)(eValue1, "A")(eValue2, "B").... 那么它相当于 boost::assign : insert(MyMap)(eValue1, "A")(eValue2, "B")... (boost.org/doc/libs/1_35_0/libs/assign/doc/index.html)
A
Alastair

MSalters 解决方案是一个很好的解决方案,但基本上重新实现了 boost::assign::map_list_of。如果你有boost,可以直接使用:

#include <boost/assign/list_of.hpp>
#include <boost/unordered_map.hpp>
#include <iostream>

using boost::assign::map_list_of;

enum eee { AA,BB,CC };

const boost::unordered_map<eee,const char*> eeeToString = map_list_of
    (AA, "AA")
    (BB, "BB")
    (CC, "CC");

int main()
{
    std::cout << " enum AA = " << eeeToString.at(AA) << std::endl;
    return 0;
}

如果 eeeToString 是类的数据成员,您将如何使用它?我收到“错误:不允许数据成员初始化”
@User:类数据成员在构造函数中初始化,通常在初始化列表中。
有没有办法使所有枚举都可以使用。我有多个枚举声明,并且不希望地图仅适用于您的情况下的 eee 类型。
我尝试使用模板,但随后出现错误:error: template declaration of 'const boost::unordered::unordered_map<T, const char*> enumToString'
实际上,这个答案在 C++11 中基本上已经过时了。
j
jfs

从另一种形式自动生成一种形式。

资源:

enum {
  VALUE1, /* value 1 */
  VALUE2, /* value 2 */
};

生成:

const char* enum2str[] = {
  "value 1", /* VALUE1 */
  "value 2", /* VALUE2 */
};

如果枚举值很大,则生成的表单可以使用 unordered_map<> 或康斯坦丁建议的模板。

资源:

enum State{
  state0 = 0, /* state 0 */
  state1 = 1, /* state 1 */
  state2 = 2, /* state 2 */
  state3 = 4, /* state 3 */

  state16 = 0x10000, /* state 16 */
};

生成:

template <State n> struct enum2str { static const char * const value; };
template <State n> const char * const enum2str<n>::value = "error";

template <> struct enum2str<state0> { static const char * const value; };
const char * const enum2str<state0>::value = "state 0";

例子:

#include <iostream>

int main()
{
  std::cout << enum2str<state16>::value << std::endl;
  return 0;
}

虽然最快,但它并不像@MSalters 那样简单。
如果您有一点 perl/python 从文本文件中读取字符串列表并在编译时生成带有静态字符的 .h 文件。 ="写程序写程序"
@mgb:perl/python 不是几乎任何语言的任何模板引擎都会做的唯一选项(在这种情况下,一个是从模板生成两种表单)。
@jf。是的,重点是在编译时自动构建静态数据表。我可能更喜欢只生成一个哑静态数组。
如果在编译时不知道状态,这会起作用吗?我很确定它不会——理论上编译器必须用枚举的所有可能值来实例化 enum2str 模板,我很确定 gcc(至少)不会这样做。
C
Community

我建议混合使用 X-macros are the best solution 和以下模板函数:

借用 marcinkoziukmyopenidcom 并延长

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

template <class T> T str2enum( const char* );
template <class T> const char* enum2str( T );

#define STR2ENUM(TYPE,ARRAY) \
template <> \
TYPE str2enum<TYPE>( const char* str ) \
    { \
    for( int i = 0; i < (sizeof(ARRAY)/sizeof(ARRAY[0])); i++ ) \
        if( !strcmp( ARRAY[i], str ) ) \
            return TYPE(i); \
    return TYPE(0); \
    }

#define ENUM2STR(TYPE,ARRAY) \
template <> \
const char* enum2str<TYPE>( TYPE v ) \
    { \
    return ARRAY[v]; \
    }

#define ENUMANDSTR(TYPE,ARRAY)\
    STR2ENUM(TYPE,ARRAY) \
    ENUM2STR(TYPE,ARRAY)

ENUMANDSTR(Colours,colours_str)

颜色.def

X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)

有没有办法使枚举字符串数组定义通用? (我不知道如何在宏中处理 X-Macro,也不容易处理模板)
stackoverflow.com/questions/70455489/… 非常相似,但稍有改进并使用标准预处理器...
D
Debdatta Basu

我记得在 StackOverflow 上的其他地方回答过这个问题。在这里重复一遍。基本上它是一个基于可变参数宏的解决方案,并且非常易于使用:

#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
);
auto dog = Animal::DOG;
std::cout<<dog;

只需将 enum 类声明更改为 enum 即可在 c++11 之前使用。
你是对的,它可以工作(自动也只有 c++11)。不错的解决方案!如果您还可以为某些枚举设置一个值,那将是完美的
我想我在 boost 中看到过类似的东西
C
Community

我使用 this 解决方案,我在下面复制:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}

这是基本的 X 宏,我很惊讶这是这里提出的第一个答案! +1
j
jose.angel.jimenez

我花了更多时间研究这个我想承认的话题。幸运的是,有很多很棒的开源解决方案。

这是两种很棒的方法,即使(还)还不够为人所知,

wise_enum

用于 C++11/14/17 的独立智能枚举库。它支持您期望从 C++ 中的智能枚举类中获得的所有标准功能。

限制:至少需要 C++11。

Better Enums

具有简洁语法的反射式编译时枚举库,位于单个头文件中,并且没有依赖项。

限制:基于宏,不能在类中使用。


u
uIM7AI9S

我知道我迟到了,但对于所有来访问这个页面的人来说,你可以试试这个,它比那里的所有东西都容易,而且更有意义:

namespace texs {
    typedef std::string Type;
    Type apple = "apple";
    Type wood = "wood";
}

您是否建议使用字符串而不使用枚举?这并不能真正解决问题。
C
Constantin

如果您想获得 MyEnum 变量 的字符串表示形式,那么模板将无法满足要求。模板可以专门用于编译时已知的整数值。

但是,如果这是您想要的,请尝试:

#include <iostream>

enum MyEnum { VAL1, VAL2 };

template<MyEnum n> struct StrMyEnum {
    static char const* name() { return "Unknown"; }
};

#define STRENUM(val, str) \
  template<> struct StrMyEnum<val> { \
    static char const* name() { return str; }};

STRENUM(VAL1, "Value 1");
STRENUM(VAL2, "Value 2");

int main() {
  std::cout << StrMyEnum<VAL2>::name();
}

这很冗长,但会捕获与您所提出的错误类似的错误 - 您的 case VAL1 重复。


实际上方法 name() 不是必需的。看我的回答。
R
Richard Harrison

我很想拥有一张地图 m - 并将其嵌入到枚举中。

设置 m[MyEnum.VAL1] = "Value 1";

一切都完成了。


S
Smi

我多次需要此功能来调试/分析其他人的代码。为此,我编写了一个 Perl 脚本,该脚本生成一个具有多个重载 toString 方法的类。每个 toString 方法都将 Enum 作为参数并返回 const char*

当然,脚本本身并不解析 C++ 中的枚举,而是使用 ctags 生成符号表。

Perl 脚本在这里:http://heinitz-it.de/download/enum2string/enum2string.pl.html


m
muqker

您的回答启发了我自己编写一些宏。我的要求如下:

仅将枚举的每个值写入一次,因此没有要维护的双重列表不要将枚举值保存在稍后#included 的单独文件中,因此我可以将其写入任何我想要的地方不要替换枚举本身,我仍然希望定义枚举类型,但除此之外,我希望能够将每个枚举名称映射到相应的字符串(不影响遗留代码),搜索应该很快,所以最好不要使用 switch-case,因为那些巨大的枚举

这段代码创建了一个带有一些值的经典枚举。此外,它创建为 std::map ,它将每个枚举值映射到它的名称(即 map[E_SUNDAY] = "E_SUNDAY" 等)

好的,现在是代码:

EnumUtilsImpl.h:

map<int, string> & operator , (map<int, string> & dest, 
                               const pair<int, string> & keyValue) {
    dest[keyValue.first] = keyValue.second; 
    return dest;
}

#define ADD_TO_MAP(name, value) pair<int, string>(name, #name)

EnumUtils.h // 这是您在需要执行此操作时要包含的文件,您将使用其中的宏:

#include "EnumUtilsImpl.h"
#define ADD_TO_ENUM(name, value) \
    name value

#define MAKE_ENUM_MAP_GLOBAL(values, mapName) \
    int __makeMap##mapName() {mapName, values(ADD_TO_MAP); return 0;}  \
    int __makeMapTmp##mapName = __makeMap##mapName();

#define MAKE_ENUM_MAP(values, mapName) \
    mapName, values(ADD_TO_MAP);

MyProjectCodeFile.h // 这是一个如何使用它来创建自定义枚举的示例:

#include "EnumUtils.h*

#define MyEnumValues(ADD) \
    ADD(val1, ), \
    ADD(val2, ), \
    ADD(val3, = 100), \
    ADD(val4, )

enum MyEnum {
    MyEnumValues(ADD_TO_ENUM)
};

map<int, string> MyEnumStrings;
// this is how you initialize it outside any function
MAKE_ENUM_MAP_GLOBAL(MyEnumValues, MyEnumStrings); 

void MyInitializationMethod()
{ 
    // or you can initialize it inside one of your functions/methods
    MAKE_ENUM_MAP(MyEnumValues, MyEnumStrings); 
}

干杯。


O
OlivierB

这是仅使用一行宏命令自动获取枚举上的 << 和 >> 流运算符的尝试...

定义:

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

虽然不确定这个方案的局限性......欢迎评论!


D
Daniel

通过使用指定的数组初始值设定项,您的字符串数组独立于枚举中元素的顺序:

enum Values {
    Val1,
    Val2
};

constexpr string_view v_name[] = {
    [Val1] = "Value 1",
    [Val2] = "Value 2"
}

此代码导致:array designators are nonstandard in C++C/C++(2901)
@Xoyce 数组指示符 are standard since C99
m
moogs

在标题中:

enum EFooOptions
 {
FooOptionsA = 0, EFooOptionsMin = 0,
FooOptionsB,
FooOptionsC,
FooOptionsD 
EFooOptionsMax
};
extern const wchar* FOO_OPTIONS[EFooOptionsMax];

在 .cpp 文件中:

const wchar* FOO_OPTIONS[] = {
    L"One",
    L"Two",
    L"Three",
    L"Four"
};

警告:不要处理错误的数组索引。 :) 但是您可以在从数组中获取字符串之前轻松添加一个函数来验证枚举。


确实是一个非常非 DRY-SPOT 的解决方案。
既然你提到了 DRY。 .h 和 .cpp 文件是从其他输入文件自动生成的。我希望看到更好的解决方案(不诉诸不必要的复杂性)
j
jamk

我只是想用宏展示这个可能的优雅解决方案。这并不能解决问题,但我认为这是重新思考问题的好方法。

#define MY_LIST(X) X(value1), X(value2), X(value3)

enum eMyEnum
    {
    MY_LIST(PLAIN)
    };

const char *szMyEnum[] =
    {
    MY_LIST(STRINGY)
    };


int main(int argc, char *argv[])
{

std::cout << szMyEnum[value1] << value1 <<" " <<  szMyEnum[value2] << value2 << std::endl;

return 0;
}

- - 编辑 - -

经过一些互联网研究和一些自己的实验后,我得出了以下解决方案:

//this is the enum definition
#define COLOR_LIST(X) \
  X( RED    ,=21)      \
  X( GREEN  )      \
  X( BLUE   )      \
  X( PURPLE , =242)      \
  X( ORANGE )      \
  X( YELLOW )

//these are the macros
#define enumfunc(enums,value) enums,
#define enumfunc2(enums,value) enums value,
#define ENUM2SWITCHCASE(enums) case(enums): return #enums;

#define AUTOENUM(enumname,listname) enum enumname{listname(enumfunc2)};
#define ENUM2STRTABLE(funname,listname) char* funname(int val) {switch(val) {listname(ENUM2SWITCHCASE) default: return "undef";}}
#define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int values[] = {listname(enumfunc)};int N = sizeof(values)/sizeof(int);ENUM2STRTABLE(enum2str,listname)};

//here the enum and the string enum map table are generated
AUTOENUM(testenum,COLOR_LIST)
ENUM2STRTABLE(testfunenum,COLOR_LIST)
ENUM2STRUCTINFO(colorinfo,COLOR_LIST)//colorinfo structur {int values[]; int N; char * enum2str(int);}

//debug macros
#define str(a) #a
#define xstr(a) str(a)


int main( int argc, char** argv )
{
testenum x = YELLOW;
std::cout << testfunenum(GREEN) << "   " << testfunenum(PURPLE) << PURPLE << "  " << testfunenum(x);

for (int i=0;i< colorinfo::N;i++)
std::cout << std::endl << colorinfo::values[i] <<  "  "<< colorinfo::enum2str(colorinfo::values[i]);

  return EXIT_SUCCESS;
}

我只是想发布它,也许有人会发现这个解决方案很有用。不需要模板类,不需要 c++11,也不需要 boost,所以这也可以用于简单的 C。

---- EDIT2 ----

当使用超过 2 个枚举时,信息表会产生一些问题(编译器问题)。以下解决方法有效:

#define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int spacename##_##values[] = {listname(enumfunc)};int spacename##_##N = sizeof(spacename##_##values)/sizeof(int);ENUM2STRTABLE(spacename##_##enum2str,listname)};

M
Madwyn
typedef enum {
    ERR_CODE_OK = 0,
    ERR_CODE_SNAP,

    ERR_CODE_NUM
} ERR_CODE;

const char* g_err_msg[ERR_CODE_NUM] = {
    /* ERR_CODE_OK   */ "OK",
    /* ERR_CODE_SNAP */ "Oh, snap!",
};

以上是我的简单解决方案。它的一个好处是控制消息数组大小的“NUM”,它还可以防止越界访问(如果您明智地使用它)。

您还可以定义一个函数来获取字符串:

const char* get_err_msg(ERR_CODE code) {
    return g_err_msg[code];
}

在我的解决方案之外,我发现以下一个非常有趣。它通常解决了上述同步问题。

此处的幻灯片:http://www.slideshare.net/arunksaha/touchless-enum-tostring-28684724

此处代码:https://github.com/arunksaha/enum_to_string


J
Jerzy Jamroz
#include <vector>
#include <string>

//Split one comma-separated value string to vector
std::vector<std::string> split(std::string csv, char separator){/*trivial*/}

//Initializer
#define ENUMIFY(name, ...)                                                                               \
  struct name                                                                                             \
  {                                                                                                       \
    enum Enum                                                                                       \
    {                                                                                                     \
      __VA_ARGS__                                                                                         \
    };                                                                                                    \
    static const std::vector<std::string>& Names()                                                        \
    {                                                                                                     \
      const static std::vector<std::string> _{split(#__VA_ARGS__, ',')}; \
      return _;                                                                                           \
    };                                                                                                    \
  };

宣言:

ENUMIFY(States, INIT, ON, OFF, RUNNING)

然后所有枚举都可用,加上它们的字符串被矢量化:

std::string enum_str = States::Names()[States::ON];

另一种选择是直接使用静态向量而不使用函数包装器。


上次编辑将 enum class 更改为 enum。恕我直言,这违反了 SO 的编辑规则,因为它偏离了作者的意图。这个问题可能有 12 年的历史,但我认为使用 C++11 习语给出答案是可以的。
使用 enum class 访问实际枚举(写为 States::ON)需要为 States::Enum::ON,这似乎有点多余,因为枚举已经在结构内命名空间。您可能是对的,也许更好的编辑是更改枚举访问线?
这是给出一个想法,它可以是枚举类(我的偏好)和强制转换或只是枚举。好处是它很简单,您只需定义一次项目。另一种选择是直接使用静态向量而不使用函数包装器。
f
fengshun

这是我的解决方案,我参考了其他一些设计,但我的更完整和使用更简单。

// file: enum_with_string.h
#pragma once

#include <map>
#include <string>
#include <vector>

namespace EnumString {

template <typename T>
static inline void split_string_for_each(const std::string &str,
                                         const std::string &delimiter,
                                         const T &foreach_function,
                                         ssize_t max_number = -1) {
  ssize_t num = 0;
  std::string::size_type start;
  std::string::size_type end = -1;
  while (true) {
    start = str.find_first_not_of(delimiter, end + 1);
    if (start == std::string::npos) break;  // over

    end = str.find_first_of(delimiter, start + 1);

    if (end == std::string::npos) {
      foreach_function(num, str.substr(start));
      break;
    }
    foreach_function(num, str.substr(start, end - start));
    ++num;

    if (max_number > 0 && num == max_number) break;
  }
}

/**
 * Strip function, delete the specified characters on both sides of the string.
 */
inline std::string &strip(std::string &s,
                          const std::string &characters = " \t\r\n") {
  s.erase(0, s.find_first_not_of(characters));
  return s.erase(s.find_last_not_of(characters) + 1);
}

static inline std::map<int, std::string> ParserEnumDefine(
    const std::string &define_str) {
  int cur_num = 0;
  std::string cur_item_str;
  std::map<int, std::string> result_map;
  split_string_for_each(define_str, ",", [&](int num, const std::string &str) {
    split_string_for_each(
        str, "=",
        [&](int num, const std::string &str) {
          if (num == 0) cur_item_str = str;
          if (num == 1) cur_num = std::stoi(str);
        },
        2);
    result_map.emplace(cur_num, strip(cur_item_str));
    cur_num++;
  });
  return result_map;
}

}  // namespace EnumString

/**
 * Example:
 * @code
 * @endcode
 */
#define ENUM_WITH_STRING(Name, ...)                                     \
  enum class Name { __VA_ARGS__, __COUNT };                             \
  static inline const std::string &to_string(Name value) {              \
    static const auto map = EnumString::ParserEnumDefine(#__VA_ARGS__); \
    static const std::string cannot_converted =                         \
        "Cannot be converted to string";                                \
    int int_value = (int)value;                                         \
    if (map.count(int_value))                                           \
      return map.at(int_value);                                         \
    else                                                                \
      return cannot_converted;                                          \
  }

你可以像这样使用它:

#include <iostream>
#include "enum_with_string.h"
ENUM_WITH_STRING(Animal, dog, cat, monkey = 50, fish, human = 100, duck)
int main() {
  std::cout << to_string(Animal::dog) << std::endl;
  std::cout << to_string(Animal::cat) << std::endl;
  std::cout << to_string(Animal::monkey) << std::endl;
  std::cout << to_string(Animal::fish) << std::endl;
  std::cout << to_string(Animal::human) << std::endl;
  std::cout << to_string(Animal::duck) << std::endl;
}

我有一个 github gist


T
Tom

有一种非常简单的方法可以重复这样的枚举定义:

#ifndef ENUM_ITEMS
...
enum myEnum
{
#ifndef ENUM_ITEM
#define ENUM_ITEM(i) i
#endif // !ENUM_ITEM
#endif // !ENUM_ITEMS trick: ENUM_ITEM(i) = ENUM_ITEMS ? #i : i
    ENUM_ITEM(DEFINITION),
    ...
    ENUM_ITEM(DEFINITION_N)
#ifndef ENUM_ITEMS
};
...
#endif // !ENUM_ITEMS

然后您可以通过再次包含文件来重用它

#define ENUM_ITEMS
#define ENUM_ITEM(i) i
enum myEnum
{
#include "myCpp.cpp"
};
#undef ENUM_ITEM
    static const char* myEnum[] =
    {
#define ENUM_ITEM(i) #i
#include "myCpp.cpp"
// Include full file with defined ENUM_ITEMS => get enum items without code around
    };
    int max = sizeof(myEnum) / sizeof(char*);

Nice is Go To Definition 会在这种情况下找到合适的行...

那么相当可移植的 Enum 类实现呢?为了便于理解,它没有进行太多优化。

#define FOREACH_FRUIT(item) \
        item(apple)   \
        item(orange)  \
        item(grape, 5)   \
        item(banana)  \

无需重复或更新定义副本。

class EnumClass
{
#define GENERATE_ENUM(ENUM, ...) ENUM,
#define GENERATE_STRINGS(STRING, ...) { #STRING, ##__VA_ARGS__ },
#define GENERATE_SIZE(...) + 1
public:
    enum Enum {
        FOREACH_FRUIT(GENERATE_ENUM) // apple, orange, grape, banana,
    } _;
    EnumClass(Enum init)
    {
        _ = init; // grape(2)
        _EnumItem build[itemsNo] = { FOREACH_FRUIT(GENERATE_STRINGS) }; // _EnumItem build[itemsNo] = { { "apple"  }, { "orange"  }, { "grape",5 }, { "banana"  }, };
        int pos = 0;
        for (int i = 0; i < itemsNo; i++)
        {
            items[i].Name = build[i].Name;
            if (0 == build[i].No) {
                items[i].No = pos;
                for (int j = i; j--;)
                {
                    if (items[j].No == pos)
                        throw "Existing item # !";
                }
                pos++;
            }
            else {
                int destPos = build[i].No;
                if (destPos < pos) {
                    for (int j = 0; j < i; j++)
                    {
                        if (items[j].No == destPos)
                            throw "Existing item # !";
                    }
                }
                items[i].No = destPos;
                pos = destPos + 1;
            }
        }
    }
    operator int()
    {
        return items[_].No;
    }
    operator char*()
    {
        return items[_].Name;
    }
    EnumClass& operator ++(int)
    {
        if (_ == itemsNo - 1) {
            throw "Out of Enum options !";
        }
        _ = static_cast<EnumClass::Enum>(_ + 1);
        return *this;
    }
    EnumClass& operator --(int)
    {
        if (0 == _) {
            throw "Out of Enum options !";
        }
        _ = static_cast<EnumClass::Enum>(_ - 1);
        return *this;
    }
    EnumClass operator =(int right)
    {
        for (int i = 0; i < itemsNo; i++)
        {
            if (items[i].No == right)
            {
                _ = static_cast<EnumClass::Enum>(i);
                return *this;
            }
        }
        throw "Enum option does not exist !";
    }
    EnumClass operator =(char *right)
    {
        for (int i = 0; i < itemsNo; i++)
        {
            if (!strcmp(items[i].Name, right))
            {
                _ = static_cast<EnumClass::Enum>(i);
                return *this;
            }
        }
        throw "Enum option does not exist !";
    }
protected:
    static const int itemsNo = FOREACH_FRUIT(GENERATE_SIZE); // + 1 + 1 + 1 + 1; 
    struct _EnumItem {
        char *Name;
        int No;
    } items[itemsNo]; // { Name = "apple" No = 0 }, { Name = "orange" No = 1 } ,{ Name = "grape" No = 5 } ,{ Name = "banana" No = 6 }

#undef GENERATE_ENUM
#undef GENERATE_STRINGS
#undef GENERATE_SIZE
};

现在您可以执行任何常见操作 + 检查定义和运行时操作:

int main()
{
    EnumClass ec(EnumClass::grape);
    ec = "banana"; // ec {_=banana (3)...}
    ec--; // ec {_=grape (2)...}
    char *name = ec;
    int val = ec; // 5
    printf("%s(%i)", name, val); // grape(5)
    return 0;
}

printf 问题...“The compiler does not know, technically, which type is required.


佚名

我最近在供应商库(Fincad)上遇到了同样的问题。幸运的是,供应商为所有枚举提供了 xml 文档。我最终为每个枚举类型生成了一个映射,并为每个枚举提供了一个查找函数。此技术还允许您拦截枚举范围之外的查找。

我确信 swig 可以为您做类似的事情,但我很高兴提供用 ruby 编写的代码生成工具。

这是代码示例:

std::map<std::string, switches::FCSW2::type> init_FCSW2_map() {
        std::map<std::string, switches::FCSW2::type> ans;
        ans["Act365Fixed"] = FCSW2::Act365Fixed;
        ans["actual/365 (fixed)"] = FCSW2::Act365Fixed;
        ans["Act360"] = FCSW2::Act360;
        ans["actual/360"] = FCSW2::Act360;
        ans["Act365Act"] = FCSW2::Act365Act;
        ans["actual/365 (actual)"] = FCSW2::Act365Act;
        ans["ISDA30360"] = FCSW2::ISDA30360;
        ans["30/360 (ISDA)"] = FCSW2::ISDA30360;
        ans["ISMA30E360"] = FCSW2::ISMA30E360;
        ans["30E/360 (30/360 ISMA)"] = FCSW2::ISMA30E360;
        return ans;
}
switches::FCSW2::type FCSW2_lookup(const char* fincad_switch) {
        static std::map<std::string, switches::FCSW2::type> switch_map = init_FCSW2_map();
        std::map<std::string, switches::FCSW2::type>::iterator it = switch_map.find(fincad_switch);
        if(it != switch_map.end()) {
                return it->second;
        } else {
                throw FCSwitchLookupError("Bad Match: FCSW2");
        }
}

似乎您想采用另一种方式(枚举到字符串,而不是字符串到枚举),但这应该是微不足道的。

-惠特


a) 有没有其他人觉得这绝对不可读?一些 typedef 和 using 声明将大大提高可读性。 b) 局部静态声明不是线程安全的。 c) 使用 const string& 而不是 char*, d) 包含在抛出的异常中找不到的值怎么样?
佚名

看看以下语法是否适合您:

// WeekEnd enumeration
enum WeekEnd
{
    Sunday = 1,
    Saturday = 7
};

// String support for WeekEnd
Begin_Enum_String( WeekEnd )
{
    Enum_String( Sunday );
    Enum_String( Saturday );
}
End_Enum_String;

// Convert from WeekEnd to string
const std::string &str = EnumString<WeekEnd>::From( Saturday );
// str should now be "Saturday"

// Convert from string to WeekEnd
WeekEnd w;
EnumString<WeekEnd>::To( w, "Sunday" );
// w should now be Sunday

如果是这样,那么您可能需要查看这篇文章:
http://www.gamedev.net/reference/snippets/features/cppstringizing/


r
rmawatson

这个正确的旧混乱是我基于 SO 的点点滴滴的努力。 for_each 必须扩展以支持超过 20 个枚举值。在 Visual Studio 2019、clang 和 gcc 上对其进行了测试。 c++11

#define _enum_expand(arg) arg
#define _enum_select_for_each(_,_0, _1, _2,_3,_4, _5, _6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,N, ...) N
#define _enum_for_each_0(_call, arg0,arg1,...)
#define _enum_for_each_1(_call, arg0,arg1) _call(arg0,arg1)
#define _enum_for_each_2(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_1(_call,arg0, __VA_ARGS__))
#define _enum_for_each_3(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_2(_call,arg0, __VA_ARGS__))
#define _enum_for_each_4(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_3(_call,arg0, __VA_ARGS__))
#define _enum_for_each_5(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_4(_call,arg0, __VA_ARGS__))
#define _enum_for_each_6(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_5(_call,arg0, __VA_ARGS__))
#define _enum_for_each_7(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_6(_call,arg0, __VA_ARGS__))
#define _enum_for_each_8(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_7(_call,arg0, __VA_ARGS__))
#define _enum_for_each_9(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_8(_call,arg0, __VA_ARGS__))
#define _enum_for_each_10(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_9(_call,arg0, __VA_ARGS__))
#define _enum_for_each_11(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_10(_call,arg0, __VA_ARGS__))
#define _enum_for_each_12(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_11(_call,arg0, __VA_ARGS__))
#define _enum_for_each_13(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_12(_call,arg0, __VA_ARGS__))
#define _enum_for_each_14(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_13(_call,arg0, __VA_ARGS__))
#define _enum_for_each_15(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_14(_call,arg0, __VA_ARGS__))
#define _enum_for_each_16(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_15(_call,arg0, __VA_ARGS__))
#define _enum_for_each_17(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_16(_call,arg0, __VA_ARGS__))
#define _enum_for_each_18(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_17(_call,arg0, __VA_ARGS__))
#define _enum_for_each_19(_call, arg0,arg1, ...) _call(arg) _enum_expand(_enum_for_each_18(_call,arg0, __VA_ARGS__))
#define _enum_for_each(arg, ...) \
    _enum_expand(_enum_select_for_each(_, ##__VA_ARGS__, \
    _enum_for_each_19, _enum_for_each_18, _enum_for_each_17, _enum_for_each_16, _enum_for_each_15, \
    _enum_for_each_14, _enum_for_each_13, _enum_for_each_12, _enum_for_each_11, _enum_for_each_10, \
    _enum_for_each_9,  _enum_for_each_8,  _enum_for_each_7,  _enum_for_each_6,  _enum_for_each_5,  \
    _enum_for_each_4,  _enum_for_each_3,  _enum_for_each_2,  _enum_for_each_1,  _enum_for_each_0)(arg, ##__VA_ARGS__))

#define _enum_strip_args_1(arg0) arg0
#define _enum_strip_args_2(arg0, arg1) arg0, arg1
#define _enum_make_args(...) (__VA_ARGS__)

#define _enum_elem_arity1_1(arg) arg,
#define _enum_elem_arity1( ...) _enum_expand(_enum_elem_arity1_1 __VA_ARGS__)
#define _enum_elem_arity2_1(arg0,arg1) arg0 = arg1,
#define _enum_elem_arity2( ...) _enum_expand(_enum_elem_arity2_1 __VA_ARGS__)

#define _enum_elem_select_arity_2(_0, _1, NAME,...) NAME
#define _enum_elem_select_arity_1(...) _enum_expand(_enum_elem_select_arity_2(__VA_ARGS__, _enum_elem_arity2,_enum_elem_arity1,_))
#define _enum_elem_select_arity(enum_type,...) _enum_expand(_enum_elem_select_arity_1 __VA_ARGS__)(__VA_ARGS__)

#define _enum_str_arity1_1(enum_type,arg) { enum_type::arg,#arg },
#define _enum_str_arity1(enum_type,...) _enum_expand(_enum_str_arity1_1 _enum_make_args( enum_type, _enum_expand(_enum_strip_args_1 __VA_ARGS__)))
#define _enum_str_arity2_1(enum_type,arg,value) { enum_type::arg,#arg },
#define _enum_str_arity2(enum_type, ...) _enum_expand(_enum_str_arity2_1 _enum_make_args( enum_type, _enum_expand(_enum_strip_args_2 __VA_ARGS__)))
#define _enum_str_select_arity_2(_0, _1, NAME,...) NAME
#define _enum_str_select_arity_1(...) _enum_expand(_enum_str_select_arity_2(__VA_ARGS__, _enum_str_arity2,_enum_str_arity1,_))
#define _enum_str_select_arity(enum_type,...) _enum_expand(_enum_str_select_arity_1 __VA_ARGS__)(enum_type,__VA_ARGS__)

#define error_code_enum(enum_type,...)  enum class enum_type {              \
    _enum_expand(_enum_for_each(_enum_elem_select_arity,enum_type, ##__VA_ARGS__))};  \
    namespace _ ## enum_type ## _detail { \
        template <typename> struct _ ## enum_type ## _error_code{ \
            static const std::map<enum_type, const char*> enum_type ## _map; \
        }; \
            template <typename T> \
            const std::map<enum_type, const char*> _ ## enum_type ## _error_code<T>::enum_type ## _map = { \
                _enum_expand(_enum_for_each(_enum_str_select_arity,enum_type,  ##__VA_ARGS__)) \
        }; \
    } \
    inline const char* get_error_code_name(const enum_type& value) { \
        return _ ## enum_type ## _detail::_ ## enum_type ## _error_code<enum_type>::enum_type ## _map.find(value)->second; \
    } 

error_code_enum(myenum,
    (one, 1),
    (two)
);

产生以下代码

enum class myenum { 
    one = 1,
    two,
};
namespace _myenum_detail {
    template <typename>
    struct _myenum_error_code {
        static const std::map<myenum, const char*> myenum_map;
    };
    template <typename T>
    const std::map<myenum, const char*> _myenum_error_code<T>::myenum_map = {
        { myenum::one, "one" }, 
        { myenum::two, "two" },
    };
}
inline const char* get_error_code_name(const myenum& value) { 
    return _myenum_detail::_myenum_error_code<myenum>::myenum_map.find(value)->second; 
}

真是太可惜了,你不得不用预处理器跳起来,用世界上最常用的编程语言之一来做这件事……