与所有其他类似问题相反,这个问题是关于使用新的 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 中对条件编译的枚举进行字符串化
在阅读了许多答案后,我还没有找到任何答案:
使用 C++11、C++14 或 C++17 新功能的优雅方式
或者在 Boost 中可以使用的东西
为 C++20 计划的其他内容
例子
一个例子通常比一个长解释更好。
您可以在 Coliru 上编译和运行此代码段。
(Another former example 也可用)
#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 开销。
请不要手动枚举 -> 字符串映射。
很高兴有
支持从不为零的数字开始的枚举值
支持负枚举值
支持碎片化枚举值
支持类枚举 (C++11)
支持类枚举:
编译时(不是运行时)转换为字符串,或者至少在运行时快速执行(例如 std::map 不是一个好主意......)
constexpr(C++11,然后在 C++14/17/20 中放宽)
无异常 (C++11)
C++17/C++20 友好代码片段
一种可能的想法是使用 C++ 编译器功能在编译时使用基于 variadic template class
和 constexpr
函数的元编程技巧生成 C++ 代码......
std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
DEC_ENUM(enumname, (a,b,c,(d,b),(e,42)))
,除非您必须维护生成宏......并且恕我直言,将这种情况放入语言中只是另一种hack,而不是更强大的模板/宏混合。我们不应该将所有有用的宏用例添加到语言中,只是为了能够说宏不再有用。
#define
。您所要求的是一个可用的解决方案。今天正确的答案 是完全“正确”的解决方案要到以后才可用(即现在接受@ecatmur)。
(better_enums 库的方法)
有一种方法可以在当前 C++ 中对字符串进行枚举,如下所示:
ENUM(Channel, char, Red = 1, Green, Blue)
// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };
用法:
Channel c = Channel::_from_string("Green"); // Channel::Green (2)
c._to_string(); // string "Green"
for (Channel c : Channel::_values())
std::cout << c << std::endl;
// And so on...
可以进行所有操作constexpr
。您还可以实现@ecatmur 的答案中提到的 C++17 反射提案。
只有一个宏。我相信这是最小的可能,因为预处理器字符串化 (#) 是将标记转换为当前 C++ 中的字符串的唯一方法。
这个宏非常不显眼——常量声明,包括初始化器,被粘贴到一个内置的枚举声明中。这意味着它们具有与内置枚举相同的语法和含义。
重复被消除。
由于 constexpr,该实现至少在 C++11 中是最自然和最有用的。它也可以用于 C++98 + __VA_ARGS__。它绝对是现代 C++。
宏的定义有些牵扯,所以我从几个方面来回答这个问题。
这个答案的大部分是我认为适合 StackOverflow 上的空间限制的实现。
还有一篇 CodeProject 文章在长篇教程中描述了实现的基础知识。 [我应该把它移到这里吗?我认为这样的答案太多了]。
有一个功能齐全的库“Better Enums”,它在单个头文件中实现了宏。它还实现了 N4428 类型属性查询,这是 C++17 反射提案 N4113 的当前修订版。因此,至少对于通过此宏声明的枚举,您现在可以在 C++11/C++14 中使用建议的 C++17 枚举反射。
将这个答案扩展到库的功能很简单——这里没有遗漏任何“重要”的东西。然而,它相当乏味,并且存在编译器可移植性问题。
免责声明:我是 CodeProject 文章和库的作者。
您可以在 Wandbox 中在线试用 code in this answer、the library 和 the implementation of N4428。库文档还包含一个 overview of how to use it as N4428,它解释了该提案的枚举部分。
解释
下面的代码实现了枚举和字符串之间的转换。但是,它也可以扩展到做其他事情,例如迭代。此答案将枚举包装在 struct
中。您也可以在枚举旁边生成一个特征 struct
。
策略是生成如下内容:
struct Channel {
enum _enum : char { __VA_ARGS__ };
constexpr static const Channel _values[] = { __VA_ARGS__ };
constexpr static const char * const _names[] = { #__VA_ARGS__ };
static const char* _to_string(Channel v) { /* easy */ }
constexpr static Channel _from_string(const char *s) { /* easy */ }
};
问题是:
我们最终会得到像 {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,采用后者。
代码
#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"
在编译期间被转换为枚举。
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
};
有关更多示例,请查看主存储库 https://github.com/Neargye/magic_enum。
缺点在哪里?
该库使用特定于编译器的 hack(基于 __PRETTY_FUNCTION__
/ __FUNCSIG__
),适用于 Clang >= 5、MSVC >= 15.3 和 GCC >= 9。
枚举值必须在 [MAGIC_ENUM_RANGE_MIN, MAGIC_ENUM_RANGE_MAX]
范围内。
默认情况下 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
is_valid
函数模板。这可能会导致大量的编译时间,因此默认情况下该范围非常保守。以下是该技术的简化版本:godbolt.org/z/GTxfva
对于 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's reflexpr branch of 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;来自 Herb Sutter's trip report:
特别是,Reflection 研究组审查了最新的合并静态反射提案,并发现它已准备好在我们的下一次会议上进入主要的 Evolution 组,以开始考虑针对 TS 或下一个标准的统一静态反射提案。
这类似于 Yuri Finkelstein;但不需要提升。我正在使用地图,因此您可以为枚举分配任何值,任何顺序。
枚举类声明为:
DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
以下代码将自动创建枚举类并重载:
'+' '+=' 代表 std::string
'<<' 用于流
'~' 只是为了转换为字符串(任何一元运算符都可以,但为了清楚起见,我个人不喜欢它)
'*' 获取枚举数
无需升压,提供所有必需的功能。
代码:
#include <algorithm>
#include <iostream>
#include <map>
#include <sstream>
#include <string>
#include <vector>
#define STRING_REMOVE_CHAR(str, ch) str.erase(std::remove(str.begin(), str.end(), ch), str.end())
std::vector<std::string> splitString(std::string str, char sep = ',') {
std::vector<std::string> vecString;
std::string item;
std::stringstream stringStream(str);
while (std::getline(stringStream, item, sep))
{
vecString.push_back(item);
}
return vecString;
}
#define DECLARE_ENUM_WITH_TYPE(E, T, ...) \
enum class E : T \
{ \
__VA_ARGS__ \
}; \
std::map<T, std::string> E##MapName(generateEnumMap<T>(#__VA_ARGS__)); \
std::ostream &operator<<(std::ostream &os, E enumTmp) \
{ \
os << E##MapName[static_cast<T>(enumTmp)]; \
return os; \
} \
size_t operator*(E enumTmp) { (void) enumTmp; return E##MapName.size(); } \
std::string operator~(E enumTmp) { return E##MapName[static_cast<T>(enumTmp)]; } \
std::string operator+(std::string &&str, E enumTmp) { return str + E##MapName[static_cast<T>(enumTmp)]; } \
std::string operator+(E enumTmp, std::string &&str) { return E##MapName[static_cast<T>(enumTmp)] + str; } \
std::string &operator+=(std::string &str, E enumTmp) \
{ \
str += E##MapName[static_cast<T>(enumTmp)]; \
return str; \
} \
E operator++(E &enumTmp) \
{ \
auto iter = E##MapName.find(static_cast<T>(enumTmp)); \
if (iter == E##MapName.end() || std::next(iter) == E##MapName.end()) \
iter = E##MapName.begin(); \
else \
{ \
++iter; \
} \
enumTmp = static_cast<E>(iter->first); \
return enumTmp; \
} \
bool valid##E(T value) { return (E##MapName.find(value) != E##MapName.end()); }
#define DECLARE_ENUM(E, ...) DECLARE_ENUM_WITH_TYPE(E, int32_t, __VA_ARGS__)
template <typename T>
std::map<T, std::string> generateEnumMap(std::string strMap)
{
STRING_REMOVE_CHAR(strMap, ' ');
STRING_REMOVE_CHAR(strMap, '(');
std::vector<std::string> enumTokens(splitString(strMap));
std::map<T, std::string> retMap;
T inxMap;
inxMap = 0;
for (auto iter = enumTokens.begin(); iter != enumTokens.end(); ++iter)
{
// Token: [EnumName | EnumName=EnumValue]
std::string enumName;
T enumValue;
if (iter->find('=') == std::string::npos)
{
enumName = *iter;
}
else
{
std::vector<std::string> enumNameValue(splitString(*iter, '='));
enumName = enumNameValue[0];
//inxMap = static_cast<T>(enumNameValue[1]);
if (std::is_unsigned<T>::value)
{
inxMap = static_cast<T>(std::stoull(enumNameValue[1], 0, 0));
}
else
{
inxMap = static_cast<T>(std::stoll(enumNameValue[1], 0, 0));
}
}
retMap[inxMap++] = enumName;
}
return retMap;
}
例子:
DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
int main(void) {
TestEnumClass first, second;
first = TestEnumClass::FOUR;
second = TestEnumClass::TWO;
std::cout << first << "(" << static_cast<uint32_t>(first) << ")" << std::endl; // FOUR(4)
std::string strOne;
strOne = ~first;
std::cout << strOne << std::endl; // FOUR
std::string strTwo;
strTwo = ("Enum-" + second) + (TestEnumClass::THREE + "-test");
std::cout << strTwo << std::endl; // Enum-TWOTHREE-test
std::string strThree("TestEnumClass: ");
strThree += second;
std::cout << strThree << std::endl; // TestEnumClass: TWO
std::cout << "Enum count=" << *first << std::endl;
}
您可以运行代码here
*
添加了重载以获取枚举数...希望您不介意:-)
std::map
(O(log(n)) 索引)而不是 std::unordered_map
(O(1) 索引)?
inline
,这样您就可以像往常一样在头文件中声明枚举,而不会从链接器中获得“多重定义”错误。 (但不确定这是否真的是最干净/最好的解决方案)
E##MapName
) 需要移动到也可以访问枚举的编译单元。我已经创建了一个解决方案,但它不是很干净,我必须获得共享它的权限。现在,我只是评论说没有必要的附加功能来支持头文件中的使用,将方法标记为内联是没有意义的。
Back in 2011 I spent a weekend fine-tuning a macro-based solution 并最终从未使用过它。
我目前的程序是启动 Vim,将枚举数复制到一个空的 switch 体中,启动一个新宏,将第一个枚举数转换为 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 在 comments 中发布了另一种方法。
.
计数):i.imgur.com/gY4ZhBE.gif
:'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/
之类的东西呢?
我不知道你是否会喜欢这个,我对这个解决方案不太满意,但它是一种 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;
}
这种方法最糟糕的是维护起来很痛苦,但维护其他一些类似的方法也很痛苦,不是吗?
关于这种方法的优点:
使用变量 tempates(C++14 特性)
通过模板专业化,我们可以“检测”何时使用无效值(但我不确定这是否有用)。
它看起来很整洁。
名称查找在编译时完成。
编辑
神秘的user673679你是对的; 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';
这也很麻烦维护,可以改进,但希望你明白。
std::array
而不是笨拙的地图吗?它只会变得更可取的枚举从......什么,2 ^ 10 值开始?也许更多。
enum
有多少元素,那将是惊人的。不幸的是,我们不能。地图的全部意义只是将名称与值相关联,这正是 std::map
的优点。
initialize()
函数,其参数数量是枚举值的数量,因此您知道编译时的值数量。只有在运行时才知道您被要求打印的特定值。此外,即使您不知道该数字,在几乎所有实际情况下,std::vector 也会比 std::map 更快。
std::array
不是键值容器,因此缺少查找方法;反正我会考虑的。
如果您的 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++ 元素,但它可以用于生成大量代码,而无需太多努力和维护。
几天前我遇到了同样的问题。如果没有一些奇怪的宏魔法,我找不到任何 C++ 解决方案,因此我决定编写 a CMake code generator 来生成简单的 switch 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_directories 命令提供的包含目录),读取它们并执行一些正则表达式来生成类和函数。
注意: constexpr 在 C++ 中表示内联,因此使用 USE_CONSTEXPR 选项将生成仅标头类!
例子:
./包括/啊:
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>";
}
}
}
更新:
该脚本现在还支持范围枚举(枚举类|结构),我将它与我经常使用的其他一些脚本一起移至单独的存储库:https://github.com/mensinda/cmakeBuildTools
根据 OP 的要求,这里是基于 Boost Preprosessor 和 Variadic Macros 的丑陋宏解决方案的精简版。
它允许一个简单的列表,如枚举器元素的语法以及特定元素的设置值,以便
XXX_ENUM(foo,(a,b,(c,42)));
扩展到
enum foo {
a,
b,
c=42
};
除了必要的功能来输出和做一些转换回来。这个宏已经存在很长时间了,我不完全确定它是最有效的方式,或者它是一种合规的方式,但它一直在工作
在 Ideone 和 Coliru 中都可以看到完整的代码。
它巨大的丑陋在上面;如果我知道怎么做,我会把它放在剧透后面来保护你的眼睛,但 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
main
时,我真的感到如释重负——家,甜蜜的家!
namespace xxx
的东西可以移到页眉位置吗?您可以在介绍中说您使用 boost/preprocessor.hpp
,因此答案符合现代 C++。请修复警告并清理源代码以获得更好的质量。
main<tab>
,包括我不使用的 args。我不认为这段代码可以真正被清理,它只是为了展示可以做什么而不应该做什么;)如果我在这里改变它,它就不再是我在生产中使用的代码......它是那些脆弱的东西之一一旦它起作用了,你最好永远不要碰它,因为它可能会以没有人预测的方式崩溃。
只需生成您的枚举。为此目的编写一个生成器大约需要五分钟。
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()
variadic template class
和 constexpr
函数)在编译时生成枚举。
enum class Hallo{ First=5, Second=6, Third=7, Fourth=8};
等 C++ 代码中或在几行中检测枚举,我认为大多数 C++ 开发人员会很感激 :-D 您认为您可以调整您的生成器以检测enum
在 C++ 文件中?最好的办法是仅在检测到 /*<Generate enum to string here>*/
之类的标签时生成代码。然后您的生成器就地编写相应的 C++ 生成代码(替换之前生成的代码)。 ^_^ 多么棒的生成器,不是吗?干杯:-)
很长一段时间以来,我也对这个问题感到沮丧,以及以适当的方式将类型转换为字符串的问题。但是,对于最后一个问题,我对 Is it possible to print a variable's type in standard C++? 中解释的解决方案感到惊讶,它使用了 Can I obtain C++ type names in a constexpr way? 中的想法。使用这种技术,可以构造一个类似的函数来获取枚举值作为字符串:
#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 开始并按顺序上升而没有间隙。
这不是一个内在的限制——只是我不使用临时枚举值。如果需要,可以用传统的 switch/case 实现替换向量查找。
该解决方案将一些 c++17 用于内联变量,但如果需要,可以很容易地避免这种情况。由于简单,它还使用 boost:trim。
最重要的是,它只需要 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); \
}
只要您可以为每个可查询枚举编写单独的 .h/.cpp
对,此解决方案就可以使用与常规 c++ 枚举几乎相同的语法和功能:
// 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
解释
这个实现利用了这样一个事实,即枚举定义的元素的花括号列表也可以用作类成员初始化的花括号初始值设定项列表。
在 EnumTraits.inl
的上下文中评估 ETRAITS
时,它会扩展为 EnumTraits<>
类的静态成员定义。
EDECL
宏将每个枚举成员转换为初始化列表值,这些值随后被传递到成员构造函数中以填充枚举信息。
EnumInitGuard
类旨在使用枚举初始值设定项值然后折叠 - 留下一个纯枚举数据列表。
好处
类 c++ 语法
枚举和枚举类的工作方式相同(*几乎)
适用于具有任何数字基础类型的枚举类型
适用于具有自动、显式和分段初始值设定项值的枚举类型
用于大规模重命名(保留智能链接)
只有 5 个预处理器符号(3 个全局)
*
与 enums
相比,enum class
类型中的初始化器引用来自同一枚举的其他值必须具有完全限定的这些值
不利因素
每个可查询的枚举都需要一个单独的 .h/.cpp 对
取决于复杂的宏并包含魔法
轻微的语法错误会爆发成更大的错误
定义类或命名空间范围的枚举很重要
没有编译时初始化
注释
Intellisense 在打开 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
}
非常简单的解决方案,有一个很大的限制:您不能将自定义值分配给 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);
auto name = MyEnumStrings["Red"];
之类的内容 -- (2) 为什么使用 enum class
? -- (3) 你支持enum class MyEnum : char { Red, Green, Blue };
吗? -- (4) 解释函数 split()
-- (5) 你需要参数 const std::regex& delim
吗? -- (6) 在编译时生成 MyEnumStrings
怎么样? =>你能用constexpr
吗? ...干杯:-)
我不确定这种方法是否已经包含在其他答案之一中(实际上是,见下文)。我多次遇到该问题,但没有找到不使用混淆宏或第三方库的解决方案。因此我决定编写自己的混淆宏版本。
我想要启用的是相当于
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
其中大部分是定义和取消定义用户将作为参数通过包含传递给 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 answer很相似。也许我以前读过它,也许它是灵感的主要来源。在我编写自己的宏之前,我总是无法理解 X 宏;)。
我的解决方案,使用预处理器定义。
您可以在 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
铛
https://i.stack.imgur.com/yIqDx.png
视觉 C++
https://i.stack.imgur.com/Qq8Up.png
您可以滥用 user-defined literals 来达到预期的结果:
enum
{
AAA = "AAA"_h8,
BB = "BB"_h8,
};
std::cout << h8::to_string(AAA) << std::endl;
std::cout << h8::to_string(BB) << std::endl;
这会将字符串打包成一个整数,这是可逆的。查看示例 here。
以下解决方案基于给定枚举的 std::array<std::string,N>
。
对于 enum
到 std::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::string
到 enum
的转换,我们必须对数组进行线性搜索并将数组索引转换为 enum
。
在此处尝试使用示例:http://coliru.stacked-crooked.com/a/e4212f93bee65076
编辑:重新设计了我的解决方案,以便可以在类中使用自定义枚举。
constexpr
和 noexept
关键字。干杯:-)
enum class X : Type
格式。请查看我的贡献:coliru.stacked-crooked.com/a/b02db9190d3491a3您如何看待我的更改?你有什么想法支持枚举中设置的值吗?示例 enum E{A=3, B=6, C=A-B};
干杯
在类/结构中使用枚举(结构默认为公共成员)和重载运算符的解决方案:
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;
这将输出“红色 1 2”。您可能会重载 << 以使蓝色输出一个字符串(尽管它可能会导致歧义,因此不可能),但它不适用于 Color::GREEN,因为它不会自动转换为 Color。
隐式转换为 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;
};
case Enum::RED: return "red";
的东西。问题是关于编译器自动化这些东西(在编译时)。这个问题的想法是只更改或添加枚举值,而不必更新东西 toString()
。你有看到?谢谢
此 gist 提供了一个基于 C++ 可变参数模板的简单映射。
这是 gist 中基于类型的映射的 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
编辑:检查下面的更新版本
如上所述,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。
(有人可以指出标准中提到 ^^这种行为^^ 的位置吗?)
枚举器的声明。
缺点
它不是真正的枚举类型,因此不能提升为 int
不能在开关盒中使用
替代解决方案
这个牺牲了行号(不是真的),但可以在 switch case 上使用。
#define ENUM_TYPE(x) using type = Enum<x>
#define ENUM(x) constexpr type x{__LINE__,#x}
template<typename T>
struct Enum final
{
const T value;
const char* name;
constexpr operator const T() const noexcept {return value;}
constexpr const char* operator&() const noexcept {return name;}
};
勘误表
#line 0
在 GCC 和 clang 上与 -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;
}
现实生活中的实现和使用
r3dVoxel - Enum
r3dVoxel - ELoggingLevel
快速参考
#line lineno -- cppreference.com
您可以使用反射库,例如 Ponder:
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"
(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
我写了一个库来解决这个问题,一切都发生在编译时,除了得到消息。
用法:
使用宏 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_CODE
的 enum
;在 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););
}
我喜欢这种设计的另一件事是您可以管理不同文件中的消息定义。
我发现 this question 的解决方案看起来好多了。
enum
值使用/更新/维护一个语句 DEF_MSG
:-/ 理想情况下,这就是我们希望停止做的事情......干杯
DEF_MSG
使 enum
与消息紧密配对,尽管它有一些限制。
enum class
(C++11) 的支持怎么样?您可以在运行时使用 constexpr
来限制 _g_messages
。使用元编程(类型传送 {enum-type, enum-value})或 template variables (C++14) 支持多种 enum
类型(避免 _g_messages
)。我认为您的库(还没有?)符合 C++11/14/17 的要求。你怎么看?干杯;-)
#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 宏生成“枚举类”和具有“枚举反射功能”的辅助类。
为了减少错误,一次性只用一个 ENUM_MAKE 定义了 Everything。
此代码的优点是自动创建反射和仔细查看宏代码,易于理解的代码。 'enum to string' , 'string to enum' 性能都是算法 O(1)。
缺点是第一次使用时,枚举 relection 的字符串向量和映射的辅助类被初始化。但如果你愿意,你也会被预先初始化。 –
constexpr
,编译器可以在编译时计算您的示例。我的意思是函数 toName()
和 toType()
可以在编译期间评估,而不是在执行期间(运行时)。请在您的回答中采用 C++11/14/17 风格。干杯;-)
enum class MyEnum : short { A, B, C };
兼容吗?
我的解决方案是没有宏使用。
优点:
你清楚地看到你在做什么
访问是使用哈希映射,对许多有价值的枚举非常有用
无需考虑顺序或非连续值
枚举到字符串和字符串到枚举的翻译,而添加的枚举值必须添加到一个额外的位置
缺点:
您需要将所有枚举值复制为文本
哈希映射中的访问必须考虑字符串大小写
如果添加值很痛苦,则维护 - 必须同时添加枚举和直接翻译映射
所以......直到 C++ 实现 C# Enum.Parse 功能的那一天,我将坚持这一点:
#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];
好吧,还有另一种选择。一个典型的用例是您需要 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;
}
}
动词类:
// -----------------------------------------------------------
// -----------------------------------------------------------
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
const std::string text
替换为 theStrings[v]
。然而,问题是关于 C++11/C++14/C++17/C++20 的特性,以避免不得不手动编写此类:-/
我的答案就在这里。
您可以同时获取枚举值名称和这些索引作为字符串的双端队列。
这种方法只需要很少的复制和粘贴和编辑。
当您需要枚举类类型值时,获得的结果需要从 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;
}
我的 3 美分,虽然这与操作人员想要的不完全匹配。这是相关的reference。
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;
}
因此,您生成了一个类型,您可以将其转换为整数和/或字符串。
我对与此一起提出的所有花哨的框架(宏、模板和类)不太满意,因为我认为使用它们会使代码更难理解,并且会增加编译时间并隐藏错误。一般来说,我想要一个简单的解决方案来解决这个问题。添加额外的 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);
}
具体来说:
内部数据结构现在是“静态”和“常量”。这些是不变的,所以没有必要在每次调用函数时都构造它们,这样做效率很低。相反,它们仅在第一次调用该函数时构建。返回值现在是 'const std::string&'。此函数将仅返回对已分配的具有“静态”生命周期的 std::string 对象的引用,因此在返回时无需复制它们。对于 O(1) 访问,地图类型现在是 'std::unordered_map',而不是 std::map 的 O(log(N)) 访问。使用 ENTRY 宏可以使代码更加简洁,还可以避免在字符串文字中输入名称时出现的拼写错误的潜在问题。 (如果程序员输入了一个无效的名字,将会导致编译器错误。)
不定期副业成功案例分享
1U
移动了一些量?_trimmed_names()
中似乎存在内存泄漏(new char[length + 1]
但您没有将initialized
设置为 true)。我错过了什么吗?我在你的 github 代码中没有看到同样的问题。true
,但在if
分支之外(最初由 @mrhthepie 捕获的内存泄漏)。应该把它移到里面......编辑。感谢您仔细查看此代码和 GH 代码。to_string
可以从 C++17 返回一个string_view
,它不需要 null 终止,并成为 constexpr。