在现代c++ 11 / c++ 14 / c++ 17和未来的c++ 20中,enum为字符串

与其他类似的问题不同,这个问题是关于如何使用c++的新特性。

  • 2008 __abc0 __abc1
  • 2008 __abc0 __abc1
  • 2008 __abc0 __abc1
  • 2008 __abc0 __abc1
  • 2008 __abc0 __abc1
  • 2009 __abc0 __abc1
  • 2011 __abc0 __abc1
  • 2011 __abc0 __abc1
  • 2011 __abc0 __abc1
  • 2012 __abc0 __abc1
  • 2013 __abc0 __abc1

看了很多答案后,我还没有找到:

例子

例子往往比冗长的解释更好。< br / > 你可以在Coliru上编译并运行这个代码片段。< br / > (另一个之前的例子也可用)

#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/17/20)
  • noexcept (C + + 11)
  • c++ 17/C + + 20友好代码段

一个可能的想法是使用c++编译器功能,在编译时使用基于variadic template classconstexpr函数的元编程技巧来生成c++代码……

307064 次浏览

对于c++ 17 c++ 20,你会对反思研究小组(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反射分支):

#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(更确切地说,进入了2016年11月在Issaquah举行的标准会议上提出的可能是最终草案),但有信心它将进入c++ 20;从赫布·萨特的旅行报告:

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

根据OP的请求,这里有一个基于提高Preprosessor可变的宏的丑陋宏解决方案的剥离版本。

它允许类似枚举元素语法的简单列表,并为特定元素设置值,以便

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

扩大到

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

与必要的函数一起输出并做一些转换。这个宏已经存在了很长时间,我不完全确定这是最有效的方式,或者它是一种一致的方式,但它一直以来都在工作

完整的代码可以在IdeoneColiru处看到。

它的巨大丑陋在上面;如果我知道怎么做,我会把它放在剧透后面保护你的眼睛,但markdown不喜欢我。

库(合并在一个头文件中)

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

编译(在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

早在2011年,我花了一个周末对基于宏的解决方案进行微调,并最终从未使用它。

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

Vim宏比c++宏更有趣。

现实生活中的例子:

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

我将创建这个:

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

这就是我的生活方式。

不过,对枚举字符串化的本地支持会更好。我对c++ 17中反射工作组的结果非常感兴趣。

@sehe在评论中发布了另一种方法。

我不知道你是否会喜欢这个,我对这个解决方案不太满意,但它是一个c++ 14友好的方法,因为它使用模板变量和滥用模板专门化:

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

这种方法最糟糕的地方是维护起来很痛苦,但维护其他一些类似的方法也很痛苦,不是吗?

这种方法的优点:

  • 使用可变温度(c++ 14特性)
  • 有了模板专门化,我们可以“检测”;当使用无效值时(但我不确定这是否有用)。
  • 看起来很整洁。
  • 名称查找在编译时完成。

现场示例 . .

编辑

你是对的;c++ 14变量模板方法不处理运行时情况,这是我的错,忘记了它:(

但是我们仍然可以使用一些现代c++特性和变量模板加上变进模板技巧来实现从枚举值到字符串的运行时转换…它和其他一样麻烦,但仍然值得一提。

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

// 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和重载<<操作符来改进:

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 <<,现在我们可以这样使用枚举:

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

维护这一点也很麻烦,可以改进,但希望您能理解。

现场示例 . .

编辑:检查下面的新版本

如上所述,N4113是这个问题的最终解决方案,但我们必须等待一年多才能看到它的出现。

同时,如果你想要这样的特性,你将需要求助于“简单的”模板和一些预处理器魔法。

枚举器

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
}

简单的解释

Enum<T>::m_counter在每个命名空间声明中被设置为0 (有人能告诉我^^这种行为^^在标准中被提到了吗?) < br > 预处理器神奇地自动声明枚举数

缺点

  • 它不是真正的enum类型,因此不能提升为int类型
  • 不能在交换机情况下使用

可选择的解决方案

这个函数牺牲了行号(不是真的)但可用于开关情况

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

勘误表

在GCC和clang上,#line 0-pedantic冲突。

解决方案

#line 1开始,从__LINE__减去1 或者,不要使用-pedantic.
当我们在它的时候,不惜一切代价避免vc++,它一直是一个编译器的笑话

使用

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

真实的实现和使用

< p > r3dVoxel Enum < br > r3dVoxel - ELoggingLevel < / p >

快速参考

#line lineno——cppreference.com

(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++中将令牌转换为字符串的唯一方法。
  • 宏是相当不显眼的-常量声明,包括初始化,被粘贴到一个内置enum声明。这意味着它们具有与内置枚举相同的语法和含义。
  • 消除重复。
  • 由于constexpr,该实现至少在c++ 11中是最自然和有用的。它也可以用于c++ 98 + __VA_ARGS__。它绝对是现代c++。

宏的定义有些复杂,所以我将从几个方面回答这个问题。

  • 这个答案的大部分是我认为适合StackOverflow的空间限制的实现。
  • 还有一个CodeProject上文章在一个长篇教程中描述了实现的基础知识。[我应该把它移到这里吗?我认为这样的回答太过分了]。
  • 有一个全功能库“Better Enums”在一个头文件中实现宏。它还实现了类型属性查询, c++ 17反射提议N4113的当前修订。因此,至少对于通过这个宏声明的枚举,你现在可以在c++ 11/ c++ 14中拥有提议的c++ 17枚举反射。

将这个答案扩展到库的特性是很简单的——这里没有遗漏任何“重要”的东西。然而,这是相当乏味的,并且存在编译器可移植性问题。

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

您可以在Wandbox中在线尝试在这个答案中编码图书馆N4428的实现。标准库文档还包含概述如何使用它作为N4428,它解释了该提议的enumums部分。


解释

下面的代码实现了枚举和字符串之间的转换。然而,它也可以扩展到做其他事情,比如迭代。这个答案在struct中包装了一个枚举。你也可以在枚举旁边生成trait 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不是一个可赋值表达式。这可以通过将每个常量强制转换为类型T来解决,该类型具有赋值操作符,但会删除赋值:{(T)Red = 1, (T)Green, (T)Blue}
  2. 类似地,我们将以{"Red = 1", "Green", "Blue"}作为names数组的初始化式结束。我们将需要修剪" = 1"。我不知道在编译时做这件事的好方法,所以我们将把它推迟到运行时。因此,_to_string不会是constexpr,但_from_string仍然可以是constexpr,因为在与未修剪的字符串比较时,我们可以将空白和等号作为终止符。
  3. 上面两个都需要一个“映射”宏,可以将另一个宏应用到__VA_ARGS__中的每个元素。这是相当标准的。这个答案包括一个简单的版本,最多可以处理8个元素。
  4. 如果宏是真正自包含的,它不需要声明需要单独定义的静态数据。实际上,这意味着数组需要特殊处理。有两种可能的解决方案:命名空间范围内的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,正如你所期望的那样。有一定程度的类型安全,因为你不能在没有初始化的情况下创建枚举,并且从switch中删除一个情况将导致编译器发出警告(取决于你的编译器和标志)。另外,注意"Red"在编译过程中被转换为枚举。

我写了一个库来解决这个问题,所有的事情都发生在编译时,除了获取消息。

用法:

使用宏DEF_MSG定义宏和消息对:

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

CODE_OK是要使用的宏,而"OK!"是相应的消息。

使用get_message()gm()来获取消息:

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

使用MSG_NUM找出已经定义了多少个宏。它会自动增加,你不需要做任何事情。

预定义的消息:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

项目:libcodemsg


标准库不会创建额外的数据。一切都发生在编译时。在message_def.h中,它生成一个名为MSG_CODEenum;在message_def.c中,它生成一个保存static const char* _g_messages[]中所有字符串的变量。

在这种情况下,标准库只能创建一个enum。这对于返回值非常理想,例如:

MSG_CODE foo(void) {
return MSG_OK; // or something else
}


MSG_CODE ret = foo();


if (MSG_OK != ret) {
printf("%s\n", gm(ret););
}

我喜欢这种设计的另一个原因是,您可以在不同的文件中管理消息定义。


我发现这个问题的解决方案看起来更好。

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

自动ENUM_MAKE宏生成“枚举类”和“枚举反射函数”辅助类。

为了减少错误,Everything只定义一个ENUM_MAKE。

这种代码的优点是自动创建用于反射和查看宏代码,易于理解的代码。'enum to string', 'string to enum'性能都是算法O(1)。

缺点是当第一次使用时,枚举relection的string vector和map的helper类被初始化。 但是如果你想,你也会被预初始化。- < / p >

我的解决方案是不使用宏。

优点:

  • 你知道你在做什么
  • 访问是通过哈希映射进行的,因此适用于许多有值枚举
  • 不需要考虑顺序值或非连续值
  • 既enum到字符串和字符串到enum转换,而添加的enum值必须添加在一个额外的地方

缺点:

  • 您需要将所有枚举值复制为文本
  • 哈希映射中的访问必须考虑字符串大小写
  • 维护如果添加值是痛苦的-必须添加在enum和直接翻译映射

所以…直到c++实现c# Enum。解析功能,我将坚持这个:

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

下面的解决方案基于给定枚举的std::array<std::string,N>

对于enumstd::string的转换,我们只需将枚举转换为size_t,并从数组中查找字符串。该操作是O(1),不需要堆分配。

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

对于std::stringenum的转换,我们必须对数组进行线性搜索,并将数组索引强制转换为enum

在这里尝试使用示例:http://coliru.stacked-crooked.com/a/e4212f93bee65076

编辑:重做我的解决方案,以便自定义Enum可以在类中使用。

几天前我也遇到了同样的问题。如果没有一些奇怪的宏魔法,我无法找到任何c++解决方案,所以我决定编写一个CMake代码生成器来生成简单的开关case语句。

用法:

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

该函数搜索文件系统中的include文件(使用include_directories命令提供的include目录),读取它们并执行一些regex来生成类和函数。

注意:constexpr在c++中意味着内联,所以使用USE_CONSTEXPR选项将只生成一个头类!

例子:

/ / a.h包括:

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   "a.h" # WITHOUT directory
ENUMS      "AAA" "BBB"
BLACKLIST  "VAL_STRANGE")

生成:

/ enum2Str.hpp:

/*!
* \file enum2Str.hpp
* \warning This is an automatically generated file!
*/


#ifndef ENUM2STR_HPP
#define ENUM2STR_HPP


#include <string>
#include <a.h>


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

更新:

脚本现在还支持范围枚举(枚举类|struct)和 我将它与一些我经常使用的其他脚本一起移动到一个单独的repo: https://github.com/mensinda/cmakeBuildTools

只需要生成枚举。为此目的编写一个生成器大约需要5分钟的工作。

生成器代码在java和python,超级容易移植到任何你喜欢的语言,包括c++。

而且非常容易扩展任何你想要的功能。

示例输入:

First = 5
Second
Third = 7
Fourth
Fifth=11

生成头文件:

#include <iosfwd>


enum class Hallo
{
First = 5,
Second = 6,
Third = 7,
Fourth = 8,
Fifth = 11
};


std::ostream & operator << (std::ostream &, const Hallo&);

生成的CPP文件

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

生成器以非常简洁的形式作为移植和扩展的模板。这个示例代码确实试图避免覆盖任何文件,但使用它仍然要自担风险。

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

并将其移植到Python 3.5,因为它的不同可能会有所帮助

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

在类/struct (struct默认为public成员)和重载操作符中使用enum的解决方案:

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 = o.e; return *this; }
bool operator==(Enum o) { return e == o; }
bool operator==(Color o) { return e == o.e; }
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";
}
}
};

从外部看,它几乎完全像一个类枚举:

Color red;
red = Color::RED;
Color blue = Color::BLUE;


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

这将输出“red 12”。你可能会超载<<使蓝色输出为字符串(虽然它可能会导致歧义,所以不可能),但它不会与Color::GREEN一起工作,因为它不会自动转换为颜色。

隐式转换为Enum(隐式转换为int或给定类型)的目的是能够做到:

Color color;
switch (color) ...

这是可行的,但这也意味着这也是可行的:

int i = color;

对于枚举类,它不会编译。 如果重载两个以枚举和整数为参数的函数,或者删除隐式转换…

,您应该小心

另一个解决方案将涉及使用实际的枚举类和静态成员:

struct Color
{
enum class Enum { RED, GREEN, BLUE };
static const Enum RED = Enum::RED, GREEN = Enum::GREEN, BLUE = Enum::BLUE;


//same as previous...
};

它可能会占用更多的空间,并且花费更长的时间,但会导致隐式int转换的编译错误。我就会用这个!

虽然这样做肯定有开销,但我认为它比我见过的其他代码更简单,看起来更好。还可以添加功能,这些功能都可以在类中进行范围限定。

编辑:这是有效的,大多数可以在执行前编译:

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 == o.e; }
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) = o.e; 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;
};

要点提供了一个基于c++可变参数模板的简单映射。

这是c++ 17中要点基于类型的映射的简化版本:

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

map<KeyValues...>可以在两个方向上使用:

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

这个例子在godbolt.org上可用

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

gcc-7 -std=c++1z -Ofast -S的结果

main:
mov     eax, 3
ret

如果你的enum看起来像

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

你可以将enum的内容移动到一个新文件中:

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

然后这些值可以被宏包围:

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

该解决方案可以应用于旧的c++标准,它不使用现代的c++元素,但它可以用来生成大量代码,而不需要太多的工作和维护。

嗯,还有另一个选择。一个典型的用例是,您需要为HTTP谓词使用常量,并使用其字符串版本值。

示例:

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


}

VERB类:

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

非常简单的解决方案,但有一个很大的限制:你不能将自定义值分配给enum值,但通过正确的正则表达式,你可以这样做。你也可以添加一个映射,将它们转换回enum值,而不需要更多的努力:

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

很长一段时间以来,我也一直为这个问题感到沮丧,还有以适当的方式将类型转换为字符串的问题。然而,对于最后一个问题,我对是否有可能在标准c++中打印变量的类型?中解释的解决方案感到惊讶,使用了来自我能以constexpr的方式获取c++类型名吗?的思想。使用这种技术,可以构造一个类似的函数来获取枚举值为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;
}

上面的代码只在Clang(参见https://ideone.com/je5Quv)和VS2015上进行了测试,但应该可以通过对整数常量进行一点调整来适应其他编译器。当然,它仍然在底层使用宏,但至少有一个宏不需要访问枚举实现。

我从@antron的想法和实现不同:生成一个真正的枚举类

这个实现满足了原始问题中列出的所有要求,但目前只有一个真正的限制:它假设枚举值要么没有提供,要么如果提供了,必须从0开始,并且无间隙地依次上升。

这并不是一个内在的限制——只是我不使用特别的enum值。如果需要,可以用传统的开关/案例实现替换向量查找。

解决方案使用一些c++17作为内联变量,但如果需要,这可以很容易地避免。为了简单起见,它还使用增加:修剪

最重要的是,它只需要30行代码,没有黑魔法宏。 代码如下。它意味着要放在头文件中,并包含在多个编译模块中

它可以使用与本文前面建议的相同的方式:

ENUM(Channel, int, Red, Green = 1, Blue)
std::out << "My name is " << Channel::Green;
//prints My name is Green

请让我知道这是有用的,以及如何进一步改进。


#include <boost/algorithm/string.hpp>
struct EnumSupportBase {
static std::vector<std::string> split(const std::string s, char delim) {
std::stringstream ss(s);
std::string item;
std::vector<std::string> tokens;
while (std::getline(ss, item, delim)) {
auto pos = item.find_first_of ('=');
if (pos != std::string::npos)
item.erase (pos);
boost::trim (item);
tokens.push_back(item);
}
return tokens;
}
};
#define ENUM(EnumName, Underlying, ...) \
enum class EnumName : Underlying { __VA_ARGS__, _count }; \
struct EnumName ## Support : EnumSupportBase { \
static inline std::vector<std::string> _token_names = split(#__VA_ARGS__, ','); \
static constexpr const char* get_name(EnumName enum_value) { \
int index = (int)enum_value; \
if (index >= (int)EnumName::_count || index < 0) \
return "???"; \
else \
return _token_names[index].c_str(); \
} \
}; \
inline std::ostream& operator<<(std::ostream& os, const EnumName & es) { \
return os << EnumName##Support::get_name(es); \
}

这和尤里·芬克尔斯坦的观点相似;但不需要提高。我正在使用一个地图,所以你可以分配任何值枚举,任何顺序。

枚举类的声明为:

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

下面的代码将自动创建枚举类并重载:

  • '+' '+='用于std::string
  • '<<'表示溪流
  • '~'只是转换为字符串(任何一元运算符都可以,但我个人不喜欢它的清晰度)
  • '*'获取枚举的计数

不需要boost,提供所有需要的功能。

代码:

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

您可以运行代码here

只要你愿意为每个可查询enum编写单独的.h/.cpp对,这个解决方案的语法和功能与常规的c++ enum几乎相同:

// MyEnum.h
#include <EnumTraits.h>
#ifndef ENUM_INCLUDE_MULTI
#pragma once
#end if


enum MyEnum : int ETRAITS
{
EDECL(AAA) = -8,
EDECL(BBB) = '8',
EDECL(CCC) = AAA + BBB
};

.cpp文件是3行样板文件:

// MyEnum.cpp
#define ENUM_DEFINE MyEnum
#define ENUM_INCLUDE <MyEnum.h>
#include <EnumTraits.inl>

使用示例:

for (MyEnum value : EnumTraits<MyEnum>::GetValues())
std::cout << EnumTraits<MyEnum>::GetName(value) << std::endl;

代码

该解决方案需要2个源文件:

// EnumTraits.h
#pragma once
#include <string>
#include <unordered_map>
#include <vector>


#define ETRAITS
#define EDECL(x) x


template <class ENUM>
class EnumTraits
{
public:
static const std::vector<ENUM>& GetValues()
{
return values;
}


static ENUM GetValue(const char* name)
{
auto match = valueMap.find(name);
return (match == valueMap.end() ? ENUM() : match->second);
}


static const char* GetName(ENUM value)
{
auto match = nameMap.find(value);
return (match == nameMap.end() ? nullptr : match->second);
}


public:
EnumTraits() = delete;


using vector_type = std::vector<ENUM>;
using name_map_type = std::unordered_map<ENUM, const char*>;
using value_map_type = std::unordered_map<std::string, ENUM>;


private:
static const vector_type values;
static const name_map_type nameMap;
static const value_map_type valueMap;
};


struct EnumInitGuard{ constexpr const EnumInitGuard& operator=(int) const { return *this; } };
template <class T> constexpr T& operator<<=(T&& x, const EnumInitGuard&) { return x; }

// EnumTraits.inl
#define ENUM_INCLUDE_MULTI


#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL


using EnumType = ENUM_DEFINE;
using TraitsType = EnumTraits<EnumType>;
using VectorType = typename TraitsType::vector_type;
using NameMapType = typename TraitsType::name_map_type;
using ValueMapType = typename TraitsType::value_map_type;
using NamePairType = typename NameMapType::value_type;
using ValuePairType = typename ValueMapType::value_type;


#define ETRAITS ; const VectorType TraitsType::values
#define EDECL(x) EnumType::x <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL


#define ETRAITS ; const NameMapType TraitsType::nameMap
#define EDECL(x) NamePairType(EnumType::x, #x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL


#define ETRAITS ; const ValueMapType TraitsType::valueMap
#define EDECL(x) ValuePairType(#x, EnumType::x) <<= EnumInitGuard()
#include ENUM_INCLUDE
#undef ETRAITS
#undef EDECL

解释

此实现利用了这样一个事实,即枚举定义的带括号元素列表也可以用作类成员初始化的带括号初始化列表。

ETRAITSEnumTraits.inl上下文中求值时, 它展开为EnumTraits<>类的静态成员定义。< / p >

EDECL宏将每个枚举成员转换为初始化列表值,这些值随后被传递到成员构造函数中,以填充枚举信息。

EnumInitGuard类被设计为使用枚举初始化值,然后折叠——留下一个纯枚举数据列表。

好处

  • c++-like语法
  • 适用于enumenum class(*几乎)
  • 适用于具有任何数字基础类型的enum类型
  • 适用于具有自动、显式和分段初始化值的enum类型
  • 大规模重命名工作(智能感知链接保留)
  • 只有5个预处理器符号(3个全局的)

* 与__ABC1相反,enum class类型中引用同一枚举中的其他值的初始化式必须完全限定这些值

不利

  • 每个可查询的enum需要一个单独的.h/.cpp
  • 取决于复杂的macroinclude魔法
  • 小的语法错误会演变成大得多的错误
  • 定义classnamespace作用域枚举不是简单的
  • 没有编译时初始化

评论

在打开EnumTraits.inl时,智能感知会抱怨一些私有成员访问,但由于扩展的宏实际上是在定义类成员,所以这实际上不是问题。

头文件顶部的#ifndef ENUM_INCLUDE_MULTI块是一个小麻烦,可能会缩小到宏或其他东西,但它足够小,以目前的大小生存。

声明命名空间作用域的枚举要求首先在其命名空间作用域内向前声明枚举,然后在全局命名空间中定义枚举。此外,任何使用相同枚举值的枚举初始化器必须完全限定这些值。

namespace ns { enum MyEnum : int; }
enum ns::MyEnum : int ETRAITS
{
EDECL(AAA) = -8,
EDECL(BBB) = '8',
EDECL(CCC) = ns::MyEnum::AAA + ns::MyEnum::BBB
}

我的答案在这里。

你可以同时获得枚举值名称和这些索引,如deque of string。

这种方法只需要少量的复制粘贴和编辑。

当需要枚举类类型值时,需要将获得的结果从size_t类型转换为枚举类类型,但我认为这是一种非常可移植和强大的处理枚举类的方法。

enum class myenum
{
one = 0,
two,
three,
};


deque<string> ssplit(const string &_src, boost::regex &_re)
{
boost::sregex_token_iterator it(_src.begin(), _src.end(), _re, -1);
boost::sregex_token_iterator e;
deque<string> tokens;
while (it != e)
tokens.push_back(*it++);
return std::move(tokens);
}


int main()
{
regex re(",");
deque<string> tokens = ssplit("one,two,three", re);
for (auto &t : tokens) cout << t << endl;
getchar();
return 0;
}

你可以使用一个反射库,比如思考:

enum class MyEnum
{
Zero = 0,
One  = 1,
Two  = 2
};


ponder::Enum::declare<MyEnum>()
.value("Zero", MyEnum::Zero)
.value("One",  MyEnum::One)
.value("Two",  MyEnum::Two);


ponder::EnumObject zero(MyEnum::Zero);


zero.name(); // -> "Zero"

我不确定这种方法是否已经包含在其他答案中(实际上是,见下文)。我遇到过这个问题很多次,但没有找到不使用混淆宏或第三方库的解决方案。因此,我决定编写自己的模糊宏版本。

我想启用的是等价的

enum class test1 { ONE, TWO = 13, SIX };


std::string toString(const test1& e) { ... }


int main() {
test1 x;
std::cout << toString(x) << "\n";
std::cout << toString(test1::TWO) << "\n";
std::cout << static_cast<std::underlying_type<test1>::type>(test1::TWO) << "\n";
//std::cout << toString(123);// invalid
}

应该打印

ONE
TWO
13

我不是宏的粉丝。然而,除非c++本身支持将枚举转换为字符串,否则必须使用某种代码生成和/或宏(我怀疑这种情况不会很快发生)。我正在使用X-macro:

// x_enum.h
#include <string>
#include <map>
#include <type_traits>
#define x_begin enum class x_name {
#define x_val(X) X
#define x_value(X,Y) X = Y
#define x_end };
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end


#define x_begin inline std::string toString(const x_name& e) { \
static std::map<x_name,std::string> names = {
#define x_val(X)      { x_name::X , #X }
#define x_value(X,Y)  { x_name::X , #X }
#define x_end }; return names[e]; }
x_enum_def
#undef x_begin
#undef x_val
#undef x_value
#undef x_end
#undef x_name
#undef x_enum_def

其中大部分是定义和取消定义符号,用户将通过include将这些符号作为参数传递给X-marco。用法是这样的

#define x_name test1
#define x_enum_def x_begin x_val(ONE) , \
x_value(TWO,13) , \
x_val(SIX) \
x_end
#include "x_enum.h"

现场演示 .

注意,我还没有包括选择基础类型。到目前为止,我还不需要它,但它应该是直接修改代码来启用它。

写完这篇文章后,我才意识到它与eferions回答非常相似。也许我以前读过,也许它是灵感的主要来源。我总是不能理解x -宏,直到我写了自己的;)。

(类似https://stackoverflow.com/a/54967187/2338477,略有修改)。

下面是我自己的解决方案,最小的定义魔术和支持单个枚举赋值。

下面是头文件:

#pragma once
#include <string>
#include <map>
#include <regex>


template <class Enum>
class EnumReflect
{
public:
static const char* getEnums() { return ""; }
};


//
//  Just a container for each enumeration type.
//
template <class Enum>
class EnumReflectBase
{
public:
static std::map<std::string, int> enum2int;
static std::map<int, std::string> int2enum;


static void EnsureEnumMapReady( const char* enumsInfo )
{
if (*enumsInfo == 0 || enum2int.size() != 0 )
return;


// Should be called once per each enumeration.
std::string senumsInfo(enumsInfo);
std::regex re("^([a-zA-Z_][a-zA-Z0-9_]+) *=? *([^,]*)(,|$) *");     // C++ identifier to optional " = <value>"
std::smatch sm;
int value = 0;


for (; regex_search(senumsInfo, sm, re); senumsInfo = sm.suffix(), value++)
{
string enumName = sm[1].str();
string enumValue = sm[2].str();


if (enumValue.length() != 0)
value = atoi(enumValue.c_str());


enum2int[enumName] = value;
int2enum[value] = enumName;
}
}
};


template <class Enum>
std::map<std::string, int> EnumReflectBase<Enum>::enum2int;


template <class Enum>
std::map<int, std::string> EnumReflectBase<Enum>::int2enum;




#define DECLARE_ENUM(name, ...)                                         \
enum name { __VA_ARGS__ };                                          \
template <>                                                         \
class EnumReflect<##name>: public EnumReflectBase<##name> {         \
public:                                                             \
static const char* getEnums() { return #__VA_ARGS__; }          \
};








/*
Basic usage:


Declare enumeration:


DECLARE_ENUM( enumName,


enumValue1,
enumValue2,
enumValue3 = 5,


// comment
enumValue4
);


Conversion logic:


From enumeration to string:


printf( EnumToString(enumValue3).c_str() );


From string to enumeration:


enumName value;


if( !StringToEnum("enumValue4", value) )
printf("Conversion failed...");
*/


//
//  Converts enumeration to string, if not found - empty string is returned.
//
template <class T>
std::string EnumToString(T t)
{
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
auto& int2enum = EnumReflect<T>::int2enum;
auto it = int2enum.find(t);


if (it == int2enum.end())
return "";


return it->second;
}


//
//  Converts string to enumeration, if not found - false is returned.
//
template <class T>
bool StringToEnum(const char* enumName, T& t)
{
EnumReflect<T>::EnsureEnumMapReady(EnumReflect<T>::getEnums());
auto& enum2int = EnumReflect<T>::enum2int;
auto it = enum2int.find(enumName);


if (it == enum2int.end())
return false;


t = (T) it->second;
return true;
}

下面是示例测试应用程序:

DECLARE_ENUM(TestEnum,
ValueOne,
ValueTwo,
ValueThree = 5,
ValueFour = 7
);


DECLARE_ENUM(TestEnum2,
ValueOne2 = -1,
ValueTwo2,
ValueThree2 = -4,
ValueFour2
);


void main(void)
{
string sName1 = EnumToString(ValueOne);
string sName2 = EnumToString(ValueTwo);
string sName3 = EnumToString(ValueThree);
string sName4 = EnumToString(ValueFour);


TestEnum t1, t2, t3, t4, t5 = ValueOne;
bool b1 = StringToEnum(sName1.c_str(), t1);
bool b2 = StringToEnum(sName2.c_str(), t2);
bool b3 = StringToEnum(sName3.c_str(), t3);
bool b4 = StringToEnum(sName4.c_str(), t4);
bool b5 = StringToEnum("Unknown", t5);


string sName2_1 = EnumToString(ValueOne2);
string sName2_2 = EnumToString(ValueTwo2);
string sName2_3 = EnumToString(ValueThree2);
string sName2_4 = EnumToString(ValueFour2);


TestEnum2 t2_1, t2_2, t2_3, t2_4, t2_5 = ValueOne2;
bool b2_1 = StringToEnum(sName2_1.c_str(), t2_1);
bool b2_2 = StringToEnum(sName2_2.c_str(), t2_2);
bool b2_3 = StringToEnum(sName2_3.c_str(), t2_3);
bool b2_4 = StringToEnum(sName2_4.c_str(), t2_4);
bool b2_5 = StringToEnum("Unknown", t2_5);

同一头文件的更新版本将保存在这里:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/EnumReflect.h

神奇的枚举头库为c++ 17的枚举(到字符串,从字符串,迭代)提供静态反射。

#include <magic_enum.hpp>


enum Color { RED = 2, BLUE = 4, GREEN = 8 };


Color color = Color::RED;
auto color_name = magic_enum::enum_name(color);
// color_name -> "RED"


std::string color_name{"GREEN"};
auto color = magic_enum::enum_cast<Color>(color_name)
if (color.has_value()) {
// color.value() -> Color::GREEN
};

有关更多示例,请查看主存储库https://github.com/Neargye/magic_enum

缺点在哪里?

这个库使用了一个特定于编译器的hack(基于__PRETTY_FUNCTION__ / __FUNCSIG__),它适用于Clang >= 5, MSVC >= 15.3和GCC >= 9。

Enum值必须在[MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]范围内。

  • 默认为MAGIC_ENUM_RANGE_MIN = -128MAGIC_ENUM_RANGE_MAX = 128

  • 如果默认情况下所有枚举类型都需要另一个范围,请重新定义宏MAGIC_ENUM_RANGE_MINMAGIC_ENUM_RANGE_MAX

  • MAGIC_ENUM_RANGE_MIN必须小于或等于0,并且必须大于INT16_MIN

  • MAGIC_ENUM_RANGE_MAX必须大于0且必须小于INT16_MAX

  • 如果需要为特定的enum类型添加另一个范围,为必要的enum类型添加特化enum_range。

    #include <magic_enum.hpp>
    
    
    enum number { one = 100, two = 200, three = 300 };
    
    
    namespace magic_enum {
    template <>
    struct enum_range<number> {
    static constexpr int min = 100;
    static constexpr int max = 300;
    };
    }
    

我的意见,虽然这和行动要求的不完全相符。这里是相关的参考

namespace enums
{


template <typename T, T I, char ...Chars>
struct enums : std::integral_constant<T, I>
{
static constexpr char const chars[sizeof...(Chars)]{Chars...};
};


template <typename T, T X, typename S, std::size_t ...I>
constexpr auto make(std::index_sequence<I...>) noexcept
{
return enums<T, X, S().chars[I]...>();
}


#define ENUM(s, n) []() noexcept{\
struct S { char const (&chars)[sizeof(s)]{s}; };\
return enums::make<decltype(n), n, S>(\
std::make_index_sequence<sizeof(s)>());}()


#define ENUM_T(s, n)\
static constexpr auto s ## _tmp{ENUM(#s, n)};\
using s ## _enum_t = decltype(s ## _tmp)


template <typename T, typename ...A, std::size_t N>
inline auto map(char const (&s)[N]) noexcept
{
constexpr auto invalid(~T{});


auto r{invalid};


return
(
(
invalid == r ?
r = std::strncmp(A::chars, s, N) ? invalid : A{} :
r
),
...
);
}


}


int main()
{
ENUM_T(echo, 0);
ENUM_T(cat, 1);
ENUM_T(ls, 2);


std::cout << echo_enum_t{} << " " << echo_enum_t::chars << std::endl;


std::cout << enums::map<int, echo_enum_t, cat_enum_t, ls_enum_t>("ls")) << std::endl;


return 0;
}

你生成了一个类型,你可以把它转换成整数或者字符串。

我的解决方案,使用预处理器定义。

你可以在https://repl.it/@JomaCorpFX/nameof#main.cpp上检查这段代码

#include <iostream>
#include <stdexcept>
#include <regex>


typedef std::string String;
using namespace std::literals::string_literals;


class Strings
{
public:
static String TrimStart(const std::string& data)
{
String s = data;
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
return s;
}


static String TrimEnd(const std::string& data)
{
String s = data;
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(),
s.end());
return s;
}


static String Trim(const std::string& data)
{
return TrimEnd(TrimStart(data));
}


static String Replace(const String& data, const String& toFind, const String& toReplace)
{
String result = data;
size_t pos = 0;
while ((pos = result.find(toFind, pos)) != String::npos)
{
result.replace(pos, toFind.length(), toReplace);
pos += toReplace.length();
pos = result.find(toFind, pos);
}
return result;
}


};


static String Nameof(const String& name)
{
std::smatch groups;
String str = Strings::Trim(name);
if (std::regex_match(str, groups, std::regex(u8R"(^&?([_a-zA-Z]\w*(->|\.|::))*([_a-zA-Z]\w*)$)")))
{
if (groups.size() == 4)
{
return groups[3];
}
}
throw std::invalid_argument(Strings::Replace(u8R"(nameof(#). Invalid identifier "#".)", u8"#", name));
}


#define nameof(name) Nameof(u8## #name ## s)
#define cnameof(name) Nameof(u8## #name ## s).c_str()


enum TokenType {
COMMA,
PERIOD,
Q_MARK
};


struct MyClass
{
enum class MyEnum : char {
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
};


int main() {
String greetings = u8"Hello"s;
std::cout << nameof(COMMA) << std::endl;
std::cout << nameof(TokenType::PERIOD) << std::endl;
std::cout << nameof(TokenType::Q_MARK) << std::endl;
std::cout << nameof(int) << std::endl;
std::cout << nameof(std::string) << std::endl;
std::cout << nameof(Strings) << std::endl;
std::cout << nameof(String) << std::endl;
std::cout << nameof(greetings) << std::endl;
std::cout << nameof(&greetings) << std::endl;
std::cout << nameof(greetings.c_str) << std::endl;
std::cout << nameof(std::string::npos) << std::endl;
std::cout << nameof(MyClass::MyEnum::AAA) << std::endl;
std::cout << nameof(MyClass::MyEnum::BBB) << std::endl;
std::cout << nameof(MyClass::MyEnum::CCC) << std::endl;




std::cin.get();
return 0;
}

输出

COMMA
PERIOD
Q_MARK
int
string
Strings
String
greetings
greetings
c_str
npos
AAA
BBB
CCC

铿锵声

clang

Visual c++

enter image description here

你可以滥用用户定义的文字来达到想要的结果:

enum
{
AAA = "AAA"_h8,
BB = "BB"_h8,
};
   

std::cout << h8::to_string(AAA) << std::endl;
std::cout << h8::to_string(BB) << std::endl;

这将一个字符串打包成一个整数,这是可逆的。看看例子在这里

我不太喜欢与此相关的所有花哨的框架(宏、模板和类),因为我认为使用它们会使代码更难理解,并且会增加编译时间并隐藏错误。总的来说,我想要一个简单的解决这个问题的方法。添加额外的100行代码并不简单。

最初问题中给出的示例与我实际在生产中使用的代码非常接近。相反,我只想对原来的示例查找函数提出一些小的改进:

const std::string& magic(MyClass::MyEnum e)
{
static const std::string OUT_OF_RANGE = "Out of range";
#define ENTRY(v) { MyClass::MyEnum::v, "MyClass::MyEnum::" #v }
static const std::unordered_map<MyClass::MyEnum, std::string> LOOKUP {
ENTRY(AAA),
ENTRY(BBB),
ENTRY(CCC),
};
#undef ENTRY
auto it  = LOOKUP.find(e);
return ((it != LOOKUP.end()) ? it->second : OUT_OF_RANGE);
}

具体地说:

    内部数据结构现在是“static”和“const”。这些都是 不变的,因此不需要在每次调用时构造这些 函数,这样做效率很低。相反,这些是
  1. 返回值现在是'const std::string&'。这 函数将只返回对已分配对象的引用 string对象具有“静态”生命周期,因此不需要这样做 返回时复制它们
  2. 映射类型现在是'std::unordered_map' 对于O(1)访问而不是std::map的O(log(N))访问,
  3. 使用ENTRY宏可以使代码更简洁,也可以避免潜在的 在字符串字面量中输入名称时出现的拼写错误。(如果 程序员输入一个无效的名称,将导致编译器错误)

你可以使用select()函数,它实际上只是一个简短的开关;这不是真正意义上的解决方案,但它让生活更容易:

enum
{
NORMAL,
INVALID
} state(NORMAL);


//std::cout << (state ? "INVALID" : "NORMAL") << std::endl;
std::cout << select(state, "NORMAL", "INVALID") << std::endl;

select()函数在SIMD/GPU编程中很常见。它们是三元?:操作符的泛化。你也可以将select()视为一个函数数组(一个实现数组数据结构的函数)。

这是一个完整的例子