(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");
c._to_string();
for (Channel c : Channel::_values())
std::cout << c << std::endl;
所有操作都可以使用constexpr
。您还可以实现@ecatmur提到的C++17反射提案。
- 只有一个宏。我认为这是最小的可能性,因为预处理器字符串化(
#
)是当前C ++中将令牌转换为字符串的唯一方法。
- 宏非常不显眼 - 常量声明(包括初始化程序)被粘贴到内置枚举声明中。这意味着它们具有与内置枚举相同的语法和含义。
- 消除了重复。
- 由于
constexpr
,在至少C++11中,实现最自然和有用。它也可以在C ++ 98 + __VA_ARGS__
中使用。它绝对是现代C ++。
该宏的定义有一定的复杂性,因此我将以几种方式进行回答。
将此答案扩展到库的特性非常简单-这里没有遗漏任何“重要”的内容。然而,这相当繁琐,并且存在编译器可移植性问题。
免责声明:我是CodeProject文章和该库的作者。
您可以在Wandbox上在线体验此答案中的代码, 该库以及N4428的实现。该库文档还包含一个如何将其用作N4428的概述,其中解释了该提案的枚举部分。
说明
下面的代码实现了枚举和字符串之间的转换。但是,它也可以扩展到做其他事情,比如迭代。这个答案将一个枚举包装在一个 struct
中。你也可以在一个枚举旁边生成一个 traits struct
。
策略是生成像这样的东西:
struct Channel {
enum _enum : char { __VA_ARGS__ };
constexpr static const Channel _values[] = { __VA_ARGS__ };
constexpr static const char * const _names[] = { #__VA_ARGS__ };
static const char* _to_string(Channel v) { }
constexpr static Channel _from_string(const char *s) { }
};
问题如下:
我们最终会得到类似
{Red = 1, Green, Blue}
的值数组初始化器。这不是有效的 C++,因为
Red
不是可分配的表达式。解决方法是将每个常量转换为具有赋值运算符但会丢弃赋值的类型
T
:
{(T)Red = 1, (T)Green, (T)Blue}
。
同样地,我们最终会得到
{"Red = 1", "Green", "Blue"}
作为名称数组的初始化器。我们需要去掉
" = 1"
。我不知道有什么好的方法可以在编译时做到这一点,所以我们将其推迟到运行时。因此,
_to_string
将不是
constexpr
,但
_from_string
仍然可以是
constexpr
,因为我们可以将空格和等号视为未修剪字符串的终止符进行比较。
以上两者都需要一个“映射”宏,该宏可以将另一个宏应用于
__VA_ARGS__
中的每个元素。这是相当标准的。此答案包括一个简单版本,可以处理多达 8 个元素。
如果宏要真正自包含,它就不能声明需要单独定义的静态数据。实际上,这意味着数组需要特殊处理。有两种可能的解决方案:命名空间作用域中的
constexpr
(或只是
const
)数组,或者在非
constexpr
静态内联函数中使用常规数组。本答案的代码适用于 C++11,并采用前一种方法。CodeProject 文章适用于 C++98,并采用后一种方法。
代码
#include <cstddef>
#include <cstring>
#include <stdexcept>
#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))
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;
};
#define IGNORE_ASSIGN_SINGLE(e) (ignore_assign<_underlying>)e,
#define IGNORE_ASSIGN(...) \
IDENTITY(MAP(IGNORE_ASSIGN_SINGLE, __VA_ARGS__))
#define STRINGIZE_SINGLE(e) #e,
#define STRINGIZE(...) IDENTITY(MAP(STRINGIZE_SINGLE, __VA_ARGS__))
constexpr const char terminators[] = " =\t\r\n";
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);
}
#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; \
} \
};
并且
#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
中删除其中一个case将导致编译器发出警告(取决于您的编译器和标志)。此外,请注意,在编译期间将
"Red"
转换为枚举类型。
std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
- ecatmurDEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))
并不是那么糟糕,除非您必须维护生成宏... 在我看来,将这种情况放入语言中只是另一种在缺少更强大的模板/宏混合体的情况下的替代方法。我们不应该为了能够说宏不再有用而将所有这些有用的宏用例添加到语言中。 - PlasmaHH#define
。你所问的是一个可用的解决方案。今天的正确答案是,在以后没有完全“正确”的解决方案之前(即现在接受@ecatmur)。 - antron