enum to string in modern C++11 / C++14 / C++17 and future C++20

后端 未结 28 1930
逝去的感伤
逝去的感伤 2020-11-22 16:57

Contrary to all other similar questions, this question is about using the new C++ features.

  • 2008 c Is there a simple way to convert C++ enum to string?
  • <
28条回答
  •  南笙
    南笙 (楼主)
    2020-11-22 17:23

    I don't know if you're going to like this or not, I'm not pretty happy with this solution but it is a C++14 friendly approach because it is using template variables and abusing template specialization:

    enum class MyEnum : std::uint_fast8_t {
       AAA,
       BBB,
       CCC,
    };
    
    template const char MyEnumName[] = "Invalid MyEnum value";
    template<> const char MyEnumName[] = "AAA";
    template<> const char MyEnumName[] = "BBB";
    template<> const char MyEnumName[] = "CCC";
    
    int main()
    {
        // Prints "AAA"
        std::cout << MyEnumName << '\n';
        // Prints "Invalid MyEnum value"
        std::cout << MyEnumName(0x12345678)> << '\n';
        // Well... in fact it prints "Invalid MyEnum value" for any value
        // different of MyEnum::AAA, MyEnum::BBB or MyEnum::CCC.
    
        return 0;
    }
    

    The worst about this approach is that is a pain to maintain, but it is also a pain to maintain some of other similar aproaches, aren't they?

    Good points about this aproach:

    • Using variable tempates (C++14 feature)
    • With template specialization we can "detect" when an invalid value is used (but I'm not sure if this could be useful at all).
    • It looks neat.
    • The name lookup is done at compile time.

    Live example

    Edit

    Misterious user673679 you're right; the C++14 variable template approach doesn't handles the runtime case, it was my fault to forget it :(

    But we can still use some modern C++ features and variable template plus variadic template trickery to achieve a runtime translation from enum value to string... it is as bothersome as the others but still worth to mention.

    Let's start using a template alias to shorten the access to a enum-to-string map:

    // enum_map contains pairs of enum value and value string for each enum
    // this shortcut allows us to use enum_map.
    template 
    using enum_map = std::map;
    
    // This variable template will create a map for each enum type which is
    // instantiated with.
    template 
    enum_map enum_values{};
    

    Then, the variadic template trickery:

    template 
    void initialize() {}
    
    template 
    void initialize(const ENUM value, const char *name, args ... tail)
    {
        enum_values.emplace(value, name);
        initialize(tail ...);
    }
    

    The "best trick" here is the use of variable template for the map which contains the values and names of each enum entry; this map will be the same in each translation unit and have the same name everywhere so is pretty straightforward and neat, if we call the initialize function like this:

    initialize
    (
        MyEnum::AAA, "AAA",
        MyEnum::BBB, "BBB",
        MyEnum::CCC, "CCC"
    );
    

    We are asigning names to each MyEnum entry and can be used in runtime:

    std::cout << enum_values[MyEnum::AAA] << '\n';
    

    But can be improved with SFINAE and overloading << operator:

    template::value>::type>
    std::ostream &operator <<(std::ostream &o, const ENUM value)
    {
        static const std::string Unknown{std::string{typeid(ENUM).name()} + " unknown value"};
        auto found = enum_values.find(value);
    
        return o << (found == enum_values.end() ? Unknown : found->second);
    }
    

    With the correct operator << now we can use the enum this way:

    std::cout << MyEnum::AAA << '\n';
    

    This is also bothersome to maintain and can be improved, but hope you get the idea.

    Live example

提交回复
热议问题