在现代C ++ 11 / C ++ 14和未来的C ++ 17 / C ++ 20中枚举string

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

  • 有没有一种简单的方法来将C ++枚举转换为string?
  • 2008 c 简单的方法来使用C中的string枚举types的variables?
  • 2008年c ++ 如何轻松地将c ++枚举映射到string
  • 2008 c ++ 做一个C标识符和一个string?
  • 2008年c ++ 有没有一个简单的脚本来将C ++枚举转换为string?
  • 如何在枚举中使用枚举作为标志?
  • 如何将枚举types的variables转换为string?
  • 2011年c ++ 枚举到stringC ++
  • 如何将枚举types的variables转换为string?
  • 如何将枚举名称转换为c中的string
  • c 将C中有条件编译的枚举进行string化

读了很多答案之后,我还没有find任何答案:

  • 优雅的方式使用C ++ 11 , C ++ 14或C ++ 17的新function
  • 或在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 auto 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'; } 

约束

  • 请不要重复其他答案或基本链接 。
  • 请避免膨胀macros观的答案,或尽量减less#定义的开销尽可能最小。
  • 请不要手动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 ++编译器function在编译时使用基于variadic template classconstexpr函数的元编程技巧来生成C ++代码。

有一种方法可以在当前的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 ++ 17reflection提议。

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

macros的定义有些牵扯,所以我用几种方式回答这个问题。

  • 这个答案的大部分是我认为适用于StackOverflow空间约束的实现。
  • 还有一个CodeProject文章描述了长篇教程中的实现基础。 [ 我应该在这里移动吗? 我认为这是太多的一个SO回答 ]。
  • 有一个function齐全的库“Better Enums” ,它在一个头文件中实现macros。 它还实现了N4428types属性查询 ,即C ++ 17反映提案N4113的当前修订版本。 所以,至less对于通过这个macros声明的枚举,你现在可以用C ++ 11 / C ++ 14来build立C ++ 17的枚举reflection。

把这个答案扩展到图书馆的function是直截了当的 – 没有什么“重要的”在这里被遗漏。 然而,这是相当繁琐的,并且有编译器的可移植性问题。

免责声明 :我是CodeProject文章和图书馆的作者。

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


说明

下面的代码实现了枚举和string之间的转换。 然而,它也可以扩展到做其他事情,比如迭代。 这个答案将一个枚举包装在一个struct 。 您也可以生成一个特征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 */ } }; 

问题是:

  1. 我们将以{Red = 1, Green, Blue}作为值数组的初始值设定项。 这不是有效的C ++,因为Red不是可指定的expression式。 这是通过将每个常量转换为具有赋值运算符的typesT来解决的,但是将放弃赋值: {(T)Red = 1, (T)Green, (T)Blue}
  2. 同样,我们将以{"Red = 1", "Green", "Blue"}作为名称数组的初始值设定项。 我们将需要修剪" = 1" 。 我不知道在编译时这样做的好方法,所以我们将推迟这个运行时间。 因此, _to_string不会被constexpr ,但_from_string仍然可以被constexpr ,因为在与未修改的string进行比较时,我们可以将空格和等号作为终止符。
  3. 以上都需要一个“映射”macros,可以将另一个macros应用于__VA_ARGS__每个元素。 这是相当标准的。 这个答案包括一个简单的版本,可以处理多达8个元素。
  4. 如果macros是真正自包含的,那么它需要声明不需要单独定义的静态数据。 实际上,这意味着arrays需要特殊的处理。 有两种可能的解决scheme:名称空间范围内的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[] = " =\t\r\n"; // The size of terminators includes the implicit '\0'. 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] == '\0' : 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] = '\0'; \ } \ \ 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 ,如你所料。 有一定程度的types安全性,因为如果不初始化它就不能创build枚举,并且从switch删除其中的一个case将导致编译器(取决于你的编译器和标志)的警告。 另外请注意,在编译期间"Red"被转换为枚举。

对于C ++ 17 C ++ 20,您将对反思研究组(SG7)的工作感兴趣。 关于措辞 ( P0194 )和基本原理,devise和演变 ( P0385 )有一系列的论文。 (链接parsing为每个系列中的最新文章。)

截至P0194r2(2016-10-15),语法将使用build议的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; } 

静态reflection未能成为C ++ 17(相当于在Issaquah于2016年11月举行的标准会议上提出的可能最终草案),但是有信心使它成为C ++ 20; 从香草萨特的旅行报告 :

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

早在2011年,我花了一个周末,微调了一个基于macros的解决scheme ,最终没有使用它。

我目前的程序是启动Vim,将枚举器复制到一个空的开关体中,启动一个新的macros,将第一个枚举器转换为case语句, 将光标移动到下一行的开头,停止macros并生成剩余的case通过在其他枚举器上运行macros来执行语句。

Vimmacros比C ++macros更有趣。

真实的例子:

 enum class EtherType : uint16_t { ARP = 0x0806, IPv4 = 0x0800, VLAN = 0x8100, IPv6 = 0x86DD }; 

我会创build这个:

 std::ostream& operator<< (std::ostream& os, EtherType ethertype) { switch (ethertype) { case EtherType::ARP : return os << "ARP" ; case EtherType::IPv4: return os << "IPv4"; case EtherType::VLAN: return os << "VLAN"; case EtherType::IPv6: return os << "IPv6"; // omit default case to trigger compiler warning for missing cases }; return os << static_cast<std::uint16_t>(ethertype); } 

这就是我的方式。

原生支持枚举string会好得多。 我非常有兴趣在C ++ 17中看到reflection工作组的结果。

另一种做法是在@sehe的评论中发布:

显示vim的动画

我不知道你是否喜欢这个,我对这个解决scheme并不满意,但它是一个C ++ 14友好的方法,因为它使用模板variables和滥用模板特化:

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

这种做法最糟糕的是,这是一个难以维持的事情,但是维持一些类似的问题也是一种痛苦,不是吗?

关于这个问题的好处:

  • 使用variables的温度(C ++ 14function)
  • 有了模板专门化,我们可以“检测”何时使用了一个无效的值(但我不确定这是否有用)。
  • 它看起来整洁。
  • 名称查找是在编译时完成的。

现场示例

编辑

神秘的user673679你是对的; C ++ 14variables模板方法不处理运行时情况,这是我的错,忘记了:(

但是我们仍然可以使用一些现代的C ++特性和可变模板以及可变模板欺骗来实现从枚举值到string的运行时转换……它与其他的一样麻烦,但仍值得一提。

让我们开始使用模板别名来缩短对枚举到string映射的访问:

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

然后,可变模板的诡计:

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

这里的“ 最佳技巧 ”是使用包含每个枚举条目的值和名称的映射的可变模板; 这个映射在每个翻译单元中都是相同的,并且在任何地方都有相同的名字,所以如果我们像下面这样调用initialize函数,那么它非常简单明了:

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

我们正在为每个MyEnum条目命名,并且可以在运行时使用:

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

但可以用SFINAE和重载<< operator:

 template<typename ENUM, class = typename std::enable_if<std::is_enum<ENUM>::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<ENUM>.find(value); return o << (found == enum_values<ENUM>.end() ? Unknown : found->second); } 

用正确的operator <<现在我们可以使用enum这种方式:

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

这也是麻烦的维护和可以改善,但希望你明白了。

现场示例

如果你的enum看起来像

 enum MyEnum { AAA = -8, BBB = '8', CCC = AAA + BBB }; 

您可以将enum的内容移动到新文件中:

 AAA = -8, BBB = '8', CCC = AAA + BBB 

然后值可以被一个macros包围:

 // default definition #ifned ITEM(X,Y) #define ITEM(X,Y) #endif // Items list ITEM(AAA,-8) ITEM(BBB,'8') ITEM(CCC,AAA+BBB) // clean up #undef ITEM 

下一步可能会再次包含enum的项目:

 enum MyEnum { #define ITEM(X,Y) X=Y, #include "enum_definition_file" }; 

最后你可以生成关于这个enum实用函数:

 std::string ToString(MyEnum value) { switch( value ) { #define ITEM(X,Y) case X: return #X; #include "enum_definition_file" } return ""; } MyEnum FromString(std::string const& value) { static std::map<std::string,MyEnum> converter { #define ITEM(X,Y) { #X, X }, #include "enum_definition_file" }; auto it = converter.find(value); if( it != converter.end() ) return it->second; else throw std::runtime_error("Value is missing"); } 

该解决scheme可以应用于较旧的C ++标准,并且不使用现代C ++元素,但可用于生成大量代码,而不需要太多的精力和维护。

根据OP的要求,这里是基于Boost Preprosessor和Variadic Macros的丑陋macros解决scheme的精简版。

它允许一个简单的列表,如枚举器元素的语法,以及特定元素的设置值

 XXX_ENUM(foo,(a,b,(c,42))); 

扩展到

 enum foo { a, b, c=42 }; 

除了必要的function输出和做一些转换回来。 这个macros在这里已经有很多年了,我并不完全确定它是最有效的方法,或者说它是一个合适的方式,但是它一直在工作

在Ideone和Coliru都可以看到完整的代码。

它巨大的丑陋在上面; 如果我知道如何,我会把它放在破坏者的后面来保护你的眼睛,但减价不喜欢我。

该库(合并在一个单一的头文件)

 #include <boost/preprocessor.hpp> #include <string> #include <unordered_map> namespace xxx { template<class T> struct enum_cast_adl_helper { }; template<class E> E enum_cast( const std::string& s ) { return do_enum_cast(s,enum_cast_adl_helper<E>()); } template<class E> E enum_cast( const char* cs ) { std::string s(cs); return enum_cast<E>(s); } } // namespace xxx #define XXX_PP_ARG_N( \ _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \ _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \ _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \ _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \ _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \ _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \ _61,_62,_63,N,...) N #define XXX_PP_RSEQ_N() \ 63,62,61,60, \ 59,58,57,56,55,54,53,52,51,50, \ 49,48,47,46,45,44,43,42,41,40, \ 39,38,37,36,35,34,33,32,31,30, \ 29,28,27,26,25,24,23,22,21,20, \ 19,18,17,16,15,14,13,12,11,10, \ 9,8,7,6,5,4,3,2,1,0 #define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__) #define XXX_PP_NARG(...) XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N()) #define XXX_TUPLE_SIZE_INTERNAL(TUPLE) XXX_PP_NARG TUPLE #define XXX_TUPLE_CHOICE(i) \ BOOST_PP_APPLY( \ BOOST_PP_TUPLE_ELEM( \ 25, i, ( \ (0), (1), (2), (3), (4), (5), (6), (7), (8), \ (9), (10), (11), (12), (13), (14), (15), (16), \ (17), (18), (19), (20), (21), (22), (23), (24) \ ) ) ) #define BOOST_PP_BOOL_00 BOOST_PP_BOOL_0 #define BOOST_PP_BOOL_01 BOOST_PP_BOOL_1 #define BOOST_PP_BOOL_02 BOOST_PP_BOOL_2 #define BOOST_PP_BOOL_03 BOOST_PP_BOOL_3 #define BOOST_PP_BOOL_04 BOOST_PP_BOOL_4 #define BOOST_PP_BOOL_05 BOOST_PP_BOOL_5 #define BOOST_PP_BOOL_06 BOOST_PP_BOOL_6 #define BOOST_PP_BOOL_07 BOOST_PP_BOOL_7 #define BOOST_PP_BOOL_08 BOOST_PP_BOOL_8 #define BOOST_PP_BOOL_09 BOOST_PP_BOOL_9 #define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10 #define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11 #define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12 #define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13 #define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14 #define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15 #define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16 #define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17 #define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18 #define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19 #define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20 #define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21 #define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22 #define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23 #define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24 #define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25 #define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26 #define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27 #define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28 #define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29 #define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30 #define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31 #define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32 #define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33 #define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34 #define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35 #define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36 #define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37 #define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38 #define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39 #define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40 #define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41 #define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42 #define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43 #define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44 #define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45 #define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46 #define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47 #define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48 #define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49 #define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50 #define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51 #define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52 #define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53 #define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54 #define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55 #define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56 #define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57 #define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58 #define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59 #define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60 #define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61 #define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62 #define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63 #define BOOST_PP_DEC_00 BOOST_PP_DEC_0 #define BOOST_PP_DEC_01 BOOST_PP_DEC_1 #define BOOST_PP_DEC_02 BOOST_PP_DEC_2 #define BOOST_PP_DEC_03 BOOST_PP_DEC_3 #define BOOST_PP_DEC_04 BOOST_PP_DEC_4 #define BOOST_PP_DEC_05 BOOST_PP_DEC_5 #define BOOST_PP_DEC_06 BOOST_PP_DEC_6 #define BOOST_PP_DEC_07 BOOST_PP_DEC_7 #define BOOST_PP_DEC_08 BOOST_PP_DEC_8 #define BOOST_PP_DEC_09 BOOST_PP_DEC_9 #define BOOST_PP_DEC_010 BOOST_PP_DEC_10 #define BOOST_PP_DEC_011 BOOST_PP_DEC_11 #define BOOST_PP_DEC_012 BOOST_PP_DEC_12 #define BOOST_PP_DEC_013 BOOST_PP_DEC_13 #define BOOST_PP_DEC_014 BOOST_PP_DEC_14 #define BOOST_PP_DEC_015 BOOST_PP_DEC_15 #define BOOST_PP_DEC_016 BOOST_PP_DEC_16 #define BOOST_PP_DEC_017 BOOST_PP_DEC_17 #define BOOST_PP_DEC_018 BOOST_PP_DEC_18 #define BOOST_PP_DEC_019 BOOST_PP_DEC_19 #define BOOST_PP_DEC_020 BOOST_PP_DEC_20 #define BOOST_PP_DEC_021 BOOST_PP_DEC_21 #define BOOST_PP_DEC_022 BOOST_PP_DEC_22 #define BOOST_PP_DEC_023 BOOST_PP_DEC_23 #define BOOST_PP_DEC_024 BOOST_PP_DEC_24 #define BOOST_PP_DEC_025 BOOST_PP_DEC_25 #define BOOST_PP_DEC_026 BOOST_PP_DEC_26 #define BOOST_PP_DEC_027 BOOST_PP_DEC_27 #define BOOST_PP_DEC_028 BOOST_PP_DEC_28 #define BOOST_PP_DEC_029 BOOST_PP_DEC_29 #define BOOST_PP_DEC_030 BOOST_PP_DEC_30 #define BOOST_PP_DEC_031 BOOST_PP_DEC_31 #define BOOST_PP_DEC_032 BOOST_PP_DEC_32 #define BOOST_PP_DEC_033 BOOST_PP_DEC_33 #define BOOST_PP_DEC_034 BOOST_PP_DEC_34 #define BOOST_PP_DEC_035 BOOST_PP_DEC_35 #define BOOST_PP_DEC_036 BOOST_PP_DEC_36 #define BOOST_PP_DEC_037 BOOST_PP_DEC_37 #define BOOST_PP_DEC_038 BOOST_PP_DEC_38 #define BOOST_PP_DEC_039 BOOST_PP_DEC_39 #define BOOST_PP_DEC_040 BOOST_PP_DEC_40 #define BOOST_PP_DEC_041 BOOST_PP_DEC_41 #define BOOST_PP_DEC_042 BOOST_PP_DEC_42 #define BOOST_PP_DEC_043 BOOST_PP_DEC_43 #define BOOST_PP_DEC_044 BOOST_PP_DEC_44 #define BOOST_PP_DEC_045 BOOST_PP_DEC_45 #define BOOST_PP_DEC_046 BOOST_PP_DEC_46 #define BOOST_PP_DEC_047 BOOST_PP_DEC_47 #define BOOST_PP_DEC_048 BOOST_PP_DEC_48 #define BOOST_PP_DEC_049 BOOST_PP_DEC_49 #define BOOST_PP_DEC_050 BOOST_PP_DEC_50 #define BOOST_PP_DEC_051 BOOST_PP_DEC_51 #define BOOST_PP_DEC_052 BOOST_PP_DEC_52 #define BOOST_PP_DEC_053 BOOST_PP_DEC_53 #define BOOST_PP_DEC_054 BOOST_PP_DEC_54 #define BOOST_PP_DEC_055 BOOST_PP_DEC_55 #define BOOST_PP_DEC_056 BOOST_PP_DEC_56 #define BOOST_PP_DEC_057 BOOST_PP_DEC_57 #define BOOST_PP_DEC_058 BOOST_PP_DEC_58 #define BOOST_PP_DEC_059 BOOST_PP_DEC_59 #define BOOST_PP_DEC_060 BOOST_PP_DEC_60 #define BOOST_PP_DEC_061 BOOST_PP_DEC_61 #define BOOST_PP_DEC_062 BOOST_PP_DEC_62 #define BOOST_PP_DEC_063 BOOST_PP_DEC_63 #define XXX_TO_NUMx(x) 0 ## x #define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x)) #define XXX_STRINGIZEX(x) # x #define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x) #define XXX_VSTRINGIZE_TUPLE(tpl) XXX_TUPLE_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl) #define XXX_TUPLE_SIZE(TUPLE) XXX_TO_NUM(XXX_TUPLE_CHOICE(XXX_TUPLE_SIZE_INTERNAL(TUPLE))) #define XXX_TUPLE_FOR_EACH(MACRO,DATA,TUPLE) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_TUPLE_TO_LIST(XXX_TUPLE_SIZE(TUPLE),TUPLE)) #define XXX_STRINGIZE(x) XXX_STRINGIZEX(x) #define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_TUPLE((__VA_ARGS__)) #define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem); #define XXX_CAST_TO_VOID_INTERNAL(TUPLE) XXX_TUPLE_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,TUPLE) #define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__)) #define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) = BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),1,en) #define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_TUPLE_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) , #define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) #define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_TUPLE_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en ) #define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); #define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); } #define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) }, #define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) }, #define XXX_ENUM_INTERNAL(TYPE,NAME,TUPLE) \ enum TYPE \ { \ XXX_TUPLE_FOR_EACH(XXX_ENUM_ELEMENT,,TUPLE) \ BOOST_PP_CAT(last_enum_,NAME) \ }; \ \ inline \ const char* to_string( NAME en ) \ { \ if(false) \ { \ } \ XXX_TUPLE_FOR_EACH(XXX_ENUM_IFELSE,NAME,TUPLE) \ else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) ) \ { \ return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME)); \ } \ else \ { \ return "Invalid enum value specified for " # NAME; \ } \ } \ \ inline \ std::ostream& operator<<( std::ostream& os, const NAME& en ) \ { \ os << to_string(en); \ return os; \ } \ \ inline \ NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \ { \ static const std::unordered_map<std::string,NAME> map = \ { \ XXX_TUPLE_FOR_EACH(XXX_ENUM_CASTLIST,NAME,TUPLE) \ XXX_TUPLE_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,TUPLE) \ }; \ \ auto cit = map.find(s); \ if( cit == map.end() ) \ { \ throw std::runtime_error("Invalid value to cast to enum"); \ } \ return cit->second; \ } #define XXX_ENUM(NAME,TUPLE) XXX_ENUM_INTERNAL(NAME,NAME,TUPLE) #define XXX_ENUM_CLASS(NAME,TUPLE) XXX_ENUM_INTERNAL(class NAME,NAME,TUPLE) #define XXX_ENUM_CLASS_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,TUPLE) #define XXX_ENUM_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,TUPLE) 

用法

 #include "xxx_enum.h" // the above lib #include <iostream> XXX_ENUM(foo,(a,b,(c,42))); int main() { std::cout << "foo::a = " << foo::a <<'\n'; std::cout << "(int)foo::c = " << (int)foo::c <<'\n'; std::cout << "to_string(foo::b) = " << to_string(foo::b) <<'\n'; std::cout << "xxx::enum_cast<foo>(\"b\") = " << xxx::enum_cast<foo>("b") <<'\n'; } 

Compilation (copy paste header within main.cpp )

 > g++ --version | sed 1q g++ (GCC) 4.9.2 > g++ -std=c++14 -pedantic -Wall -Wextra main.cpp main.cpp:268:31: warning: extra ';' [-Wpedantic] XXX_ENUM(foo,(a,b,(c,42))); ^ 

产量

 foo::a = foo::a (int)foo::c = 42 to_string(foo::b) = foo::b xxx::enum_cast<foo>("b") = foo::b 

I had the same problem a couple of days ago. I couldn't find any C++ solution without some weird macro magic, so I decided to write a CMake code generator to generate simple switch case statements.

用法:

 enum2str_generate( PATH <path to place the files in> CLASS_NAME <name of the class (also prefix for the files)> FUNC_NAME <name of the (static) member function> NAMESPACE <the class will be inside this namespace> INCLUDES <LIST of files where the enums are defined> ENUMS <LIST of enums to process> BLACKLIST <LIST of constants to ignore> USE_CONSTEXPR <whether to use constexpr or not (default: off)> USE_C_STRINGS <whether to use c strings instead of std::string or not (default: off)> ) 

The function searches the include files in the filesystem (uses the include directories provided with the include_directories command), reads them and does some regex to generate the class and the function(s).

NOTE: constexpr implies inline in C++, so using the USE_CONSTEXPR option will generate a header only class!

例:

./includes/ah:

 enum AAA : char { A1, A2 }; typedef enum { VAL1 = 0, VAL2 = 1, VAL3 = 2, VAL_FIRST = VAL1, // Ignored VAL_LAST = VAL3, // Ignored VAL_DUPLICATE = 1, // Ignored VAL_STRANGE = VAL2 + 1 // Must be blacklisted } BBB; 

./CMakeLists.txt:

 include_directories( ${PROJECT_SOURCE_DIR}/includes ...) enum2str_generate( PATH "${PROJECT_SOURCE_DIR}" CLASS_NAME "enum2Str" NAMESPACE "abc" FUNC_NAME "toStr" INCLUDES "ah" # WITHOUT directory ENUMS "AAA" "BBB" BLACKLIST "VAL_STRANGE") 

Generates:

./enum2Str.hpp:

 /*! * \file enum2Str.hpp * \warning This is an automatically generated file! */ #ifndef ENUM2STR_HPP #define ENUM2STR_HPP #include <string> #include <ah> namespace abc { class enum2Str { public: static std::string toStr( AAA _var ) noexcept; static std::string toStr( BBB _var ) noexcept; }; } #endif // ENUM2STR_HPP 

./enum2Str.cpp:

 /*! * \file enum2Str.cpp * \warning This is an automatically generated file! */ #include "enum2Str.hpp" namespace abc { /*! * \brief Converts the enum AAA to a std::string * \param _var The enum value to convert * \returns _var converted to a std::string */ std::string enum2Str::toStr( AAA _var ) noexcept { switch ( _var ) { case A1: return "A1"; case A2: return "A2"; default: return "<UNKNOWN>"; } } /*! * \brief Converts the enum BBB to a std::string * \param _var The enum value to convert * \returns _var converted to a std::string */ std::string enum2Str::toStr( BBB _var ) noexcept { switch ( _var ) { case VAL1: return "VAL1"; case VAL2: return "VAL2"; case VAL3: return "VAL3"; default: return "<UNKNOWN>"; } } } 

更新:

The script now also supports scoped enumerations (enum class|struct) and I moved it to a seperate repo with some other scripts I often use: https://github.com/mensinda/cmakeBuildTools

Just generate your enums. Writing a generator for that purpose is about five minutes' work.

Generator code in java and python, super easy to port to any language you like, including C++.

Also super easy to extend by whatever functionality you want.

example input:

 First = 5 Second Third = 7 Fourth Fifth=11 

generated header:

 #include <iosfwd> enum class Hallo { First = 5, Second = 6, Third = 7, Fourth = 8, Fifth = 11 }; std::ostream & operator << (std::ostream &, const Hallo&); 

generated cpp file

 #include <ostream> #include "Hallo.h" std::ostream & operator << (std::ostream &out, const Hallo&value) { switch(value) { case Hallo::First: out << "First"; break; case Hallo::Second: out << "Second"; break; case Hallo::Third: out << "Third"; break; case Hallo::Fourth: out << "Fourth"; break; case Hallo::Fifth: out << "Fifth"; break; default: out << "<unknown>"; } return out; } 

And the generator, in a very terse form as a template for porting and extension. This example code really tries to avoid overwriting any files but still use it at your own risk.

 package cppgen; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; import java.util.LinkedHashMap; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; public class EnumGenerator { static void fail(String message) { System.err.println(message); System.exit(1); } static void run(String[] args) throws Exception { Pattern pattern = Pattern.compile("\\s*(\\w+)\\s*(?:=\\s*(\\d+))?\\s*", Pattern.UNICODE_CHARACTER_CLASS); Charset charset = Charset.forName("UTF8"); String tab = " "; if (args.length != 3) { fail("Required arguments: <enum name> <input file> <output dir>"); } String enumName = args[0]; File inputFile = new File(args[1]); if (inputFile.isFile() == false) { fail("Not a file: [" + inputFile.getCanonicalPath() + "]"); } File outputDir = new File(args[2]); if (outputDir.isDirectory() == false) { fail("Not a directory: [" + outputDir.getCanonicalPath() + "]"); } File headerFile = new File(outputDir, enumName + ".h"); File codeFile = new File(outputDir, enumName + ".cpp"); for (File file : new File[] { headerFile, codeFile }) { if (file.exists()) { fail("Will not overwrite file [" + file.getCanonicalPath() + "]"); } } int nextValue = 0; Map<String, Integer> fields = new LinkedHashMap<>(); try ( BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(inputFile), charset)); ) { while (true) { String line = reader.readLine(); if (line == null) { break; } if (line.trim().length() == 0) { continue; } Matcher matcher = pattern.matcher(line); if (matcher.matches() == false) { fail("Syntax error: [" + line + "]"); } String fieldName = matcher.group(1); if (fields.containsKey(fieldName)) { fail("Double fiend name: " + fieldName); } String valueString = matcher.group(2); if (valueString != null) { int value = Integer.parseInt(valueString); if (value < nextValue) { fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName); } nextValue = value; } fields.put(fieldName, nextValue); ++nextValue; } } try ( PrintWriter headerWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(headerFile), charset)); PrintWriter codeWriter = new PrintWriter(new OutputStreamWriter(new FileOutputStream(codeFile), charset)); ) { headerWriter.println(); headerWriter.println("#include <iosfwd>"); headerWriter.println(); headerWriter.println("enum class " + enumName); headerWriter.println('{'); boolean first = true; for (Entry<String, Integer> entry : fields.entrySet()) { if (first == false) { headerWriter.println(","); } headerWriter.print(tab + entry.getKey() + " = " + entry.getValue()); first = false; } if (first == false) { headerWriter.println(); } headerWriter.println("};"); headerWriter.println(); headerWriter.println("std::ostream & operator << (std::ostream &, const " + enumName + "&);"); headerWriter.println(); codeWriter.println(); codeWriter.println("#include <ostream>"); codeWriter.println(); codeWriter.println("#include \"" + enumName + ".h\""); codeWriter.println(); codeWriter.println("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)"); codeWriter.println('{'); codeWriter.println(tab + "switch(value)"); codeWriter.println(tab + '{'); first = true; for (Entry<String, Integer> entry : fields.entrySet()) { codeWriter.println(tab + "case " + enumName + "::" + entry.getKey() + ':'); codeWriter.println(tab + tab + "out << \"" + entry.getKey() + "\";"); codeWriter.println(tab + tab + "break;"); first = false; } codeWriter.println(tab + "default:"); codeWriter.println(tab + tab + "out << \"<unknown>\";"); codeWriter.println(tab + '}'); codeWriter.println(); codeWriter.println(tab + "return out;"); codeWriter.println('}'); codeWriter.println(); } } public static void main(String[] args) { try { run(args); } catch(Exception exc) { exc.printStackTrace(); System.exit(1); } } } 

And a port to Python 3.5 because different enough to be potentially helpful

 import re import collections import sys import io import os def fail(*args): print(*args) exit(1) pattern = re.compile(r'\s*(\w+)\s*(?:=\s*(\d+))?\s*') tab = " " if len(sys.argv) != 4: n=0 for arg in sys.argv: print("arg", n, ":", arg, " / ", sys.argv[n]) n += 1 fail("Required arguments: <enum name> <input file> <output dir>") enumName = sys.argv[1] inputFile = sys.argv[2] if not os.path.isfile(inputFile): fail("Not a file: [" + os.path.abspath(inputFile) + "]") outputDir = sys.argv[3] if not os.path.isdir(outputDir): fail("Not a directory: [" + os.path.abspath(outputDir) + "]") headerFile = os.path.join(outputDir, enumName + ".h") codeFile = os.path.join(outputDir, enumName + ".cpp") for file in [ headerFile, codeFile ]: if os.path.exists(file): fail("Will not overwrite file [" + os.path.abspath(file) + "]") nextValue = 0 fields = collections.OrderedDict() for line in open(inputFile, 'r'): line = line.strip() if len(line) == 0: continue match = pattern.match(line) if match == None: fail("Syntax error: [" + line + "]") fieldName = match.group(1) if fieldName in fields: fail("Double field name: " + fieldName) valueString = match.group(2) if valueString != None: value = int(valueString) if value < nextValue: fail("Not a monotonous progression from " + nextValue + " to " + value + " for enum field " + fieldName) nextValue = value fields[fieldName] = nextValue nextValue += 1 headerWriter = open(headerFile, 'w') codeWriter = open(codeFile, 'w') try: headerWriter.write("\n") headerWriter.write("#include <iosfwd>\n") headerWriter.write("\n") headerWriter.write("enum class " + enumName + "\n") headerWriter.write("{\n") first = True for fieldName, fieldValue in fields.items(): if not first: headerWriter.write(",\n") headerWriter.write(tab + fieldName + " = " + str(fieldValue)) first = False if not first: headerWriter.write("\n") headerWriter.write("};\n") headerWriter.write("\n") headerWriter.write("std::ostream & operator << (std::ostream &, const " + enumName + "&);\n") headerWriter.write("\n") codeWriter.write("\n") codeWriter.write("#include <ostream>\n") codeWriter.write("\n") codeWriter.write("#include \"" + enumName + ".h\"\n") codeWriter.write("\n") codeWriter.write("std::ostream & operator << (std::ostream &out, const " + enumName + "&value)\n") codeWriter.write("{\n") codeWriter.write(tab + "switch(value)\n") codeWriter.write(tab + "{\n") for fieldName in fields.keys(): codeWriter.write(tab + "case " + enumName + "::" + fieldName + ":\n") codeWriter.write(tab + tab + "out << \"" + fieldName + "\";\n") codeWriter.write(tab + tab + "break;\n") codeWriter.write(tab + "default:\n") codeWriter.write(tab + tab + "out << \"<unknown>\";\n") codeWriter.write(tab + "}\n") codeWriter.write("\n") codeWriter.write(tab + "return out;\n") codeWriter.write("}\n") codeWriter.write("\n") finally: headerWriter.close() codeWriter.close() 

I wrote a library for solving this problem, everything happens in compiling time, except for getting the message.

用法:

Use macro DEF_MSG to define a macro and message pair:

 DEF_MSG(CODE_OK, "OK!") DEF_MSG(CODE_FAIL, "Fail!") 

CODE_OK is the macro to use, and "OK!" is the corresponding message.

Use get_message() or just gm() to get the message:

 get_message(CODE_FAIL); // will return "Fail!" gm(CODE_FAIL); // works exactly the same as above 

Use MSG_NUM to find out how many macros have been defined. This will automatically increse, you don't need to do anything.

Predefined messages:

 MSG_OK: OK MSG_BOTTOM: Message bottom 

Project: libcodemsg


The library doesn't create extra data. Everything happens in compiling time. In message_def.h , it generates an enum called MSG_CODE ; in message_def.c , it generates a variable holds all the strings in static const char* _g_messages[] .

In such case, the library is limited to create one enum only. This is ideal for return values, for example:

 MSG_CODE foo(void) { return MSG_OK; // or something else } MSG_CODE ret = foo(); if (MSG_OK != ret) { printf("%s\n", gm(ret);); } 

Another thing I like this design is you can manage message definitions in different files.


I found the solution to this question looks much better.

 #define ENUM_MAKE(TYPE, ...) \ enum class TYPE {__VA_ARGS__};\ struct Helper_ ## TYPE { \ static const String& toName(TYPE type) {\ int index = static_cast<int>(type);\ return splitStringVec()[index];}\ static const TYPE toType(const String& name){\ static std::unordered_map<String,TYPE> typeNameMap;\ if( typeNameMap.empty() )\ {\ const StringVector& ssVec = splitStringVec();\ for (size_t i = 0; i < ssVec.size(); ++i)\ typeNameMap.insert(std::make_pair(ssVec[i], static_cast<TYPE>(i)));\ }\ return typeNameMap[name];}\ static const StringVector& splitStringVec() {\ static StringVector typeNameVector;\ if(typeNameVector.empty()) \ {\ typeNameVector = StringUtil::split(#__VA_ARGS__, ",");\ for (auto& name : typeNameVector)\ {\ name.erase(std::remove(name.begin(), name.end(), ' '),name.end()); \ name = String(#TYPE) + "::" + name;\ }\ }\ return typeNameVector;\ }\ }; using String = std::string; using StringVector = std::vector<String>; StringVector StringUtil::split( const String& str, const String& delims, unsigned int maxSplits, bool preserveDelims) { StringVector ret; // Pre-allocate some space for performance ret.reserve(maxSplits ? maxSplits+1 : 10); // 10 is guessed capacity for most case unsigned int numSplits = 0; // Use STL methods size_t start, pos; start = 0; do { pos = str.find_first_of(delims, start); if (pos == start) { // Do nothing start = pos + 1; } else if (pos == String::npos || (maxSplits && numSplits == maxSplits)) { // Copy the rest of the string ret.push_back( str.substr(start) ); break; } else { // Copy up to delimiter ret.push_back( str.substr(start, pos - start) ); if(preserveDelims) { // Sometimes there could be more than one delimiter in a row. // Loop until we don't find any more delims size_t delimStart = pos, delimPos; delimPos = str.find_first_not_of(delims, delimStart); if (delimPos == String::npos) { // Copy the rest of the string ret.push_back( str.substr(delimStart) ); } else { ret.push_back( str.substr(delimStart, delimPos - delimStart) ); } } start = pos + 1; } // parse up to next real data start = str.find_first_not_of(delims, start); ++numSplits; } while (pos != String::npos); return ret; } 

 ENUM_MAKE(MY_TEST, MY_1, MY_2, MY_3) MY_TEST s1 = MY_TEST::MY_1; MY_TEST s2 = MY_TEST::MY_2; MY_TEST s3 = MY_TEST::MY_3; String z1 = Helper_MY_TEST::toName(s1); String z2 = Helper_MY_TEST::toName(s2); String z3 = Helper_MY_TEST::toName(s3); MY_TEST q1 = Helper_MY_TEST::toType(z1); MY_TEST q2 = Helper_MY_TEST::toType(z2); MY_TEST q3 = Helper_MY_TEST::toType(z3); 

automatically ENUM_MAKE macro generate 'enum class' and helper class with 'enum reflection function'.

In order to reduce mistakes, at once Everything is defined with only one ENUM_MAKE.

The advantage of this code is automatically created for reflection and a close look at macro code ,easy-to-understand code. 'enum to string' , 'string to enum' performance both is algorithm O(1).

Disadvantages is when first use , helper class for enum relection 's string vector and map is initialized. but If you want you'll also be pre-initialized. –

my solution is without macro usage.

advantages:

  • you see exactly what you do
  • access is with hash maps, so good for many valued enums
  • no need to consider order or non-consecutive values
  • both enum to string and string to enum translation, while added enum value must be added in one additional place only

disadvantages:

  • you need to replicate all the enums values as text
  • access in hash map must consider string case
  • maintenance if adding values is painful – must add in both enum and direct translate map

so… until the day that C++ implements the C# Enum.Parse functionality, I will be stuck with this:

  #include <unordered_map> enum class Language { unknown, Chinese, English, French, German // etc etc }; class Enumerations { public: static void fnInit(void); static std::unordered_map <std::wstring, Language> m_Language; static std::unordered_map <Language, std::wstring> m_invLanguage; private: static void fnClear(); static void fnSetValues(void); static void fnInvertValues(void); static bool m_init_done; }; std::unordered_map <std::wstring, Language> Enumerations::m_Language = std::unordered_map <std::wstring, Language>(); std::unordered_map <Language, std::wstring> Enumerations::m_invLanguage = std::unordered_map <Language, std::wstring>(); void Enumerations::fnInit() { fnClear(); fnSetValues(); fnInvertValues(); } void Enumerations::fnClear() { m_Language.clear(); m_invLanguage.clear(); } void Enumerations::fnSetValues(void) { m_Language[L"unknown"] = Language::unknown; m_Language[L"Chinese"] = Language::Chinese; m_Language[L"English"] = Language::English; m_Language[L"French"] = Language::French; m_Language[L"German"] = Language::German; // and more etc etc } void Enumerations::fnInvertValues(void) { for (auto it = m_Language.begin(); it != m_Language.end(); it++) { m_invLanguage[it->second] = it->first; } } // usage - //Language aLanguage = Language::English; //wstring sLanguage = Enumerations::m_invLanguage[aLanguage]; //wstring sLanguage = L"French" ; //Language aLanguage = Enumerations::m_Language[sLanguage]; 

EDIT: check below for a newer version

As mentioned above, N4113 is the final solution to this matter, but we'll have to wait more than a year to see it coming out.

Meanwhile, if you want such feature, you'll need to resort to "simple" templates and some preprocessor magic.

Enumerator

 template<typename T> class Enum final { const char* m_name; const T m_value; static T m_counter; public: Enum(const char* str, T init = m_counter) : m_name(str), m_value(init) {m_counter = (init + 1);} const T value() const {return m_value;} const char* name() const {return m_name;} }; template<typename T> T Enum<T>::m_counter = 0; #define ENUM_TYPE(x) using Enum = Enum<x>; #define ENUM_DECL(x,...) x(#x,##__VA_ARGS__) #define ENUM(...) const Enum ENUM_DECL(__VA_ARGS__); 

用法

 #include <iostream> //the initialization order should be correct in all scenarios namespace Level { ENUM_TYPE(std::uint8) ENUM(OFF) ENUM(SEVERE) ENUM(WARNING) ENUM(INFO, 10) ENUM(DEBUG) ENUM(ALL) } namespace Example { ENUM_TYPE(long) ENUM(A) ENUM(B) ENUM(C, 20) ENUM(D) ENUM(E) ENUM(F) } int main(int argc, char** argv) { Level::Enum lvl = Level::WARNING; Example::Enum ex = Example::C; std::cout << lvl.value() << std::endl; //2 std::cout << ex.value() << std::endl; //20 } 

Simple explaination

Enum<T>::m_counter is set to 0 inside each namespace declaration.
( Could someone point me out where ^^this behaviour^^ is mentioned on the standard? )
The preprocessor magic automates the declaration of enumerators.

缺点

  • It's not a true enum type, therefore not promotable to int
  • Cannot be used in switch cases

替代scheme

This one sacrifices line numbering (not really) but can be used on switch cases .

 #define ENUM_TYPE(x) using type = Enum<x> #define ENUM(x) constexpr type x{__LINE__,#x} template<typename T> struct Enum final { const T value; const char* name; constexpr operator const T() const noexcept {return value;} constexpr const char* operator&() const noexcept {return name;} }; 

勘误表

#line 0 conflicts with -pedantic on GCC and clang.

解决方法

Either start at #line 1 and subtract 1 from __LINE__ .
Or, don't use -pedantic .
And while we're at it, avoid VC++ at all costs, it has always been a joke of a compiler.

用法

 #include <iostream> namespace Level { ENUM_TYPE(short); #line 0 ENUM(OFF); ENUM(SEVERE); ENUM(WARNING); #line 10 ENUM(INFO); ENUM(DEBUG); ENUM(ALL); #line <next line number> //restore the line numbering }; int main(int argc, char** argv) { std::cout << Level::OFF << std::endl; // 0 std::cout << &Level::OFF << std::endl; // OFF std::cout << Level::INFO << std::endl; // 10 std::cout << &Level::INFO << std::endl; // INFO switch(/* any integer or integer-convertible type */) { case Level::OFF: //... break; case Level::SEVERE: //... break; //... } return 0; } 

Real-life implementation and use

r3dVoxel – Enum
r3dVoxel – ELoggingLevel

快速参考

#line lineno — cppreference.com

This gist provides a simple mapping based on C++ variadic templates.

This is a C++17-simplified version of the type-based map from the gist :

 #include <cstring> // http://stackoverflow.com/q/24520781 template<typename KeyValue, typename ... RestOfKeyValues> struct map { static constexpr typename KeyValue::key_t get(const char* val) noexcept { if constexpr (sizeof...(RestOfKeyValues)==0) // C++17 if constexpr return KeyValue::key; // Returns last element else { static_assert(KeyValue::val != nullptr, "Only last element may have null name"); return strcmp(val, KeyValue::val()) ? map<RestOfKeyValues...>::get(val) : KeyValue::key; } } static constexpr const char* get(typename KeyValue::key_t key) noexcept { if constexpr (sizeof...(RestOfKeyValues)==0) return (KeyValue::val != nullptr) && (key == KeyValue::key) ? KeyValue::val() : ""; else return (key == KeyValue::key) ? KeyValue::val() : map<RestOfKeyValues...>::get(key); } }; template<typename Enum, typename ... KeyValues> class names { typedef map<KeyValues...> Map; public: static constexpr Enum get(const char* nam) noexcept { return Map::get(nam); } static constexpr const char* get(Enum key) noexcept { return Map::get(key); } }; 

一个示例用法:

 enum class fasion { fancy, classic, sporty, emo, __last__ = emo, __unknown__ = -1 }; #define NAME(s) static inline constexpr const char* s() noexcept {return #s;} namespace name { NAME(fancy) NAME(classic) NAME(sporty) NAME(emo) } template<auto K, const char* (*V)()> // C++17 template<auto> struct _ { typedef decltype(K) key_t; typedef decltype(V) name_t; static constexpr key_t key = K; // enum id value static constexpr name_t val = V; // enum id name }; typedef names<fasion, _<fasion::fancy, name::fancy>, _<fasion::classic, name::classic>, _<fasion::sporty, name::sporty>, _<fasion::emo, name::emo>, _<fasion::__unknown__, nullptr> > fasion_names; 

The map<KeyValues...> can be used in both directions:

  • fasion_names::get(fasion::emo)
  • fasion_names::get("emo")

This example is available on godbolt.org

 int main () { constexpr auto str = fasion_names::get(fasion::emo); constexpr auto fsn = fasion_names::get(str); return (int) fsn; } 

Result from gcc-7 -std=c++1z -Ofast -S

 main: mov eax, 3 ret 

I have been frustrated by this problem for a long time too, along with the problem of getting a type converted to string in a proper way. However, for the last problem, I was surprised by the solution explained in Is it possible to print a variable's type in standard C++? , using the idea from Can I obtain C++ type names in a constexpr way? 。 Using this technique, an analogous function can be constructed for getting an enum value as string:

 #include <iostream> using namespace std; class static_string { const char* const p_; const std::size_t sz_; public: typedef const char* const_iterator; template <std::size_t N> constexpr static_string(const char(&a)[N]) noexcept : p_(a) , sz_(N - 1) {} constexpr static_string(const char* p, std::size_t N) noexcept : p_(p) , sz_(N) {} constexpr const char* data() const noexcept { return p_; } constexpr std::size_t size() const noexcept { return sz_; } constexpr const_iterator begin() const noexcept { return p_; } constexpr const_iterator end() const noexcept { return p_ + sz_; } constexpr char operator[](std::size_t n) const { return n < sz_ ? p_[n] : throw std::out_of_range("static_string"); } }; inline std::ostream& operator<<(std::ostream& os, static_string const& s) { return os.write(s.data(), s.size()); } /// \brief Get the name of a type template <class T> static_string typeName() { #ifdef __clang__ static_string p = __PRETTY_FUNCTION__; return static_string(p.data() + 30, p.size() - 30 - 1); #elif defined(_MSC_VER) static_string p = __FUNCSIG__; return static_string(p.data() + 37, p.size() - 37 - 7); #endif } namespace details { template <class Enum> struct EnumWrapper { template < Enum enu > static static_string name() { #ifdef __clang__ static_string p = __PRETTY_FUNCTION__; static_string enumType = typeName<Enum>(); return static_string(p.data() + 73 + enumType.size(), p.size() - 73 - enumType.size() - 1); #elif defined(_MSC_VER) static_string p = __FUNCSIG__; static_string enumType = typeName<Enum>(); return static_string(p.data() + 57 + enumType.size(), p.size() - 57 - enumType.size() - 7); #endif } }; } /// \brief Get the name of an enum value template <typename Enum, Enum enu> static_string enumName() { return details::EnumWrapper<Enum>::template name<enu>(); } enum class Color { Blue = 0, Yellow = 1 }; int main() { std::cout << "_" << typeName<Color>() << "_" << std::endl; std::cout << "_" << enumName<Color, Color::Blue>() << "_" << std::endl; return 0; } 

The code above has only been tested on Clang (see https://ideone.com/je5Quv ) and VS2015, but should be adaptable to other compilers by fiddling a bit with the integer constants. Of course, it still uses macros under the hood, but at least one doesn't need access to the enum implementation.

The following solution is based on a std::array<std::string,N> for a given enum.

For enum to std::string conversion we can just cast the enum to size_t and lookup the string from the array. The operation is O(1) and requires no heap allocation.

 #include <boost/preprocessor/seq/transform.hpp> #include <boost/preprocessor/seq/enum.hpp> #include <boost/preprocessor/stringize.hpp> #include <string> #include <array> #include <iostream> #define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem) // ENUM // ============================================================================ #define ENUM(X, SEQ) \ struct X { \ enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \ static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \ return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \ } \ static std::string to_string(Enum e) { \ auto a = array_of_strings(); \ return a[static_cast<size_t>(e)]; \ } \ } 

For std::string to enum conversion we would have to make a linear search over the array and cast the array index to enum .

Try it here with usage examples: http://coliru.stacked-crooked.com/a/e4212f93bee65076

Edit: Reworked my solution so the custom Enum can be used inside a class.

Solutions using enum within class/struct (struct defaults with public members) and overloaded operators:

 struct Color { enum Enum { RED, GREEN, BLUE }; Enum e; Color() {} Color(Enum e) : e(e) {} Color operator=(Enum o) { e = o; return *this; } Color operator=(Color o) { e = oe; return *this; } bool operator==(Enum o) { return e == o; } bool operator==(Color o) { return e == oe; } operator Enum() const { return e; } std::string toString() const { switch (e) { case Color::RED: return "red"; case Color::GREEN: return "green"; case Color::BLUE: return "blue"; default: return "unknown"; } } }; 

From the outside it looks nearly exactly like a class enum:

 Color red; red = Color::RED; Color blue = Color::BLUE; cout << red.toString() << " " << Color::GREEN << " " << blue << endl; 

This will output "red 1 2". You could possibly overload << to make blue output a string (although it might cause ambiguity so not possible), but it wouldn't work with Color::GREEN since it doesn't automatically convert to Color.

The purpose of having an implicit convert to Enum (which implicitly converts to int or type given) is to be able to do:

 Color color; switch (color) ... 

This works, but it also means that this work too:

 int i = color; 

With an enum class it wouldn't compile. You ought to be careful if you overload two functions taking the enum and an integer, or remove the implicit conversion…

Another solution would involve using an actual enum class and static members:

 struct Color { enum class Enum { RED, GREEN, BLUE }; static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE; //same as previous... }; 

It possibly takes more space, and is longer to make, but causes a compile error for implicit int conversions. I'd use this one because of that!

There's surely overhead with this though, but I think it's just simpler and looks better than other code I've seen. There's also potential for adding functionality, which could all be scoped within the class.

Edit : this works and most can be compiled before execution:

 class Color { public: enum class Enum { RED, GREEN, BLUE }; static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE; constexpr Color() : e(Enum::RED) {} constexpr Color(Enum e) : e(e) {} constexpr bool operator==(Enum o) const { return e == o; } constexpr bool operator==(Color o) const { return e == oe; } constexpr operator Enum() const { return e; } Color& operator=(Enum o) { const_cast<Enum>(this->e) = o; return *this; } Color& operator=(Color o) { const_cast<Enum>(this->e) = oe; return *this; } std::string toString() const { switch (e) { case Enum::RED: return "red"; case Enum::GREEN: return "green"; case Enum::BLUE: return "blue"; default: return "unknown"; } } private: const Enum e; }; 

Very simple solution with one big constraint: you can't assign custom values to enum values, but with the right regex, you could. you could also add a map to translate them back to enum values without much more effort:

 #include <vector> #include <string> #include <regex> #include <iterator> std::vector<std::string> split(const std::string& s, const std::regex& delim = std::regex(",\\s*")) { using namespace std; vector<string> cont; copy(regex_token_iterator<string::const_iterator>(s.begin(), s.end(), delim, -1), regex_token_iterator<string::const_iterator>(), back_inserter(cont)); return cont; } #define EnumType(Type, ...) enum class Type { __VA_ARGS__ } #define EnumStrings(Type, ...) static const std::vector<std::string> \ Type##Strings = split(#__VA_ARGS__); #define EnumToString(Type, ...) EnumType(Type, __VA_ARGS__); \ EnumStrings(Type, __VA_ARGS__) 

用法示例:

 EnumToString(MyEnum, Red, Green, Blue); 

Well, yet another option. A typical use case is where you need constants for the HTTP verbs as well as using its string version values.

例子:

 int main () { VERB a = VERB::GET; VERB b = VERB::GET; VERB c = VERB::POST; VERB d = VERB::PUT; VERB e = VERB::DELETE; std::cout << a.toString() << std::endl; std::cout << a << std::endl; if ( a == VERB::GET ) { std::cout << "yes" << std::endl; } if ( a == b ) { std::cout << "yes" << std::endl; } if ( a != c ) { std::cout << "no" << std::endl; } } 

The VERB class:

 // ----------------------------------------------------------- // ----------------------------------------------------------- class VERB { private: // private constants enum Verb {GET_=0, POST_, PUT_, DELETE_}; // private string values static const std::string theStrings[]; // private value const Verb value; const std::string text; // private constructor VERB (Verb v) : value(v), text (theStrings[v]) { // std::cout << " constructor \n"; } public: operator const char * () const { return text.c_str(); } operator const std::string () const { return text; } const std::string toString () const { return text; } bool operator == (const VERB & other) const { return (*this).value == other.value; } bool operator != (const VERB & other) const { return ! ( (*this) == other); } // --- static const VERB GET; static const VERB POST; static const VERB PUT; static const VERB DELETE; }; const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"}; const VERB VERB::GET = VERB ( VERB::Verb::GET_ ); const VERB VERB::POST = VERB ( VERB::Verb::POST_ ); const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ ); const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ ); // end of file 

What about a simple streaming overload? You still have to maintain the mapping if you don't want to do some macro magic, but I find it cleaner than your original solution.

 #include <cstdint> // for std::uint_fast8_t #include <array> #include <string> #include <iostream> enum class MyEnum : std::uint_fast8_t { AAA, BBB, CCC, }; std::ostream& operator<<(std::ostream& str, MyEnum type) { switch(type) { case MyEnum::AAA: str << "AAA"; break; case MyEnum::BBB: str << "BBB"; break; case MyEnum::CCC: str << "CCC"; break; default: break; } return str; } int main() { std::cout << MyEnum::AAA <<'\n'; }