在现代C ++ 11 / C ++ 14 / C ++ 17和将来的C ++ 20中枚举字符串
与所有其他类似的问题相反,这个问题是关于使用新的C ++特性的。
在阅读了很多答案之后,我还没有找到任何答案:
例
一个例子往往比长时间的解释要好。
你可以在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';
}
约束
#define
开销。 enum
- > string
映射。 很高兴有
enum
值 enum
值 enum
值 class enum
(C ++ 11) class enum : <type>
具有任何允许的<type>
(C ++ 11) 或者至少在运行时快速执行(例如
std::map
不是一个好主意......) constexpr
(C ++ 11,在C ++ 14中放松) noexcept
(C ++ 11) 一个可能的想法是使用C ++编译器功能在编译时使用基于variadic template class
和constexpr
函数的元编程技巧来生成C ++代码。
这与Yuri Finkelstein相似; 但不需要提升。 我正在使用地图,因此您可以将任何值分配给枚举,任何顺序。
枚举类声明如下:
DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
以下代码将自动创建枚举类和重载:
不需要增强功能,提供所有必需的功能。
码:
#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;
}
你可以在这里运行代码
(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 ++。 宏的定义有些涉及,所以我用几种方式回答了这个问题。
将这个答案扩展到图书馆的功能很简单 - 没有什么“重要的”在这里被遗漏。 然而,它非常乏味,并且存在编译器可移植性问题。
免责声明 :我是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) { /* 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"}
作为名称数组的初始值设定项。 我们需要修剪" = 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[] = " =trn";
// The size of terminators includes the implicit ' '.
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] == ' ' :
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] = ' ';
}
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"
已转换为枚举。
对于C ++ 17 C ++ 20,您将对Reflection Study Group(SG7)的工作感兴趣。 关于措辞 (P0194)和基本原理,设计和演变 (P0385)有一系列的论文。 (链接解析为每个系列中的最新文章。)
从P0194r2(2016-10-15)开始,语法将使用建议的reflexpr
关键字:
meta::get_base_name_v<
meta::get_element_m<
meta::get_enumerators_m<reflexpr(MyEnum)>,
0>
>
例如(改编自Matus Choclik的clang的reflexpr分支):
#include <reflexpr>
#include <iostream>
enum MyEnum { AAA = 1, BBB, CCC = 99 };
int main()
{
auto name_of_MyEnum_0 =
std::meta::get_base_name_v<
std::meta::get_element_m<
std::meta::get_enumerators_m<reflexpr(MyEnum)>,
0>
>;
// prints "AAA"
std::cout << name_of_MyEnum_0 << std::endl;
}
静态反射未能成为C ++ 17(相当于在Issaquah举行的2016年11月标准会议上提出的可能最终草案),但有信心它会将其变成C ++ 20; 来自Herb Sutter的旅行报告:
尤其是反思研究小组审查了最新的合并静态反思提案,并发现它准备在下次会议上进入主要演进小组,开始考虑针对TS或下一个标准的统一静态反思提案。
链接地址: http://www.djcxy.com/p/90147.html上一篇: enum to string in modern C++11 / C++14 / C++17 and future C++20