【问题标题】:How to easily map c++ enums to strings如何轻松地将 C++ 枚举映射到字符串
【发布时间】:2010-09-17 12:04:45
【问题描述】:

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

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

【问题讨论】:

标签: c++ templates enums


【解决方案1】:

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

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

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

函数template &lt;typename T&gt; map_init(T&amp;) 返回一个map_init_helper&lt;T&gt;map_init_helper&lt;T&gt; 存储一个 T&,并定义平凡的 map_init_helper&amp; operator()(typename T::key_type const&amp;, typename T::value_type const&amp;)。 (从operator() 返回*this 允许链接operator(),如operator&lt;&lt; on std::ostreams)

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

由于函数和辅助类是模板化的,您可以将它们用于任何地图或类似地图的结构。 IE。它还可以向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 : 插入(MyMap)(eValue1, "A")(eValue2, "B")... (boost.org/doc/libs/1_35_0/libs/assign/doc/index.html)
【解决方案2】:

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&lt;T, const char*&gt; enumToString'
  • 实际上这个答案在 C++11 中基本上已经过时了。
【解决方案3】:

从另一个表单自动生成一个表单。

来源:

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

生成:

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

如果枚举值很大,则生成的表单可以使用 unordered_map 或 Constantin 建议的模板。

来源:

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(至少)不会这样做。
【解决方案4】:

我建议混合使用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)

color.def

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

【讨论】:

  • 有没有办法使枚举字符串数组定义通用? (我不知道如何处理宏内部的X-Macro,也不容易处理模板)
  • stackoverflow.com/questions/70455489/… 非常相似,但有所改进并使用标准预处理器...
【解决方案5】:

我记得我在 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 中看到过类似的东西
【解决方案6】:

我使用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
【解决方案7】:

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

这是两个很好的方法,即使还不够为人所知,

wise_enum

  • 用于 C++11/14/17 的独立智能枚举库。它支持您对 C++ 中的智能枚举类所期望的所有标准功能。
  • 限制:至少需要 C++11。

Better Enums

  • 反射式编译时枚举库,语法简洁,在单个头文件中,没有依赖关系。
  • 限制:基于宏,不能在类中使用。

【讨论】:

    【解决方案8】:

    如果您想获得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() 是没有必要的。看我的回答。
    【解决方案9】:

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

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

    【讨论】:

    • 您是否建议使用字符串而不使用枚举?这并不能真正解决问题。
    【解决方案10】:

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

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

    一切都完成了。

    【讨论】:

      【解决方案11】:

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

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

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

      【讨论】:

        【解决方案12】:

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

        1. 枚举的每个值只写一次,所以不需要维护双重列表

        2. 不要将枚举值保存在稍后#included 的单独文件中,这样我可以将其写入任何我想要的地方

        3. 不要替换枚举本身,我仍然希望定义枚举类型,但除此之外我希望能够将每个枚举名称映射到相应的字符串(不影响遗留代码)

        4. 搜索应该很快,所以对于那些巨大的枚举最好不要使用 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); 
        }
        

        干杯。

        【讨论】:

          【解决方案13】:

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

          定义:

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

          虽然不确定此方案的局限性...欢迎使用 cmets!

          【讨论】:

            【解决方案14】:

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

            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.
            【解决方案15】:

            在标题中:

            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 文件是从其他输入文件自动生成的。我很想看到更好的解决方案(不诉诸不必要的复杂性)
            【解决方案16】:

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

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

            【讨论】:

              【解决方案17】:
              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

              【讨论】:

                【解决方案18】:
                #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,这似乎有点多余,因为枚举已经在结构内部命名。您可能是对的,也许更好的编辑是更改枚举访问行?
                • 给出一个想法,它可以是枚举类(我的偏好)和强制转换或只是枚举。好处是它很简单,您只需定义一次项目。另一种选择是直接使用静态向量而不使用函数包装器。
                【解决方案19】:

                我最近在供应商库 (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) 包含在抛出的异常中找不到的值怎么样?
                【解决方案20】:

                看看下面的语法是否适合你:

                // 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/

                【讨论】:

                  【解决方案21】:

                  这个正确的旧混乱是我基于 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; 
                  }
                  

                  太可惜了,你不得不用世界上最常用的编程语言之一来使用预处理器来完成这项工作......

                  【讨论】:

                    【解决方案22】:

                    那么可移植的 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.

                    【讨论】:

                      【解决方案23】:

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

                      // 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

                      【讨论】:

                        猜你喜欢
                        • 2021-11-09
                        • 2019-01-29
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2011-04-14
                        相关资源
                        最近更新 更多