在现代C ++ 11 / C ++ 14 / C ++ 17和将来的C ++ 20中枚举字符串

与所有其他类似的问题相反,这个问题是关于使用新的C ++特性的。

  • 2008 c有没有简单的方法来将C ++枚举转换为字符串?
  • 2008 c简单的方法使用枚举类型的变量作为C中的字符串?
  • 2008 c ++如何轻松将c ++枚举映射到字符串
  • 2008 c ++做一些C标识符和字符串?
  • 2008年c ++有没有一个简单的脚本来将C ++枚举转换为字符串?
  • 如何在C ++中使用枚举作为标志?
  • 如何将枚举类型变量转换为字符串?
  • 2011年c ++枚举到字符串C ++
  • 如何将枚举类型变量转换为字符串?
  • 如何将枚举名称转换为c中的字符串
  • 2013 c在C中对条件编译的枚举进行了字符串化
  • 在阅读了很多答案之后,我还没有找到任何答案:

  • 使用C ++ 11,C ++ 14或C ++ 17新特性的优雅方式
  • 或在Boost中准备好使用的东西
  • 除此之外,C ++ 20还有一些计划
  • 一个例子往往比长时间的解释要好。
    你可以在Coliru上编译并运行这段代码。
    (另一个前例也是可用的)

    #include <map>
    #include <iostream>
    
    struct MyClass
    {
        enum class MyEnum : char {
            AAA = -8,
            BBB = '8',
            CCC = AAA + BBB
        };
    };
    
    // Replace magic() by some faster compile-time generated code
    // (you're allowed to replace the return type with std::string
    // if that's easier for you)
    const char* magic (MyClass::MyEnum e)
    {
        const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
            { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
            { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
            { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
        };
        auto   it  = MyEnumStrings.find(e);
        return it == MyEnumStrings.end() ? "Out of range" : it->second;
    }
    
    int main()
    {
       std::cout << magic(MyClass::MyEnum::AAA) <<'n';
       std::cout << magic(MyClass::MyEnum::BBB) <<'n';
       std::cout << magic(MyClass::MyEnum::CCC) <<'n';
    }
    

    约束

  • 请不要重复其他答案或基本链接。
  • 请避免膨胀宏观答案,或尝试尽可能减少#define开销。
  • 请不要手动enum - > string映射。
  • 很高兴有

  • 支持从与零不同的数字开始的enum
  • 支持负面的enum
  • 支持碎片enum
  • 支持class enum (C ++ 11)
  • 支持class enum : <type>具有任何允许的<type> (C ++ 11)
  • 编译时(而不是运行时)转换为字符串,
    或者至少在运行时快速执行(例如std::map不是一个好主意......)
  • constexpr (C ++ 11,在C ++ 14中放松)
  • noexcept (C ++ 11)
  • 片段C ++ 14 / C ++ 17友好
  • C ++最先进的技术
  • 一个可能的想法是使用C ++编译器功能在编译时使用基于variadic template classconstexpr函数的元编程技巧来生成C ++代码。


    这与Yuri Finkelstein相似; 但不需要提升。 我正在使用地图,因此您可以将任何值分配给枚举,任何顺序。

    枚举类声明如下:

    DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
    

    以下代码将自动创建枚举类和重载:

  • '+''+ ='为std :: string
  • '''为流
  • '〜'只是转换为字符串(任何一元运算符都可以,但为了清晰起见,我个人不喜欢它)
  • '*'来获得枚举数
  • 不需要增强功能,提供所有必需的功能。

    码:

    #include <algorithm>
    #include <iostream>
    #include <map>
    #include <sstream>
    #include <string>
    #include <vector>
    
    #define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())
    
    std::vector<std::string> splitString(std::string str, char sep = ',') {
        std::vector<std::string> vecString;
        std::string item;
    
        std::stringstream stringStream(str);
    
        while (std::getline(stringStream, item, sep))
        {
            vecString.push_back(item);
        }
    
        return vecString;
    }
    
    #define DECLARE_ENUM_WITH_TYPE(E, T, ...)                                                                     
        enum class E : T                                                                                          
        {                                                                                                         
            __VA_ARGS__                                                                                           
        };                                                                                                        
        std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__));                                    
        std::ostream &operator<<(std::ostream &os, E enumTmp)                                                     
        {                                                                                                         
            os << E##MapName[static_cast<T>(enumTmp)];                                                            
            return os;                                                                                            
        }                                                                                                         
        size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); }                                 
        std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; }                          
        std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } 
        std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } 
        std::string &operator+=(std::string &str, E enumTmp)                                                      
        {                                                                                                         
            str += E##MapName[static_cast<T>(enumTmp)];                                                           
            return str;                                                                                           
        }                                                                                                         
        E operator++(E &enumTmp)                                                                                  
        {                                                                                                         
            auto iter = E##MapName.find(static_cast<T>(enumTmp));                                                 
            if (iter == E##MapName.end() || std::next(iter) == E##MapName.end())                                  
                iter = E##MapName.begin();                                                                        
            else                                                                                                  
            {                                                                                                     
                ++iter;                                                                                           
            }                                                                                                     
            enumTmp = static_cast<E>(iter->first);                                                                
            return enumTmp;                                                                                       
        }                                                                                                         
        bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }
    
    #define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
    template <typename T>
    std::map<T, std::string> generateEnumMap(std::string strMap)
    {
        STRING_REMOVE_CHAR(strMap, ' ');
        STRING_REMOVE_CHAR(strMap, '(');
    
        std::vector<std::string> enumTokens(splitString(strMap));
        std::map<T, std::string> retMap;
        T inxMap;
    
        inxMap = 0;
        for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
        {
            // Token: [EnumName | EnumName=EnumValue]
            std::string enumName;
            T enumValue;
            if (iter->find('=') == std::string::npos)
            {
                enumName = *iter;
            }
            else
            {
                std::vector<std::string> enumNameValue(splitString(*iter, '='));
                enumName = enumNameValue[0];
                //inxMap = static_cast<T>(enumNameValue[1]);
                if (std::is_unsigned<T>::value)
                {
                    inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
                }
                else
                {
                    inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
                }
            }
            retMap[inxMap++] = enumName;
        }
    
        return retMap;
    }
    

    例:

    DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
    
    int main(void) {
        TestEnumClass first, second;
        first = TestEnumClass::FOUR;
        second = TestEnumClass::TWO;
    
        std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)
    
        std::string strOne;
        strOne = ~first;
        std::cout << strOne << std::endl; // FOUR
    
        std::string strTwo;
        strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
        std::cout << strTwo << std::endl; // Enum-TWOTHREE-test
    
        std::string strThree("TestEnumClass: ");
        strThree += second;
        std::cout << strThree << std::endl; // TestEnumClass: TWO
        std::cout << "Enum count=" << *first << std::endl;
    }
    

    你可以在这里运行代码


    (better_enums库的方法)

    有一种方法可以在当前的C ++中使用枚举来实现,如下所示:

    ENUM(Channel, char, Red = 1, Green, Blue)
    
    // "Same as":
    // enum class Channel : char { Red = 1, Green, Blue };
    

    用法:

    Channel     c = Channel::_from_string("Green");  // Channel::Green (2)
    c._to_string();                                  // string "Green"
    
    for (Channel c : Channel::_values())
        std::cout << c << std::endl;
    
    // And so on...
    

    所有的操作都可以做到constexpr 。 你也可以实现@ecatmur的答案中提到的C ++ 17反射提议。

  • 只有一个宏。 我相信这是最小的可能,因为预处理器字符串化( # )是在当前C ++中将令牌转换为字符串的唯一方法。
  • 这个宏很不引人注意 - 常量声明,包括初始化符,被粘贴到一个内置的枚举声明中。 这意味着它们具有与内置枚举相同的语法和含义。
  • 重复被消除。
  • 由于constexpr原因,至少在C ++ 11中实现是最自然和有用的。 它也可以与C ++ 98 + __VA_ARGS__ 。 这绝对是现代C ++。

  • 宏的定义有些涉及,所以我用几种方式回答了这个问题。

  • 这个答案的大部分是我认为适用于StackOverflow空间约束的实现。
  • 还有一篇CodeProject文章描述了长篇教程中实现的基础知识。 [我应该在这里移动吗? 我认为这对于SO回答来说太过分了]。
  • 有一个功能齐全的库“Better Enums”,它在一个头文件中实现宏。 它还实现了N4428类型属性查询,即C ++ 17反映提案N4113的当前修订版本。 所以,至少对于通过这个宏声明的枚举来说,现在可以在C ++ 11 / C ++ 14中使用建议的C ++ 17枚举反射。
  • 将这个答案扩展到图书馆的功能很简单 - 没有什么“重要的”在这里被遗漏。 然而,它非常乏味,并且存在编译器可移植性问题。

    免责声明 :我是CodeProject文章和库的作者。

    你可以尝试在这个答案中的代码,图书馆,以及在Wandbox中实时在线的N4428的实现。 库文档还包含如何将其用作N4428的概述,该文档解释了该提议的枚举部分。


    说明

    下面的代码实现了枚举和字符串之间的转换。 然而,它也可以扩展到做其他事情,比如迭代。 这个答案将一个枚举包装在一个struct 。 您也可以在枚举旁边生成traits struct

    策略是产生这样的东西:

    struct Channel {
        enum _enum : char { __VA_ARGS__ };
        constexpr static const Channel          _values[] = { __VA_ARGS__ };
        constexpr static const char * const     _names[] = { #__VA_ARGS__ };
    
        static const char* _to_string(Channel v) { /* easy */ }
        constexpr static Channel _from_string(const char *s) { /* easy */ }
    };
    

    问题是:

  • 我们将以{Red = 1, Green, Blue}作为values数组的初始值设定项。 这不是有效的C ++,因为Red不是可分配的表达式。 这是通过将每个常量转换为具有赋值运算符的类型T来解决的,但将会删除赋值: {(T)Red = 1, (T)Green, (T)Blue}
  • 同样,我们将以{"Red = 1", "Green", "Blue"}作为名称数组的初始值设定项。 我们需要修剪" = 1" 。 我没有意识到在编译时做这件事的好方法,所以我们会推迟这段时间。 因此, _to_string不会被constexpr ,但是_from_string仍然可以被constexpr ,因为在与未修改的字符串进行比较时,我们可以将空白和等号作为终止符。
  • 以上两者都需要一个“映射”宏,可以将另一个宏应用于__VA_ARGS__每个元素。 这是非常标准的。 这个答案包括一个简单的版本,可以处理多达8个元素。
  • 如果宏是真正独立的,它需要声明不需要单独定义的静态数据。 实际上,这意味着阵列需要特殊处理。 有两种可能的解决方案:名称空间范围内的constexpr (或者仅是const )数组,或者非constexpr静态内联函数中的常规数组。 此答案中的代码适用于C ++ 11并采用前一种方法。 CodeProject文章适用于C ++ 98,并采用后者。

  • #include <cstddef>      // For size_t.
    #include <cstring>      // For strcspn, strncpy.
    #include <stdexcept>    // For runtime_error.
    
    
    
    // A "typical" mapping macro. MAP(macro, a, b, c, ...) expands to
    // macro(a) macro(b) macro(c) ...
    // The helper macro COUNT(a, b, c, ...) expands to the number of
    // arguments, and IDENTITY(x) is needed to control the order of
    // expansion of __VA_ARGS__ on Visual C++ compilers.
    #define MAP(macro, ...) 
        IDENTITY( 
            APPLY(CHOOSE_MAP_START, COUNT(__VA_ARGS__)) 
                (macro, __VA_ARGS__))
    
    #define CHOOSE_MAP_START(count) MAP ## count
    
    #define APPLY(macro, ...) IDENTITY(macro(__VA_ARGS__))
    
    #define IDENTITY(x) x
    
    #define MAP1(m, x)      m(x)
    #define MAP2(m, x, ...) m(x) IDENTITY(MAP1(m, __VA_ARGS__))
    #define MAP3(m, x, ...) m(x) IDENTITY(MAP2(m, __VA_ARGS__))
    #define MAP4(m, x, ...) m(x) IDENTITY(MAP3(m, __VA_ARGS__))
    #define MAP5(m, x, ...) m(x) IDENTITY(MAP4(m, __VA_ARGS__))
    #define MAP6(m, x, ...) m(x) IDENTITY(MAP5(m, __VA_ARGS__))
    #define MAP7(m, x, ...) m(x) IDENTITY(MAP6(m, __VA_ARGS__))
    #define MAP8(m, x, ...) m(x) IDENTITY(MAP7(m, __VA_ARGS__))
    
    #define EVALUATE_COUNT(_1, _2, _3, _4, _5, _6, _7, _8, count, ...) 
        count
    
    #define COUNT(...) 
        IDENTITY(EVALUATE_COUNT(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1))
    
    
    
    // The type "T" mentioned above that drops assignment operations.
    template <typename U>
    struct ignore_assign {
        constexpr explicit ignore_assign(U value) : _value(value) { }
        constexpr operator U() const { return _value; }
    
        constexpr const ignore_assign& operator =(int dummy) const
            { return *this; }
    
        U   _value;
    };
    
    
    
    // Prepends "(ignore_assign<_underlying>)" to each argument.
    #define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
    #define IGNORE_ASSIGN(...) 
        IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))
    
    // Stringizes each argument.
    #define STRINGIZE_SINGLE(e) #e,
    #define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))
    
    
    
    // Some helpers needed for _from_string.
    constexpr const char    terminators[] = " =trn";
    
    // The size of terminators includes the implicit ''.
    constexpr bool is_terminator(char c, size_t index = 0)
    {
        return
            index >= sizeof(terminators) ? false :
            c == terminators[index] ? true :
            is_terminator(c, index + 1);
    }
    
    constexpr bool matches_untrimmed(const char *untrimmed, const char *s,
                                     size_t index = 0)
    {
        return
            is_terminator(untrimmed[index]) ? s[index] == '' :
            s[index] != untrimmed[index] ? false :
            matches_untrimmed(untrimmed, s, index + 1);
    }
    
    
    
    // The macro proper.
    //
    // There are several "simplifications" in this implementation, for the
    // sake of brevity. First, we have only one viable option for declaring
    // constexpr arrays: at namespace scope. This probably should be done
    // two namespaces deep: one namespace that is likely to be unique for
    // our little enum "library", then inside it a namespace whose name is
    // based on the name of the enum to avoid collisions with other enums.
    // I am using only one level of nesting.
    //
    // Declaring constexpr arrays inside the struct is not viable because
    // they will need out-of-line definitions, which will result in
    // duplicate symbols when linking. This can be solved with weak
    // symbols, but that is compiler- and system-specific. It is not
    // possible to declare constexpr arrays as static variables in
    // constexpr functions due to the restrictions on such functions.
    //
    // Note that this prevents the use of this macro anywhere except at
    // namespace scope. Ironically, the C++98 version of this, which can
    // declare static arrays inside static member functions, is actually
    // more flexible in this regard. It is shown in the CodeProject
    // article.
    //
    // Second, for compilation performance reasons, it is best to separate
    // the macro into a "parametric" portion, and the portion that depends
    // on knowing __VA_ARGS__, and factor the former out into a template.
    //
    // Third, this code uses a default parameter in _from_string that may
    // be better not exposed in the public interface.
    
    #define ENUM(EnumName, Underlying, ...)                               
    namespace data_ ## EnumName {                                         
        using _underlying = Underlying;                                   
        enum { __VA_ARGS__ };                                             
                                                                          
        constexpr const size_t           _size =                          
            IDENTITY(COUNT(__VA_ARGS__));                                 
                                                                          
        constexpr const _underlying      _values[] =                      
            { IDENTITY(IGNORE_ASSIGN(__VA_ARGS__)) };                     
                                                                          
        constexpr const char * const     _raw_names[] =                   
            { IDENTITY(STRINGIZE(__VA_ARGS__)) };                         
    }                                                                     
                                                                          
    struct EnumName {                                                     
        using _underlying = Underlying;                                   
        enum _enum : _underlying { __VA_ARGS__ };                         
                                                                          
        const char * _to_string() const                                   
        {                                                                 
            for (size_t index = 0; index < data_ ## EnumName::_size;      
                 ++index) {                                               
                                                                          
                if (data_ ## EnumName::_values[index] == _value)          
                    return _trimmed_names()[index];                       
            }                                                             
                                                                          
            throw std::runtime_error("invalid value");                    
        }                                                                 
                                                                          
        constexpr static EnumName _from_string(const char *s,             
                                               size_t index = 0)          
        {                                                                 
            return                                                        
                index >= data_ ## EnumName::_size ?                       
                        throw std::runtime_error("invalid identifier") :  
                matches_untrimmed(                                        
                    data_ ## EnumName::_raw_names[index], s) ?            
                        (EnumName)(_enum)data_ ## EnumName::_values[      
                                                                index] :  
                _from_string(s, index + 1);                               
        }                                                                 
                                                                          
        EnumName() = delete;                                              
        constexpr EnumName(_enum value) : _value(value) { }               
        constexpr operator _enum() const { return (_enum)_value; }        
                                                                          
      private:                                                            
        _underlying     _value;                                           
                                                                          
        static const char * const * _trimmed_names()                      
        {                                                                 
            static char     *the_names[data_ ## EnumName::_size];         
            static bool     initialized = false;                          
                                                                          
            if (!initialized) {                                           
                for (size_t index = 0; index < data_ ## EnumName::_size;  
                     ++index) {                                           
                                                                          
                    size_t  length =                                      
                        std::strcspn(data_ ## EnumName::_raw_names[index],
                                     terminators);                        
                                                                          
                    the_names[index] = new char[length + 1];              
                                                                          
                    std::strncpy(the_names[index],                        
                                 data_ ## EnumName::_raw_names[index],    
                                 length);                                 
                    the_names[index][length] = '';                      
                }                                                         
                                                                          
                initialized = true;                                       
            }                                                             
                                                                          
            return the_names;                                             
        }                                                                 
    };
    

    // The code above was a "header file". This is a program that uses it.
    #include <iostream>
    #include "the_file_above.h"
    
    ENUM(Channel, char, Red = 1, Green, Blue)
    
    constexpr Channel   channel = Channel::_from_string("Red");
    
    int main()
    {
        std::cout << channel._to_string() << std::endl;
    
        switch (channel) {
            case Channel::Red:   return 0;
            case Channel::Green: return 1;
            case Channel::Blue:  return 2;
        }
    }
    
    static_assert(sizeof(Channel) == sizeof(char), "");
    

    上面的程序打印Red ,就像你期望的那样。 有一定程度的类型安全性,因为如果不初始化它就不能创建枚举,并且从switch删除其中一个案例将导致编译器发出警告(取决于您的编译器和标志)。 另外请注意,编译期间"Red"已转换为枚举。


    对于C ++ 17 C ++ 20,您将对Reflection Study Group(SG7)的工作感兴趣。 关于措辞 (P0194)和基本原理,设计和演变 (P0385)有一系列的论文。 (链接解析为每个系列中的最新文章。)

    从P0194r2(2016-10-15)开始,语法将使用建议的reflexpr关键字:

    meta::get_base_name_v<
      meta::get_element_m<
        meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
      >
    

    例如(改编自Matus Choclik的clang的reflexpr分支):

    #include <reflexpr>
    #include <iostream>
    
    enum MyEnum { AAA = 1, BBB, CCC = 99 };
    
    int main()
    {
      auto name_of_MyEnum_0 = 
        std::meta::get_base_name_v<
          std::meta::get_element_m<
            std::meta::get_enumerators_m<reflexpr(MyEnum)>,
            0>
        >;
    
      // prints "AAA"
      std::cout << name_of_MyEnum_0 << std::endl;
    }
    

    静态反射未能成为C ++ 17(相当于在Issaquah举行的2016年11月标准会议上提出的可能最终草案),但有信心它会将其变成C ++ 20; 来自Herb Sutter的旅行报告:

    尤其是反思研究小组审查了最新的合并静态反思提案,并发现它准备在下次会议上进入主要演进小组,开始考虑针对TS或下一个标准的统一静态反思提案。

    链接地址: http://www.djcxy.com/p/90147.html

    上一篇: enum to string in modern C++11 / C++14 / C++17 and future C++20

    下一篇: std::function vs template