【问题标题】:Enum to string in C++11枚举到 C++11 中的字符串
【发布时间】:2014-02-22 17:49:04
【问题描述】:

我在 SO 上不止一次意识到这个 has been asked before,但我找不到明确的问题来寻找 C++11 的当前解决方案,所以我们再来一次..

我们能否用 C++11 方便地获取枚举的字符串值?

即(现在)C++11 中是否有任何内置功能允许我们获得枚举类型的字符串表示,如

typedef enum {Linux, Apple, Windows} OS_type; 
OS_type myOS = Linux;

cout << myOS

那会在控制台上打印Linux

【问题讨论】:

  • 这方面可能没有任何变化。
  • 由于c++11没有加reflection,我怀疑现在有更好的解决方案。
  • 您提到的原始问题还包含更新的 C++11 方法,如果您只是滚动过去前 2 或 3 个答案。
  • 不能直接对变量名或类型名进行字符串化。您必须使用模板(为每个名称专门化它)、宏或这些的组合。在 C++11 中并没有真正改变,afaik 在 C++14 中也不会。

标签: c++11 enums


【解决方案1】:

在 C++(和 C)中长期且不必要地缺乏通用的枚举到字符串功能是一个痛苦的问题。 C++11 没有解决这个问题,据我所知,C++14 也没有。

我个人会使用代码生成来解决这个问题。 C 预处理器是一种方式——您可以在此处的 cmets 中查看链接的其他一些答案。但实际上我更喜欢专门为枚举编写自己的代码生成。然后它可以根据需要轻松生成to_string (char*)from_stringostream operator&lt;&lt;istream operator&lt;&lt;is_valid 和更多方法。这种方法可以非常灵活和强大,但它在项目中的许多枚举之间强制执行绝对一致性,并且不会产生运行时成本。

使用 Python 出色的“mako”包,或者如果你喜欢轻量级,或者在 Lua 中,或者如果你反对依赖,则使用 CPP,或者 CMake 自己的代码生成工具。方法很多,但归根结底都是同一件事:您需要自己生成代码——C++ 不会为您执行此操作(很遗憾)。

【讨论】:

  • @Industrial-antidepressant:很有趣,但你不觉得把它变成 C++ 还太遥远,因为它的标准化相当保守?附言你有一个错字:“swicth”。
  • 我不知道。他们说这将在 Rapperswil 被考虑
【解决方案2】:

这是一个使用命名空间和结构的简单示例。 为每个枚举项创建一个类。在本例中,我选择了int 作为 id 的类型。

#include <iostream>
using namespace std;

#define ENUMITEM(Id, Name) \
struct Name {\
    static constexpr const int id = Id;\
    static constexpr const char* name = #Name;\
};

namespace Food {
ENUMITEM(1, Banana)
ENUMITEM(2, Apple)
ENUMITEM(3, Orange)
}

int main() {
    cout << Food::Orange::id << ":" << Food::Orange::name << endl;
    return 0;
}

输出:

3:Orange

== 更新 ==

使用:

#define STARTENUM() constexpr const int enumStart = __LINE__;
#define ENUMITEM(Name) \
struct Name {\
    static constexpr const int id = __LINE__ - enumStart - 1;\
    static constexpr const char* name = #Name;\
};

在第一次使用 ENUMITEM 之前使用它一次,就不再需要 id。

namespace Food {
STARTENUM()
ENUMITEM(Banana)
ENUMITEM(Apple)
ENUMITEM(Orange)
}

变量 enumStart 只能通过命名空间访问 - 所以仍然可以使用多个枚举。

【讨论】:

    【解决方案3】:

    我喜欢使用 C 预处理器的 hack,我在这里第一次看到它: http://blogs.msdn.com/b/vcblog/archive/2008/04/30/enums-macros-unicode-and-token-pasting.aspx.

    它使用标记粘贴运算符#。

    // This code defines the enumerated values:
    
    #define MY_ENUM(x) x,
    enum Fruit_Type {
    MY_ENUM(Banana)
    MY_ENUM(Apple)
    MY_ENUM(Orange)
    };
    #undef MY_ENUM
    
    // and this code defines an array of string literals for them:
    
    #define MY_ENUM(x) #x,
            const char* const fruit_name[] = {
    MY_ENUM(Banana)
    MY_ENUM(Apple)
    MY_ENUM(Orange)
            };
    #undef MY_ENUM
    
    // Finally, here is some client code:
    
    std::cout << fruit_name[Banana] << " is enum #" << Banana << "\n";
    
    // In practice, those three "MY_ENUM" macro calls will be inside an #include file.
    

    坦率地说,这很丑陋。但您最终会在包含文件中准确地键入一次枚举,这样更易​​于维护。

    顺便说一句,在那个 MSDN 博客链接(见上文)上,一位用户用一个技巧发表了评论,使整个事情变得更漂亮,并避免#includes:

    #define Fruits(FOO) \
    FOO(Apple) \
    FOO(Banana) \
    FOO(Orange)
    
    #define DO_DESCRIPTION(e)  #e,
    #define DO_ENUM(e)  e,
    
    char* FruitDescription[] = {
    Fruits(DO_DESCRIPTION)
    };
    
    enum Fruit_Type {
    Fruits(DO_ENUM)
    };
    
    // Client code:
    
    std::cout << FruitDescription[Banana] << " is enum #" << Banana << "\n";
    

    (我刚刚注意到0x17de的回答也使用了token-pasing操作符)

    【讨论】:

    • '坦率地说,它很丑' - 事实上,它是 ;-) 我们不能把它作为 C++1X 的一部分作为内置语言特性来获取吗?!叹息。
    • 我知道...它应该自行下载、安装、激活漂亮的窗口,其中包含一些 Ruby on Rails 编写的动画,您可以在其中更改标题。不幸的是,它提醒我们它仍在编写一些机器指令。
    【解决方案4】:

    在我看来,最可维护的方法是编写一个辅助函数:

    const char* get_name(OS_type os) {
      switch (os) {
      case Linux: return "Linux";
      case Apple: return "Apple";
      case Windows: return "Windows";
      }
    }
    

    最好实现“默认”案例,因为这样做可以确保在您忘记实现案例时收到编译器警告(使用正确的编译器和编译器设置) )。

    【讨论】:

    • 是的,好吧 - 当然你可以手动处理所有事情,但这不是问题的重点,是吗?
    • 我的假设是 OP 想要编写高效、可维护的代码。使用 enum+switch,您可以获得效率和(使用正确的编译器),如果您忘记处理案例,则会发出警告。
    【解决方案5】:

    你可以使用宏来解决这个问题:

    #define MAKE_ENUM(name, ...) enum class name { __VA_ARGS__}; \
    static std::vector<std::string> Enum_##name##_init(){\
        const std::string content = #__VA_ARGS__; \
        std::vector<std::string> str;\
        size_t len = content.length();\
        std::ostringstream temp;\
        for(size_t i = 0; i < len; i ++) {\
        if(isspace(content[i])) continue;\
        else if(content[i] == ',') {\
        str.push_back(temp.str());\
        temp.str(std::string());}\
        else temp<< content[i];}\
        str.push_back(temp.str());\
        return str;}\
    static const std::vector<std::string> Enum_##name##_str_vec = Enum_##name##_init();\
    static std::string to_string(name val){\
        return Enum_##name##_str_vec[static_cast<size_t>(val)];\
    }\
    static std::string print_all_##name##_enum(){\
        int count = 0;\
        std::string ans;\
        for(auto& item:Enum_##name##_str_vec)\
        ans += std::to_string(count++) + ':' + item + '\n';\
        return ans;\
    }
    

    由于静态变量只能初始化一次,所以Enum_##name##_str_vec首先会使用Enum_##name##_init()函数来初始化自己。

    示例代码如下:

    MAKE_ENUM(Analysis_Time_Type,
                      UNKNOWN,
                      REAL_TIME, 
                      CLOSSING_TIME 
    );
    

    然后你可以使用下面的语句来打印一个枚举值:

    to_string(Analysis_Time_Type::UNKNOWN)
    

    并使用以下句子将所有枚举打印为字符串:

    print_all_Analysis_Time_Type_enum()
    

    【讨论】:

    【解决方案6】:

    如前所述,没有标准的方法可以做到这一点。但是通过一点预处理器魔法(类似于 AlejoHausner 的第二个贡献)和一些模板魔法,它可以相当优雅。

    包含此代码一次:

    #include <string>
    #include <algorithm>
    
    #define ENUM_VALS( name ) name,
    #define ENUM_STRINGS( name ) # name,
    
    /** Template function to return the enum value for a given string
    *  Note: assumes enums are all upper or all lowercase,
    *        that they are contiguous/default-ordered,
    *        and that the first value is the default
    *  @tparam ENUM     type of the enum to retrieve
    *  @tparam ENUMSIZE number of elements in the enum (implicit; need not be passed in)
    *  @param valStr   string version of enum value to convert; may be any capitalization (capitalization may be modified)
    *  @param enumStrs array of strings corresponding to enum values, assumed to all be in lower/upper case depending upon
    *  enumsUpper
    *  @param enumsUpper true if the enum values are in all uppercase, false if in all lowercase (mixed case not supported)
    *  @return enum value corresponding to valStr, or the first enum value if not found
    */
    template <typename ENUM, size_t ENUMSIZE>
    static inline ENUM fromString(std::string &valStr, const char *(&enumStrs)[ENUMSIZE], bool enumsUpper = true) {
        ENUM e = static_cast< ENUM >(0); // by default, first value
        // convert valStr to lower/upper-case
        std::transform(valStr.begin(), valStr.end(), valStr.begin(), enumsUpper ? ::toupper : ::tolower);
        for (size_t i = 0; i< ENUMSIZE; i++) {
            if (valStr == std::string(enumStrs[i])) {
                e = static_cast< ENUM >(i);
                break;
            }
        }
        return e;
    }
    

    然后像这样定义每个枚举:

    //! Define ColorType enum with array for converting to/from strings
    #define ColorTypes(ENUM) \
        ENUM(BLACK) \
        ENUM(RED) \
        ENUM(GREEN) \
        ENUM(BLUE)
    enum ColorType {
        ColorTypes(ENUM_VALS)
    };
    static const char* colorTypeNames[] = {
        ColorTypes(ENUM_STRINGS)
    };
    

    您只需枚举枚举值一次,定义它的代码相当紧凑和直观。

    值必须以默认方式编号(即,0、1、2、...)。 fromString() 的代码假定枚举值全部为大写或全部小写(用于从字符串转换),默认值是第一个,但您当然可以更改这些事情的处理方式。

    以下是获取字符串值的方法:

    ColorType c = ColorType::BLUE;   
    std::cout << colorTypeNames[c]; // BLUE
    

    这是从字符串值设置枚举的方法:

    ColorType c2 = fromString<ColorType>("Green", colorTypeNames); // == ColorType::GREEN
    

    【讨论】:

      猜你喜欢
      • 2011-11-02
      • 2010-10-18
      • 1970-01-01
      • 1970-01-01
      • 2011-09-11
      • 1970-01-01
      • 2015-05-03
      相关资源
      最近更新 更多