在现代 C++11/C++14/C++17 和未来的 C++20 中枚举到字符串
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28828957/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
enum to string in modern C++11 / C++14 / C++17 and future C++20
提问by olibre
Contrary to all other similar questions, this question is about using the new C++ features.
与所有其他类似问题相反,这个问题是关于使用新的 C++ 特性。
- 2008 cIs there a simple way to convert C++ enum to string?
- 2008 cEasy way to use variables of enum types as string in C?
- 2008 c++How to easily map c++ enums to strings
- 2008 c++Making something both a C identifier and a string?
- 2008 c++Is there a simple script to convert C++ enum to string?
- 2009 c++How to use enums as flags in C++?
- 2011 c++How to convert an enum type variable to a string?
- 2011 c++Enum to String C++
- 2011 c++How to convert an enum type variable to a string?
- 2012 cHow to convert enum names to string in c
- 2013 cStringifying an conditionally compiled enum in C
- 2008 c有没有一种简单的方法可以将 C++ 枚举转换为字符串?
- 2008 c在 C 中使用枚举类型变量作为字符串的简单方法?
- 2008 c++如何轻松地将 c++ 枚举映射到字符串
- 2008 c++使某些东西既是 C 标识符又是字符串?
- 2008 c++是否有一个简单的脚本可以将 C++ 枚举转换为字符串?
- 2009 c++如何在 C++ 中使用枚举作为标志?
- 2011 c++如何将枚举类型变量转换为字符串?
- 2011 C++枚举到字符串 C++
- 2011 c++如何将枚举类型变量转换为字符串?
- 2012 c如何在 c 中将枚举名称转换为字符串
- 2013 c在 C 中对条件编译的枚举进行字符串化
After reading many answers, I did not yet find any:
看了很多答案,还是没有找到:
- Elegant way using C++11, C++14or C++17new features
- Or something ready-to-use in Boost
- Else something planned for C++20
Example
例子
An example is often better than a long explanation.
You can compile and run this snippet on Coliru.
(Another former exampleis also available)
一个例子通常比一个长的解释更好。
您可以在Coliru上编译并运行此代码段。
(另一个以前的例子也可用)
#include <map>
#include <iostream>
struct MyClass
{
enum class MyEnum : char {
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
};
// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
{ MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
{ MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
{ MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
};
auto it = MyEnumStrings.find(e);
return it == MyEnumStrings.end() ? "Out of range" : it->second;
}
int main()
{
std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}
Constraints
约束
- Please no valueless duplication of other answersor basic link.
- Please avoid bloat macro-based answer, or try to reduce the
#define
overhead as minimum as possible. - Please no manual
enum
->string
mapping.
Nice to have
很高兴有
- Support
enum
values starting from a number different from zero - Support negative
enum
values - Support fragmented
enum
values - Support
class enum
(C++11) - Support
class enum : <type>
having any allowed<type>
(C++11) - Compile-time (not run-time) conversions to a string,
or at least fast execution at run-time (e.g.std::map
is not a great idea...) constexpr
(C++11, then relaxed in C++14/17/20)noexcept
(C++11)- C++17/C++20friendly snippet
- 支持
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友好代码段
One possible idea could be using the C++ compiler capabilities to generate C++ code at compilation-time using meta-programming tricks based on variadic template class
and constexpr
functions...
一种可能的想法是使用 C++ 编译器功能在编译时使用基于variadic template class
和constexpr
函数的元编程技巧生成 C++ 代码......
采纳答案by Neargye
Magic Enumheader-only library provides static reflection for enums (to string, from string, iteration) for C++17.
Magic Enum仅标头库为 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
};
For more examples check home repository https://github.com/Neargye/magic_enum.
有关更多示例,请查看主存储库https://github.com/Neargye/magic_enum。
Where is the drawback?
缺点在哪里?
This library uses a compiler-specific hack (based on __PRETTY_FUNCTION__
/ __FUNCSIG__
), which works on Clang >= 5, MSVC >= 15.3 and GCC >= 9.
该库使用特定于编译器的 hack(基于__PRETTY_FUNCTION__
/ __FUNCSIG__
),适用于 Clang >= 5、MSVC >= 15.3 和 GCC >= 9。
Enum value must be in range [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]
.
枚举值必须在 range 内[MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]
。
By default
MAGIC_ENUM_RANGE_MIN = -128
,MAGIC_ENUM_RANGE_MAX = 128
.If need another range for all enum types by default, redefine the macro
MAGIC_ENUM_RANGE_MIN
andMAGIC_ENUM_RANGE_MAX
.MAGIC_ENUM_RANGE_MIN
must be less or equals than0
and must be greater thanINT16_MIN
.MAGIC_ENUM_RANGE_MAX
must be greater than0
and must be less thanINT16_MAX
.If need another range for specific enum type, add specialization enum_range for necessary enum type.
#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; }; }
默认情况下
MAGIC_ENUM_RANGE_MIN = -128
,MAGIC_ENUM_RANGE_MAX = 128
。如果默认情况下需要所有枚举类型的另一个范围,请重新定义宏
MAGIC_ENUM_RANGE_MIN
和MAGIC_ENUM_RANGE_MAX
.MAGIC_ENUM_RANGE_MIN
必须小于或等于0
并且必须大于INT16_MIN
。MAGIC_ENUM_RANGE_MAX
必须大于0
且必须小于INT16_MAX
。如果需要特定枚举类型的另一个范围,请为必要的枚举类型添加专业化 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; }; }
回答by antron
(The approach of the better_enumslibrary)
(Better_enums库的方法)
There is a way to do enum to string in current C++ that looks like this:
有一种方法可以在当前的 C++ 中对字符串进行枚举,如下所示:
ENUM(Channel, char, Red = 1, Green, Blue)
// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };
Usage:
用法:
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...
All operations can be made constexpr
. You can also implement the C++17 reflection proposal mentioned in the answer by @ecatmur.
可以进行所有操作constexpr
。您还可以实现@ecatmur 在答案中提到的 C++17 反射提案。
- There is only one macro. I believe this is the minimum possible, because preprocessor stringization (
#
) is the only way to convert a token to a string in current C++. - The macro is pretty unobtrusive – the constant declarations, including initializers, are pasted into a built-in enum declaration. This means they have the same syntax and meaning as in a built-in enum.
- Repetition is eliminated.
- The implementation is most natural and useful in at least C++11, due to
constexpr
. It can also be made to work with C++98 +__VA_ARGS__
. It is definitely modern C++.
- 只有一个宏。我相信这是可能的最小值,因为预处理器字符串化 (
#
) 是当前 C++ 中将标记转换为字符串的唯一方法。 - 宏非常不引人注目——常量声明,包括初始化器,被粘贴到一个内置的枚举声明中。这意味着它们具有与内置枚举相同的语法和含义。
- 重复被消除。
- 由于
constexpr
. 它也可以与 C++98 + 一起使用__VA_ARGS__
。它绝对是现代 C++。
The macro's definition is somewhat involved, so I'm answering this in several ways.
宏的定义有点涉及,所以我从几个方面回答这个问题。
- The bulk of this answer is an implementation that I think is suitable for the space constraints on StackOverflow.
- There is also a CodeProject articledescribing the basics of the implementation in a long-form tutorial. [Should I move it here? I think it's too much for a SO answer].
- There is a full-featured library "Better Enums"that implements the macro in a single header file. It also implements N4428 Type Property Queries, the current revision of the C++17 reflection proposal N4113. So, at least for enums declared through this macro, you can have the proposed C++17 enum reflection now, in C++11/C++14.
- 这个答案的大部分是我认为适合 StackOverflow 上的空间限制的实现。
- 还有一篇CodeProject 文章在长篇教程中描述了实现的基础知识。[我应该把它移到这里吗?我认为这样的答案太多了]。
- 有一个功能齐全的库“Better Enums”,它在单个头文件中实现了宏。它还实现了N4428 类型属性查询,这是 C++17 反射提案 N4113 的当前修订版。因此,至少对于通过此宏声明的枚举,您现在可以在 C++11/C++14 中使用建议的 C++17 枚举反射。
It is straightforward to extend this answer to the features of the library – nothing "important" is left out here. It is, however, quite tedious, and there are compiler portability concerns.
将这个答案扩展到库的功能很简单——这里没有遗漏任何“重要的”。但是,它非常乏味,并且存在编译器可移植性问题。
Disclaimer: I am the author of both the CodeProject article and the library.
免责声明:我是 CodeProject 文章和库的作者。
You can try the code in this answer, the library, and the implementation of N4428live online in Wandbox. The library documentation also contains an overview of how to use it as N4428, which explains the enums portion of that proposal.
您可以尝试此答案中的代码、库以及N4428在 Wandbox 中在线直播的实现。库文档还包含如何将其用作 N4428的概述,它解释了该提案的枚举部分。
Explanation
解释
The code below implements conversions between enums and strings. However, it can be extended to do other things as well, such as iteration. This answer wraps an enum in a struct
. You can also generate a traits struct
alongside an enum instead.
下面的代码实现了枚举和字符串之间的转换。但是,它也可以扩展到做其他事情,例如迭代。此答案将枚举包装在struct
. 您还可以struct
在枚举旁边生成一个特征。
The strategy is to generate something like this:
策略是生成如下内容:
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 */ }
};
The problems are:
问题是:
- We will end up with something like
{Red = 1, Green, Blue}
as the initializer for the values array. This is not valid C++, becauseRed
is not an assignable expression. This is solved by casting each constant to a typeT
that has an assignment operator, but will drop the assignment:{(T)Red = 1, (T)Green, (T)Blue}
. - Similarly, we will end up with
{"Red = 1", "Green", "Blue"}
as the initializer for the names array. We will need to trim off the" = 1"
. I am not aware of a great way to do this at compile time, so we will defer this to run time. As a result,_to_string
won't beconstexpr
, but_from_string
can still beconstexpr
, because we can treat whitespace and equals signs as terminators when comparing with untrimmed strings. - Both the above need a "mapping" macro that can apply another macro to each element in
__VA_ARGS__
. This is pretty standard. This answer includes a simple version that can handle up to 8 elements. - If the macro is to be truly self-contained, it needs to declare no static data that requires a separate definition. In practice, this means arrays need special treatment. There are two possible solutions:
constexpr
(or justconst
) arrays at namespace scope, or regular arrays in non-constexpr
static inline functions. The code in this answer is for C++11 and takes the former approach. The CodeProject article is for C++98 and takes the latter.
- 我们最终会得到类似
{Red = 1, Green, Blue}
values 数组的初始值设定项的东西。这不是有效的 C++,因为Red
它不是可赋值的表达式。这是通过将每个常量转换为T
具有赋值运算符的类型来解决的,但会删除赋值:{(T)Red = 1, (T)Green, (T)Blue}
。 - 类似地,我们最终将
{"Red = 1", "Green", "Blue"}
作为 names 数组的初始值设定项。我们需要修剪掉" = 1"
. 我不知道在编译时有什么好方法可以做到这一点,所以我们将把它推迟到运行时。因此,_to_string
不会是constexpr
,但_from_string
仍然可以是constexpr
,因为在与未修剪的字符串进行比较时,我们可以将空格和等号视为终止符。 - 以上都需要一个“映射”宏,可以将另一个宏应用于
__VA_ARGS__
. 这是很标准的。这个答案包括一个可以处理多达 8 个元素的简单版本。 - 如果宏要真正自包含,则它不需要声明需要单独定义的静态数据。实际上,这意味着数组需要特殊处理。有两种可能的解决方案:(
constexpr
或只是const
)命名空间范围内的数组,或非constexpr
静态内联函数中的常规数组。此答案中的代码适用于 C++11,并采用前一种方法。CodeProject 文章适用于 C++98 并采用后者。
Code
代码
#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 '// 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), "");
'.
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] == 'meta::get_base_name_v<
meta::get_element_m<
meta::get_enumerators_m<reflexpr(MyEnum)>,
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] = '#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;
}
'; \
} \
\
initialized = true; \
} \
\
return the_names; \
} \
};
and
和
DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
The program above prints Red
, as you would expect. There is a degree of type safety, since you can't create an enum without initializing it, and deleting one of the cases from the switch
will result in a warning from the compiler (depending on your compiler and flags). Also, note that "Red"
was converted to an enum during compilation.
Red
如您所料,上面的程序会打印。存在一定程度的类型安全性,因为您不能在不初始化的情况下创建枚举,并且从 中删除其中一个案例switch
将导致编译器发出警告(取决于您的编译器和标志)。另请注意,"Red"
在编译期间已转换为枚举。
回答by ecatmur
For C++17C++20, you will be interested in the work of the Reflection Study Group (SG7). There is a parallel series of papers covering wording(P0194) and rationale, design and evolution(P0385). (Links resolve to the latest paper in each series.)
对于C++17C++20,您将对 Reflection Study Group (SG7) 的工作感兴趣。有一系列平行的论文涵盖措辞( P0194) 和基本原理、设计和演变( P0385)。(链接解析为每个系列中的最新论文。)
As of P0194r2 (2016-10-15), the syntax would use the proposed reflexpr
keyword:
从 P0194r2 (2016-10-15) 开始,语法将使用建议的reflexpr
关键字:
#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;
}
For example (adapted from Matus Choclik's reflexpr branch of clang):
例如(改编自Matus Choclik 的 clang 反射分支):
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;
}
Static reflection failed to make it into C++17 (rather, into the probably-final draft presented at the November 2016 standards meeting in Issaquah) but there is confidence that it will make it into C++20; from Herb Sutter's trip report:
静态反射未能进入 C++17(而是进入 2016 年 11 月在 Issaquah 举行的标准会议上提出的可能最终草案),但有信心将其纳入 C++20;来自Herb Sutter 的旅行报告:
In particular, the Reflectionstudy group reviewed the latest merged static reflection proposal and found it ready to enter the main Evolution groups at our next meeting to start considering the unified static reflection proposal for a TS or for the next standard.
特别是,反射研究组了最新的合并静态反射提案,并发现它已准备好在我们下次会议上进入主要演进组,开始考虑针对 TS 或下一个标准的统一静态反射提案。
回答by Danilo Ramos
This is similar to Yuri Finkelstein; but does not required boost. I am using a map so you can assign any value to the enums, any order.
这与尤里·芬克尔斯坦 (Yuri Finkelstein) 相似;但不需要升压。我正在使用地图,因此您可以为枚举分配任何值,任何顺序。
Declaration of enum class as:
枚举类声明为:
enum class EtherType : uint16_t
{
ARP = 0x0806,
IPv4 = 0x0800,
VLAN = 0x8100,
IPv6 = 0x86DD
};
The following code will automatically create the enum class and overload:
以下代码将自动创建枚举类和重载:
- '+' '+=' for std::string
- '<<' for streams
- '~' just to convert to string (Any unary operator will do, but I personally don't like it for clarity)
- '*' to get the count of enums
- '+' '+=' 用于 std::string
- '<<' 表示流
- '~' 只是转换为字符串(任何一元运算符都可以,但为了清楚起见,我个人不喜欢它)
- '*' 获取枚举数
No boost required, all required functions provided.
无需升压,提供了所有必需的功能。
Code:
代码:
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);
}
Example:
例子:
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;
}
You can run the code here
You can run the code here
回答by StackedCrooked
Back in 2011 I spent a weekend fine-tuning a macro-based solutionand ended up never using it.
早在 2011 年,我就花了一个周末对基于宏的解决方案进行微调,但最终从未使用过它。
My current procedure is to start Vim, copy the enumerators in an empty switch body, start a new macro, transform the first enumerator into a case statement, move the cursor to the beginning of the next line,stop the macro and generate the remaining case statements by running the macro on the other enumerators.
我当前的程序是启动 Vim,将枚举器复制到一个空的 switch 体中,启动一个新宏,将第一个枚举器转换为 case 语句,将光标移动到下一行的开头,停止宏并生成剩余的 case通过在其他枚举器上运行宏来执行语句。
Vim macros are more fun than C++ macros.
Vim 宏比 C++ 宏更有趣。
Real-life example:
现实生活中的例子:
// 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{};
I will create this:
我将创建这个:
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 ...);
}
And that's how I get by.
我就是这样度过的。
Native support for enum stringification would be much better though. I'm very interested to see the results of the reflection workgroup in C++17.
不过,对枚举字符串化的本机支持会好得多。我非常有兴趣在 C++17 中看到反射工作组的结果。
An alternative way to do it was posted by @sehe in the comments.
@sehe 在评论中发布了另一种方法。
回答by PaperBirdMaster
I don't know if you're going to like this or not, I'm not pretty happy with this solution but it is a C++14 friendly approach because it is using template variables and abusing template specialization:
我不知道你是否会喜欢这个,我对这个解决方案不太满意,但它是一种 C++14 友好的方法,因为它使用模板变量并滥用模板特化:
initialize
(
MyEnum::AAA, "AAA",
MyEnum::BBB, "BBB",
MyEnum::CCC, "CCC"
);
The worst about this approach is that is a pain to maintain, but it is also a pain to maintain some of other similar aproaches, aren't they?
这种方法最糟糕的是维护起来很痛苦,但维护其他一些类似的方法也很痛苦,不是吗?
Good points about this aproach:
这种方法的优点:
- Using variable tempates (C++14 feature)
- With template specialization we can "detect" when an invalid value is used (but I'm not sure if this could be useful at all).
- It looks neat.
- The name lookup is done at compile time.
- 使用变量模板(C++14 特性)
- 通过模板专业化,我们可以“检测”何时使用了无效值(但我不确定这是否有用)。
- 它看起来很整洁。
- 名称查找在编译时完成。
Edit
编辑
Misterious user673679you're right; the C++14 variable template approach doesn't handles the runtime case, it was my fault to forget it :(
Misterious user673679你是对的; C++14 变量模板方法不处理运行时情况,忘记它是我的错:(
But we can still use some modern C++ features and variable template plus variadic template trickery to achieve a runtime translation from enum value to string... it is as bothersome as the others but still worth to mention.
但是我们仍然可以使用一些现代 C++ 特性和变量模板加上可变参数模板技巧来实现从枚举值到字符串的运行时转换......它和其他人一样麻烦但仍然值得一提。
Let's start using a template alias to shorten the access to a enum-to-string map:
让我们开始使用模板别名来缩短对枚举到字符串映射的访问:
std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';
Then, the variadic template trickery:
然后,可变参数模板技巧:
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);
}
The "best trick" here is the use of variable template for the map which contains the values and names of each enum entry; this map will be the same in each translation unit and have the same name everywhere so is pretty straightforward and neat, if we call the initialize
function like this:
这里的“最佳技巧”是对包含每个枚举条目的值和名称的映射使用变量模板;这个映射在每个翻译单元中都是相同的,并且在任何地方都有相同的名称,所以非常简单和整洁,如果我们这样调用initialize
函数:
std::cout << MyEnum::AAA << '\n';
We are asigning names to each MyEnum
entry and can be used in runtime:
我们为每个MyEnum
条目分配名称,可以在运行时使用:
enum MyEnum
{
AAA = -8,
BBB = '8',
CCC = AAA + BBB
};
But can be improved with SFINAE and overloading <<
operator:
但可以通过 SFINAE 和重载<<
运算符进行改进:
AAA = -8,
BBB = '8',
CCC = AAA + BBB
With the correct operator <<
now we can use the enum this way:
有了正确的operator <<
现在,我们可以这样使用枚举:
// 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
This is also bothersome to maintain and can be improved, but hope you get the idea.
这也很麻烦维护并且可以改进,但希望你能理解。
回答by eferion
If your enum
looks like
如果你enum
看起来像
enum MyEnum
{
#define ITEM(X,Y) X=Y,
#include "enum_definition_file"
};
You can move the content of the enum
to a new 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");
}
And then the values can be surrounded by a macro:
然后这些值可以被一个宏包围:
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)>
)
Next step may be include the items in the enum
again:
下一步可能是enum
再次包含以下项目:
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;
And finally you can generate utility functions about this enum
:
最后,您可以生成关于此的实用程序函数enum
:
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")
The solution can be applied to older C++ standards and it does not use modern C++ elements but it can be used to generate lot of code without too much effort and maintenance.
该解决方案可以应用于旧的 C++ 标准,它不使用现代 C++ 元素,但它可以用来生成大量代码,而无需太多的努力和维护。
回答by Mense
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 generatorto generate simple switch case statements.
几天前我遇到了同样的问题。如果没有一些奇怪的宏魔法,我找不到任何 C++ 解决方案,所以我决定编写一个 CMake 代码生成器来生成简单的 switch case 语句。
Usage:
用法:
/*!
* \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
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).
该函数搜索文件系统中的包含文件(使用 include_directories 命令提供的包含目录),读取它们并执行一些正则表达式以生成类和函数。
NOTE: constexpr implies inline in C++, so using the USE_CONSTEXPR option will generate a header only class!
注意:constexpr 意味着在 C++ 中内联,因此使用 USE_CONSTEXPR 选项将生成一个只有头的类!
Example:
例子:
./includes/a.h:
./包括/啊:
/*!
* \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>";
}
}
}
./CMakeLists.txt:
./CMakeLists.txt:
First = 5
Second
Third = 7
Fourth
Fifth=11
Generates:
产生:
./enum2Str.hpp:
./enum2Str.hpp:
#include <iosfwd>
enum class Hallo
{
First = 5,
Second = 6,
Third = 7,
Fourth = 8,
Fifth = 11
};
std::ostream & operator << (std::ostream &, const Hallo&);
./enum2Str.cpp:
./enum2Str.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;
}
Update:
更新:
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
该脚本现在还支持作用域枚举(枚举类|结构),我将它与我经常使用的其他一些脚本一起移到了一个单独的仓库:https: //github.com/mensinda/cmakeBuildTools
回答by yeoman
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++.
java和python中的生成器代码,超级容易移植到你喜欢的任何语言,包括C++。
Also super easy to extend by whatever functionality you want.
也非常容易扩展您想要的任何功能。
example input:
示例输入:
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);
}
}
}
generated header:
生成的标题:
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()
generated cpp file
生成的cpp文件
XXX_ENUM(foo,(a,b,(c,42)));
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.
和生成器,以非常简洁的形式作为移植和扩展的模板。此示例代码确实尝试避免覆盖任何文件,但仍然需要您自担风险使用它。
enum foo {
a,
b,
c=42
};
And a port to Python 3.5 because different enough to be potentially helpful
和 Python 3.5 的端口,因为不同足以有潜在的帮助
#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)
回答by PlasmaHH
As per request from the OP, here a stripped down version of the ugly macro solution based on Boost Preprosessorand Variadic Macros.
根据 OP 的要求,这里有一个基于Boost Preprosessor和Variadic Macros的丑陋宏解决方案的精简版。
It allows for a simple list like syntax of the enumerator elements along with setting values for specific elements so that
它允许一个简单的列表,如枚举元素的语法以及特定元素的设置值,以便
#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';
}
expands to
扩展到
> 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)));
^
Alongside with the necessary functions to output and do some conversion back. This macro has been around here for ages, and I am not totally sure that its the most efficient way, or that it is a conforming way, but it has ever since been working
除了输出和做一些转换的必要功能外。这个宏已经存在多年了,我不完全确定它是最有效的方式,还是符合标准的方式,但从那时起它就一直在工作
The complete code can be seen in action at both Ideoneand Coliru.
完整的代码可以在Ideone和Coliru 上看到。
Its gargantuan ugliness is above; I would have put it behind spoilers to protect your eyes, if I knew how, but markdown doesn't like me.
它巨大的丑陋在上面;如果我知道怎么做,我会把它放在剧透后面来保护你的眼睛,但降价不喜欢我。
The library (merged within one single header file)
库(合并在一个单独的头文件中)
foo::a = foo::a
(int)foo::c = 42
to_string(foo::b) = foo::b
xxx::enum_cast<foo>("b") = foo::b