ChatGPT解决这个技术问题 Extra ChatGPT

在现代 C++11/C++14/C++17 和未来的 C++20 中枚举到字符串

与所有其他类似问题相反,这个问题是关于使用新的 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)

支持类枚举: 具有任何允许的 (C++11)

编译时(不是运行时)转换为字符串,或者至少在运行时快速执行(例如 std::map 不是一个好主意......)

constexpr(C++11,然后在 C++14/17/20 中放宽)

无异常 (C++11)

C++17/C++20 友好代码片段

一种可能的想法是使用 C++ 编译器功能在编译时使用基于 variadic template classconstexpr 函数的元编程技巧生成 C++ 代码......

(也许是主题)看看这个 Qt 相关的博客。 woboq.com/blog/reflection-in-cpp-and-qt-moc.html。描述使用 C++ 反射(提议的标准)替换 Qt 的 moc(元对象编译器)的可能性。
N4113std::enumerator::identifier_v<MyEnum, MyEnum::AAA>
一切都必须用 C++ 解决吗?为字符串表示自动生成代码非常容易,只需几行代码。
“如果可能,请不要提供基于 C 宏的答案”好吧,除非您愿意等待 C++17,否则几乎没有任何可用的东西,而且声明您的枚举也不错作为DEC_ENUM(enumname, (a,b,c,(d,b),(e,42))),除非您必须维护生成宏......并且恕我直言,将这种情况放入语言中只是另一种hack,而不是更强大的模板/宏混合。我们不应该将所有有用的宏用例添加到语言中,只是为了能够说宏不再有用。
@olibre 这个问题今天至少有两个可用的答案。 1. @ecatmur 关于 C++17 的好回答,我们不能每次在 C++17 讨论中有更新时都对其进行编辑。请参阅 mailing list of the reflection study group。 2. 我对当前 C++ 的语法很好的回答,许多人在生产中使用它,但在内部使用 #define。您所要求的是一个可用的解决方案。今天正确的答案 是完全“正确”的解决方案要到以后才可用(即现在接受@ecatmur)。

e
einpoklum

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 answerthe librarythe 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" 在编译期间被转换为枚举。


嘿@mrhthepie,很抱歉您的编辑被拒绝了。我刚刚看到有关它的电子邮件。我将把它合并到答案中——感谢你的错误修复!
这很棒。如果我想要一个位枚举,这也可以吗?就像我想要一个 BitFlags 枚举一样,每一个都1U移动了一些量?
您在此处发布的代码中的 _trimmed_names() 中似乎存在内存泄漏(new char[length + 1] 但您没有将 initialized 设置为 true)。我错过了什么吗?我在你的 github 代码中没有看到同样的问题。
它设置为 true,但在 if 分支之外(最初由 @mrhthepie 捕获的内存泄漏)。应该把它移到里面......编辑。感谢您仔细查看此代码和 GH 代码。
to_string 可以从 C++17 返回一个 string_view,它不需要 null 终止,并成为 constexpr。
N
Neargye

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 枚举数 { 一 = 100, 二 = 200, 三 = 300 };命名空间magic_enum { 模板<> struct enum_range { static constexpr int min = 100;静态 constexpr int max = 300; }; }


为什么有范围限制?是限制某种递归深度,还是因为某种编译时线性搜索?
这真太了不起了。谢谢!如果编译器足够聪明,只评估一次 constexpr std::array ,它可能甚至更有效。非常非常棒。
@EmileCormier 范围限制是必要的,因为库必须探测范围内的每个可能值以查看它是否对应于枚举数。它为 [-128, 127] 范围内的每个值实例化一个 is_valid 函数模板。这可能会导致大量的编译时间,因此默认情况下该范围非常保守。以下是该技术的简化版本:godbolt.org/z/GTxfva
对我来说最重要的缺点是它静默失败:godbolt.org/z/TTMx1v值的大小有限制,但是当不满足约束时,没有编译错误,没有异常,只返回空字符串。
@acegs 如果在最小/最大范围 enum-to-string 之外的值将返回一个空字符串。
o
oHo

对于 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 或下一个标准的统一静态反射提案。


@antron 抱歉,您的编辑被拒绝了;如果我及时看到它,我会批准它。我还没有看到 N4428,所以感谢您的提醒。
没问题,谢谢你的加入。我有点想知道为什么它被拒绝了。我看到了“不会使它更准确”的样板原因,但对于今天来说显然更准确。
这样一个概念上简单的任务需要 3 级嵌套模板参数这一事实是非常过度设计的。我确信它有特定的技术原因。但这并不意味着最终结果是用户友好的。我喜欢 C++,而且代码对我来说很有意义。但我每天与之共事的其他程序员中有 90% 都因为这样的代码而避开 C++。我很失望没有看到任何更简单的内置解决方案。
似乎目前在标准中包含即将推出的反射 TS 的估计是 C++23herbsutter.com/2018/04/02/…
@Sz 这些天我很享受 .NET 生态系统的出色 C# 语言、框架、包管理和工具支持。我懒得再回到 C++ 了!
P
Peter VARGA

这类似于 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) 需要移动到也可以访问枚举的编译单元。我已经创建了一个解决方案,但它不是很干净,我必须获得共享它的权限。现在,我只是评论说没有必要的附加功能来支持头文件中的使用,将方法标记为内联是没有意义的。
S
StackedCrooked

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 中发布了另一种方法。


我正是这样做的。虽然我通常一路使用环绕 vim 和块选择
@sehe 有趣。我应该看看“环绕”,因为我目前需要很多按键。
这里是血淋淋的,没有宏(除非 . 计数):i.imgur.com/gY4ZhBE.gif
动画 gif 很可爱,但很难说它什么时候开始和结束,以及我们在多远。 ......实际上,从头开始,它并不可爱,它会分散注意力。我说杀了。
vim 中的这种块选择方法非常好,但为什么不简单地使用 :'<,'>s/ *\(.*\)=.*/case EtherType::\1: return os << "\1";/ 之类的东西呢?
C
Community

我不知道你是否会喜欢这个,我对这个解决方案不太满意,但它是一种 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 特性)

通过模板专业化,我们可以“检测”何时使用无效值(但我不确定这是否有用)。

它看起来很整洁。

名称查找在编译时完成。

Live example

编辑

神秘的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';

这也很麻烦维护,可以改进,但希望你明白。

Live example


这看起来很整洁(是否可以不定义非专业变量?)。也许我错过了一些东西,尽管我根本看不到它如何处理运行时案例。
@Paula_plus_plus:您不应该只使用 std::array 而不是笨拙的地图吗?它只会变得更可取的枚举从......什么,2 ^ 10 值开始?也许更多。
@einpoklum 如果我们可以在运行时确保 enum 有多少元素,那将是惊人的。不幸的是,我们不能。地图的全部意义只是将名称与值相关联,这正是 std::map 的优点。
@Paula_plus_plus:您已经在调用 initialize() 函数,其参数数量是枚举值的数量,因此您知道编译时的值数量。只有在运行时才知道您被要求打印的特定值。此外,即使您不知道该数字,在几乎所有实际情况下,std::vector 也会比 std::map 更快。
@einpoklum 这确实是一个很好的观点,我会考虑的,谢谢!唯一让我担心的是 std::array 不是键值容器,因此缺少查找方法;反正我会考虑的。
H
HolyBlackCat

如果您的 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++ 元素,但它可以用于生成大量代码,而无需太多努力和维护。


不需要单独的文件。这本质上是一个 x-macro
@HolyBlackCat 如果将解决方案拆分为某些文件,则可以将枚举值用于不同目的
我试图对您说,如果您将值列表与标头中的枚举定义一起放入单个宏中,您可以做同样的事情。
@HolyBlackCat 是的,我理解你,但我更喜欢这个解决方案。另一方面,这个解决方案可以在 clang 源代码中找到,所以我认为这是解决问题的好方法
很公平。我猜不应该对此投反对票,因为它确实可以有一些用途。 (请原谅虚拟编辑,否则系统会锁定我的投票。)
M
Mense

几天前我遇到了同样的问题。如果没有一些奇怪的宏魔法,我找不到任何 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


哇!非常原创和创新的想法 :-) 我希望您有勇气升级您的生成器以提供 constexprnoexcept 版本;-) 我也刚刚盯着 your GitHub project ;-) 干杯
更新了生成器。这些函数现在将始终为 constexpr 和 enum :现在支持 。谢谢你的星星:)
链接坏了... -.-
链接现在已修复。
o
oHo

根据 OP 的要求,这里是基于 Boost PreprosessorVariadic Macros 的丑陋宏解决方案的精简版。

它允许一个简单的列表,如枚举器元素的语法以及特定元素的设置值,以便

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

扩展到

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

除了必要的功能来输出和做一些转换回来。这个宏已经存在很长时间了,我不完全确定它是最有效的方式,或者它是一种合规的方式,但它一直在工作

IdeoneColiru 中都可以看到完整的代码。

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

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

#include <boost/preprocessor.hpp>
#include <string>
#include <unordered_map>

namespace xxx
{

template<class T>
struct enum_cast_adl_helper { };

template<class E>
E enum_cast( const std::string& s )
{
    return do_enum_cast(s,enum_cast_adl_helper<E>());
}

template<class E>
E enum_cast( const char* cs )
{
    std::string s(cs);
    return enum_cast<E>(s);
}

} // namespace xxx

#define XXX_PP_ARG_N(                             \
          _1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
         _11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
         _21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
         _31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
         _41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
         _51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
         _61,_62,_63,N,...) N

#define XXX_PP_RSEQ_N()                 \
         63,62,61,60,                   \
         59,58,57,56,55,54,53,52,51,50, \
         49,48,47,46,45,44,43,42,41,40, \
         39,38,37,36,35,34,33,32,31,30, \
         29,28,27,26,25,24,23,22,21,20, \
         19,18,17,16,15,14,13,12,11,10, \
         9,8,7,6,5,4,3,2,1,0 

#define XXX_PP_NARG_(...) XXX_PP_ARG_N(__VA_ARGS__)
#define XXX_PP_NARG(...)  XXX_PP_NARG_(__VA_ARGS__,XXX_PP_RSEQ_N())
#define XXX_TUPLE_SIZE_INTERNAL(TUPLE) XXX_PP_NARG TUPLE

#define XXX_TUPLE_CHOICE(i)                            \
  BOOST_PP_APPLY(                                      \
    BOOST_PP_TUPLE_ELEM(                               \
      25, i, (                                         \
        (0), (1), (2), (3), (4), (5), (6), (7), (8),   \
        (9), (10), (11), (12), (13), (14), (15), (16), \
        (17), (18), (19), (20), (21), (22), (23), (24) \
  ) ) )

#define BOOST_PP_BOOL_00  BOOST_PP_BOOL_0
#define BOOST_PP_BOOL_01  BOOST_PP_BOOL_1
#define BOOST_PP_BOOL_02  BOOST_PP_BOOL_2
#define BOOST_PP_BOOL_03  BOOST_PP_BOOL_3
#define BOOST_PP_BOOL_04  BOOST_PP_BOOL_4
#define BOOST_PP_BOOL_05  BOOST_PP_BOOL_5
#define BOOST_PP_BOOL_06  BOOST_PP_BOOL_6
#define BOOST_PP_BOOL_07  BOOST_PP_BOOL_7
#define BOOST_PP_BOOL_08  BOOST_PP_BOOL_8
#define BOOST_PP_BOOL_09  BOOST_PP_BOOL_9
#define BOOST_PP_BOOL_010 BOOST_PP_BOOL_10
#define BOOST_PP_BOOL_011 BOOST_PP_BOOL_11
#define BOOST_PP_BOOL_012 BOOST_PP_BOOL_12
#define BOOST_PP_BOOL_013 BOOST_PP_BOOL_13
#define BOOST_PP_BOOL_014 BOOST_PP_BOOL_14
#define BOOST_PP_BOOL_015 BOOST_PP_BOOL_15
#define BOOST_PP_BOOL_016 BOOST_PP_BOOL_16
#define BOOST_PP_BOOL_017 BOOST_PP_BOOL_17
#define BOOST_PP_BOOL_018 BOOST_PP_BOOL_18
#define BOOST_PP_BOOL_019 BOOST_PP_BOOL_19
#define BOOST_PP_BOOL_020 BOOST_PP_BOOL_20
#define BOOST_PP_BOOL_021 BOOST_PP_BOOL_21
#define BOOST_PP_BOOL_022 BOOST_PP_BOOL_22
#define BOOST_PP_BOOL_023 BOOST_PP_BOOL_23
#define BOOST_PP_BOOL_024 BOOST_PP_BOOL_24
#define BOOST_PP_BOOL_025 BOOST_PP_BOOL_25
#define BOOST_PP_BOOL_026 BOOST_PP_BOOL_26
#define BOOST_PP_BOOL_027 BOOST_PP_BOOL_27
#define BOOST_PP_BOOL_028 BOOST_PP_BOOL_28
#define BOOST_PP_BOOL_029 BOOST_PP_BOOL_29
#define BOOST_PP_BOOL_030 BOOST_PP_BOOL_30
#define BOOST_PP_BOOL_031 BOOST_PP_BOOL_31
#define BOOST_PP_BOOL_032 BOOST_PP_BOOL_32
#define BOOST_PP_BOOL_033 BOOST_PP_BOOL_33
#define BOOST_PP_BOOL_034 BOOST_PP_BOOL_34
#define BOOST_PP_BOOL_035 BOOST_PP_BOOL_35
#define BOOST_PP_BOOL_036 BOOST_PP_BOOL_36
#define BOOST_PP_BOOL_037 BOOST_PP_BOOL_37
#define BOOST_PP_BOOL_038 BOOST_PP_BOOL_38
#define BOOST_PP_BOOL_039 BOOST_PP_BOOL_39
#define BOOST_PP_BOOL_040 BOOST_PP_BOOL_40
#define BOOST_PP_BOOL_041 BOOST_PP_BOOL_41
#define BOOST_PP_BOOL_042 BOOST_PP_BOOL_42
#define BOOST_PP_BOOL_043 BOOST_PP_BOOL_43
#define BOOST_PP_BOOL_044 BOOST_PP_BOOL_44
#define BOOST_PP_BOOL_045 BOOST_PP_BOOL_45
#define BOOST_PP_BOOL_046 BOOST_PP_BOOL_46
#define BOOST_PP_BOOL_047 BOOST_PP_BOOL_47
#define BOOST_PP_BOOL_048 BOOST_PP_BOOL_48
#define BOOST_PP_BOOL_049 BOOST_PP_BOOL_49
#define BOOST_PP_BOOL_050 BOOST_PP_BOOL_50
#define BOOST_PP_BOOL_051 BOOST_PP_BOOL_51
#define BOOST_PP_BOOL_052 BOOST_PP_BOOL_52
#define BOOST_PP_BOOL_053 BOOST_PP_BOOL_53
#define BOOST_PP_BOOL_054 BOOST_PP_BOOL_54
#define BOOST_PP_BOOL_055 BOOST_PP_BOOL_55
#define BOOST_PP_BOOL_056 BOOST_PP_BOOL_56
#define BOOST_PP_BOOL_057 BOOST_PP_BOOL_57
#define BOOST_PP_BOOL_058 BOOST_PP_BOOL_58
#define BOOST_PP_BOOL_059 BOOST_PP_BOOL_59
#define BOOST_PP_BOOL_060 BOOST_PP_BOOL_60
#define BOOST_PP_BOOL_061 BOOST_PP_BOOL_61
#define BOOST_PP_BOOL_062 BOOST_PP_BOOL_62
#define BOOST_PP_BOOL_063 BOOST_PP_BOOL_63

#define BOOST_PP_DEC_00  BOOST_PP_DEC_0
#define BOOST_PP_DEC_01  BOOST_PP_DEC_1
#define BOOST_PP_DEC_02  BOOST_PP_DEC_2
#define BOOST_PP_DEC_03  BOOST_PP_DEC_3
#define BOOST_PP_DEC_04  BOOST_PP_DEC_4
#define BOOST_PP_DEC_05  BOOST_PP_DEC_5
#define BOOST_PP_DEC_06  BOOST_PP_DEC_6
#define BOOST_PP_DEC_07  BOOST_PP_DEC_7
#define BOOST_PP_DEC_08  BOOST_PP_DEC_8
#define BOOST_PP_DEC_09  BOOST_PP_DEC_9
#define BOOST_PP_DEC_010 BOOST_PP_DEC_10
#define BOOST_PP_DEC_011 BOOST_PP_DEC_11
#define BOOST_PP_DEC_012 BOOST_PP_DEC_12
#define BOOST_PP_DEC_013 BOOST_PP_DEC_13
#define BOOST_PP_DEC_014 BOOST_PP_DEC_14
#define BOOST_PP_DEC_015 BOOST_PP_DEC_15
#define BOOST_PP_DEC_016 BOOST_PP_DEC_16
#define BOOST_PP_DEC_017 BOOST_PP_DEC_17
#define BOOST_PP_DEC_018 BOOST_PP_DEC_18
#define BOOST_PP_DEC_019 BOOST_PP_DEC_19
#define BOOST_PP_DEC_020 BOOST_PP_DEC_20
#define BOOST_PP_DEC_021 BOOST_PP_DEC_21
#define BOOST_PP_DEC_022 BOOST_PP_DEC_22
#define BOOST_PP_DEC_023 BOOST_PP_DEC_23
#define BOOST_PP_DEC_024 BOOST_PP_DEC_24
#define BOOST_PP_DEC_025 BOOST_PP_DEC_25
#define BOOST_PP_DEC_026 BOOST_PP_DEC_26
#define BOOST_PP_DEC_027 BOOST_PP_DEC_27
#define BOOST_PP_DEC_028 BOOST_PP_DEC_28
#define BOOST_PP_DEC_029 BOOST_PP_DEC_29
#define BOOST_PP_DEC_030 BOOST_PP_DEC_30
#define BOOST_PP_DEC_031 BOOST_PP_DEC_31
#define BOOST_PP_DEC_032 BOOST_PP_DEC_32
#define BOOST_PP_DEC_033 BOOST_PP_DEC_33
#define BOOST_PP_DEC_034 BOOST_PP_DEC_34
#define BOOST_PP_DEC_035 BOOST_PP_DEC_35
#define BOOST_PP_DEC_036 BOOST_PP_DEC_36
#define BOOST_PP_DEC_037 BOOST_PP_DEC_37
#define BOOST_PP_DEC_038 BOOST_PP_DEC_38
#define BOOST_PP_DEC_039 BOOST_PP_DEC_39
#define BOOST_PP_DEC_040 BOOST_PP_DEC_40
#define BOOST_PP_DEC_041 BOOST_PP_DEC_41
#define BOOST_PP_DEC_042 BOOST_PP_DEC_42
#define BOOST_PP_DEC_043 BOOST_PP_DEC_43
#define BOOST_PP_DEC_044 BOOST_PP_DEC_44
#define BOOST_PP_DEC_045 BOOST_PP_DEC_45
#define BOOST_PP_DEC_046 BOOST_PP_DEC_46
#define BOOST_PP_DEC_047 BOOST_PP_DEC_47
#define BOOST_PP_DEC_048 BOOST_PP_DEC_48
#define BOOST_PP_DEC_049 BOOST_PP_DEC_49
#define BOOST_PP_DEC_050 BOOST_PP_DEC_50
#define BOOST_PP_DEC_051 BOOST_PP_DEC_51
#define BOOST_PP_DEC_052 BOOST_PP_DEC_52
#define BOOST_PP_DEC_053 BOOST_PP_DEC_53
#define BOOST_PP_DEC_054 BOOST_PP_DEC_54
#define BOOST_PP_DEC_055 BOOST_PP_DEC_55
#define BOOST_PP_DEC_056 BOOST_PP_DEC_56
#define BOOST_PP_DEC_057 BOOST_PP_DEC_57
#define BOOST_PP_DEC_058 BOOST_PP_DEC_58
#define BOOST_PP_DEC_059 BOOST_PP_DEC_59
#define BOOST_PP_DEC_060 BOOST_PP_DEC_60
#define BOOST_PP_DEC_061 BOOST_PP_DEC_61
#define BOOST_PP_DEC_062 BOOST_PP_DEC_62
#define BOOST_PP_DEC_063 BOOST_PP_DEC_63

#define XXX_TO_NUMx(x) 0 ## x
#define XXX_TO_NUM(x) BOOST_PP_ADD(0,XXX_TO_NUMx(x))
#define XXX_STRINGIZEX(x) # x
#define XXX_VSTRINGIZE_SINGLE(a,b,x) XXX_STRINGIZE(x)
#define XXX_VSTRINGIZE_TUPLE(tpl) XXX_TUPLE_FOR_EACH(XXX_VSTRINGIZE_SINGLE,,tpl)
#define XXX_TUPLE_SIZE(TUPLE) XXX_TO_NUM(XXX_TUPLE_CHOICE(XXX_TUPLE_SIZE_INTERNAL(TUPLE)))
#define XXX_TUPLE_FOR_EACH(MACRO,DATA,TUPLE) BOOST_PP_LIST_FOR_EACH(MACRO,DATA,BOOST_PP_TUPLE_TO_LIST(XXX_TUPLE_SIZE(TUPLE),TUPLE))
#define XXX_STRINGIZE(x) XXX_STRINGIZEX(x)
#define XXX_VSTRINGIZE(...) XXX_VSTRINGIZE_TUPLE((__VA_ARGS__))
#define XXX_CAST_TO_VOID_ELEMENT(r,data,elem) (void)(elem);
#define XXX_CAST_TO_VOID_INTERNAL(TUPLE) XXX_TUPLE_FOR_EACH(XXX_CAST_TO_VOID_ELEMENT,,TUPLE)    
#define XXX_CAST_TO_VOID(...) XXX_CAST_TO_VOID_INTERNAL((__VA_ARGS__))
#define XXX_ENUM_EXTRACT_SP(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en) = BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),1,en)
#define XXX_ENUM_ELEMENT(r,data,elem) BOOST_PP_IF( XXX_TUPLE_SIZE(elem), XXX_ENUM_EXTRACT_SP(elem), elem) ,
#define XXX_ENUM_EXTRACT_ELEMENT(en) BOOST_PP_TUPLE_ELEM(XXX_TUPLE_SIZE(en),0,en)
#define XXX_ENUM_CASE_ELEMENT(en) BOOST_PP_IF( XXX_TUPLE_SIZE(en), XXX_ENUM_EXTRACT_ELEMENT(en), en )
#define XXX_ENUM_CASE(r,data,elem) case data :: XXX_ENUM_CASE_ELEMENT(elem) : return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem));
#define XXX_ENUM_IFELSE(r,data,elem) else if( en == data :: XXX_ENUM_CASE_ELEMENT(elem)) { return #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)); }
#define XXX_ENUM_CASTLIST(r,data,elem) { XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },
#define XXX_ENUM_QUALIFIED_CASTLIST(r,data,elem) { #data "::" XXX_STRINGIZE(XXX_ENUM_CASE_ELEMENT(elem)), data :: XXX_ENUM_CASE_ELEMENT(elem) },

#define XXX_ENUM_INTERNAL(TYPE,NAME,TUPLE)                       \
enum TYPE                                                        \
{                                                                \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_ELEMENT,,TUPLE)                   \
   BOOST_PP_CAT(last_enum_,NAME)                                 \
};                                                               \
                                                                 \
inline                                                           \
const char* to_string( NAME en )                                 \
{                                                                \
   if(false)                                                     \
   {                                                             \
   }                                                             \
   XXX_TUPLE_FOR_EACH(XXX_ENUM_IFELSE,NAME,TUPLE)                \
   else if( en == NAME :: BOOST_PP_CAT(last_enum_,NAME) )        \
   {                                                             \
     return XXX_VSTRINGIZE(NAME,::,BOOST_PP_CAT(last_enum_,NAME));  \
   }                                                             \
   else                                                          \
   {                                                             \
     return "Invalid enum value specified for " # NAME;          \
   }                                                             \
}                                                                \
                                                                 \
inline                                                           \
std::ostream& operator<<( std::ostream& os, const NAME& en )     \
{                                                                \
   os << to_string(en);                                          \
   return os;                                                    \
}                                                                \
                                                                 \
inline                                                           \
NAME do_enum_cast( const std::string& s, const ::xxx::enum_cast_adl_helper<NAME>& ) \
{                                                                \
  static const std::unordered_map<std::string,NAME> map =        \
  {                                                              \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_CASTLIST,NAME,TUPLE)             \
    XXX_TUPLE_FOR_EACH(XXX_ENUM_QUALIFIED_CASTLIST,NAME,TUPLE)   \
  };                                                             \
                                                                 \
  auto cit = map.find(s);                                        \
  if( cit == map.end() )                                         \
  {                                                              \
    throw std::runtime_error("Invalid value to cast to enum");   \
  }                                                              \
  return cit->second;                                            \
}

#define XXX_ENUM(NAME,TUPLE) XXX_ENUM_INTERNAL(NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS(NAME,TUPLE) XXX_ENUM_INTERNAL(class NAME,NAME,TUPLE)
#define XXX_ENUM_CLASS_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(class NAME : TYPE,NAME,TUPLE)
#define XXX_ENUM_TYPE(NAME,TYPE,TUPLE) XXX_ENUM_INTERNAL(NAME : TYPE,NAME,TUPLE)

用法

#include "xxx_enum.h"  // the above lib
#include <iostream>

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

int main()
{
  std::cout << "foo::a = "            << foo::a            <<'\n';
  std::cout << "(int)foo::c = "       << (int)foo::c       <<'\n';
  std::cout << "to_string(foo::b) = " << to_string(foo::b) <<'\n';
  std::cout << "xxx::enum_cast<foo>(\"b\") = " << xxx::enum_cast<foo>("b") <<'\n';
}

编译(在 main.cpp 中复制粘贴标头)

> g++ --version | sed 1q
g++ (GCC) 4.9.2

> g++ -std=c++14 -pedantic -Wall -Wextra main.cpp
main.cpp:268:31: warning: extra ';' [-Wpedantic]
     XXX_ENUM(foo,(a,b,(c,42)));
                               ^

输出

foo::a = foo::a
(int)foo::c = 42
to_string(foo::b) = foo::b
xxx::enum_cast<foo>("b") = foo::b

这个代码块是一段穿越元编程黑魔法神奇景观的疯狂旅程。到达main时,我真的感到如释重负——家,甜蜜的家!
刚刚添加了一个指向 coliru 的链接来检查输出(有一些警告,请单击答案中的链接)。我也分成了 Lib/Usage。 namespace xxx 的东西可以移到页眉位置吗?您可以在介绍中说您使用 boost/preprocessor.hpp,因此答案符合现代 C++。请修复警告并清理源代码以获得更好的质量。
@olibre:我认为我们图书馆中有 5 个不同的标题是 copypastad。 enum_cast 来自另一个更通用的部分,但我想也添加它以查看宏中的 do_enum_cast 的用途。警告仅来自我的 vim main<tab>,包括我不使用的 args。我不认为这段代码可以真正被清理,它只是为了展示可以做什么而不应该做什么;)如果我在这里改变它,它就不再是我在生产中使用的代码......它是那些脆弱的东西之一一旦它起作用了,你最好永远不要碰它,因为它可能会以没有人预测的方式崩溃。
好吧 Plasma,我认为这可以看作是一个概念证明。但是有太多的宏观开销需要投票。不过还是谢谢分享。干杯
嗨等离子。我已经进行了深度的源代码清理+通过编译和运行输出完成。请检查my edit。我希望这对你来说没问题。答案更有价值吗?但是,宏开销仍然很可怕!祝你有美好的一天:-) 干杯
y
yeoman

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

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

非常感谢您以两种语言分享您的生成器 :-) 但是您知道如何在编译时生成吗?例如,我们是否可以想象使用 CMake 语句翻译您的生成器,以便在输入数据更改时刷新 C++ 生成的代码?我的梦想是强制 C++ 编译器使用元编程(variadic template classconstexpr 函数)在编译时生成枚举。
Otoh,如果添加自定义 cmake 命令太麻烦,您可以自动化您的 IDE 或手动调用 gererator 并将输出保存在源代码管理中。无论如何,在源代码控制中生成代码有时是个好主意,只要它不是太多,人们明白他们不应该进行手动更改,因为有时查看生成文件的历史记录会很有趣。 '正在调试一些奇怪的东西,并且怀疑最近对生成器的更改可能破坏了某些东西:)
关于在编译时生成东西,这在 LISP 中非常容易,因为语法非常干净和简单。这得益于它是动态类型的,这使得它在没有太多语法的情况下简洁易读。 C++ 中的 LISP 宏的等价物需要一种非常复杂的方式来描述您尝试生成的 AST。而且 C++ 的 AST 从来都不是很漂亮:(
直接在Make而不是cmake中,顺便说一句超级容易。只需通过 find 为每个 .enum 文件生成 .h 和 .cpp 目标,并让这些目标依赖于所述 enum defs,因此一旦 .enum def 文件更改,它们就会自动重新生成。在 cmake 中可能要容易得多,因为它对这类事情充满了魔力,但我经常使用 Make、ant 和 gradle,但对 Maven、cmake 和 grunt 的了解有限:)
感谢您的回答 :-) 如果您的生成器可以直接在 enum class Hallo{ First=5, Second=6, Third=7, Fourth=8}; 等 C++ 代码中或在几行中检测枚举,我认为大多数 C++ 开发人员会很感激 :-D 您认为您可以调整您的生成器以检测enum 在 C++ 文件中?最好的办法是仅在检测到 /*<Generate enum to string here>*/ 之类的标签时生成代码。然后您的生成器就地编写相应的 C++ 生成代码(替换之前生成的代码)。 ^_^ 多么棒的生成器,不是吗?干杯:-)
I
Ignace

很长一段时间以来,我也对这个问题感到沮丧,以及以适当的方式将类型转换为字符串的问题。但是,对于最后一个问题,我对 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 上进行了测试,但应该可以通过调整整数常量来适应其他编译器。当然,它仍然在底层使用宏,但至少不需要访问枚举实现。


这在 g++ 6.3.0 和 C++14 中失败。
有趣的是,枚举可以正常声明,而无需将其包装在宏中。虽然我不喜欢编译器依赖项和魔术常量。
这不适用于在运行时提供的任意枚举值(例如在变量中)。
Y
Yuri Finkelstein

我从@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); \
    } 

J
Jason Lim

只要您可以为每个可查询枚举编写单独的 .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
}

o
oHo

非常简单的解决方案,有一个很大的限制:您不能将自定义值分配给 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);

感谢 Malem 的创新想法。我已编辑您的答案以提高可读性。我希望你喜欢我的改变。请继续改进您的答案:(1) 将“使用示例” 部分扩展为 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吗? ...干杯:-)
我真的很喜欢这种方法。真的很短很容易理解。
4
463035818_is_not_a_number

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

我想要启用的是相当于

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"

Live Demo

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

写完这篇我才意识到它和eferions answer很相似。也许我以前读过它,也许它是灵感的主要来源。在我编写自己的宏之前,我总是无法理解 X 宏;)。


J
Joma

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

您可以在 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


u
user1095108

您可以滥用 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


遗憾的是仅适用于长度 <= 8 的字符串
我们很快就会有 16 个字符。
here 是 h8 的当前版本。
F
FKaria

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

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

#include <boost/preprocessor/seq/transform.hpp>
#include <boost/preprocessor/seq/enum.hpp>
#include <boost/preprocessor/stringize.hpp>

#include <string>
#include <array>
#include <iostream>

#define STRINGIZE(s, data, elem) BOOST_PP_STRINGIZE(elem)

// ENUM
// ============================================================================
#define ENUM(X, SEQ) \
struct X {   \
    enum Enum {BOOST_PP_SEQ_ENUM(SEQ)}; \
    static const std::array<std::string,BOOST_PP_SEQ_SIZE(SEQ)> array_of_strings() { \
        return {{BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(STRINGIZE, 0, SEQ))}}; \
    } \
    static std::string to_string(Enum e) { \
        auto a = array_of_strings(); \
        return a[static_cast<size_t>(e)]; \
    } \
}

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

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

编辑:重新设计了我的解决方案,以便可以在类中使用自定义枚举。


谢谢你有趣的回答。请修改您的提案,以便在课程中使用您的宏。请参阅 coliru.stacked-crooked.com/a/00d362eba836d04b 此外,尽可能尝试使用 constexprnoexept 关键字。干杯:-)
这个问题没有具体说明这个必要条件。
问题已更新(参见示例)。另外两个要求:(1) 支持枚举类型和 (2) 值可以不同于序列 0、1、2...
我重新设计了我的解决方案,它可以在课堂上使用。我还没有弄清楚如何使值不同于 0,1,2,.. 不过。
嗨,FKaria。非常感谢您的返工。我做了一些更改以支持同一类中的多个枚举,并支持 enum class X : Type 格式。请查看我的贡献:coliru.stacked-crooked.com/a/b02db9190d3491a3您如何看待我的更改?你有什么想法支持枚举中设置的值吗?示例 enum E{A=3, B=6, C=A-B}; 干杯
u
user

在类/结构中使用枚举(结构默认为公共成员)和重载运算符的解决方案:

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()。你有看到?谢谢
o
oHo

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

非常有趣的元编程方式。我试图将答案简化为自治(不依赖于 Gist 链接)。为了简洁易懂,我终于编辑了很多你的答案。你还同意我的改变吗?干杯;-)
b
bit2shift

编辑:检查下面的更新版本

如上所述,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


N
Nick

您可以使用反射库,例如 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"

T
TarmoPikaro

https://stackoverflow.com/a/54967187/2338477 的类似物,稍作修改)。

这是我自己的解决方案,具有最小的定义魔法和对单个枚举分配的支持。

这是头文件:

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

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

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

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

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

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

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

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

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

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


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




/*
    Basic usage:

    Declare enumeration:

DECLARE_ENUM( enumName,

    enumValue1,
    enumValue2,
    enumValue3 = 5,

    // comment
    enumValue4
);

    Conversion logic:

    From enumeration to string:

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

    From string to enumeration:

       enumName value;

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

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

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

    return it->second;
}

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

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

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

这是示例测试应用程序:

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

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

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

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

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

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

相同头文件的更新版本将保留在这里:

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


C
Community

我写了一个库来解决这个问题,一切都发生在编译时,除了得到消息。

用法:

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

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

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

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

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

使用 MSG_NUM 查看已定义的宏数量。这会自动增加,你不需要做任何事情。

预定义消息:

MSG_OK:     OK
MSG_BOTTOM: Message bottom

项目:libcodemsg

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

在这种情况下,库仅限于创建一个 enum。这是返回值的理想选择,例如:

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

MSG_CODE ret = foo();

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

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

我发现 this question 的解决方案看起来好多了。


嗨,马德温。谢谢你的想法。但它是如何工作的?什么是开销? (零开销还是会产生额外的数据?)。您的提议似乎很好,但不幸的是,必须为每个 enum 值使用/更新/维护一个语句 DEF_MSG:-/ 理想情况下,这就是我们希望停止做的事情......干杯
谢谢你的回复,@olibre。请检查更新的答案。我在这里看不到开销,除了访问字符串需要函数调用。 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 的要求。你怎么看?干杯;-)
感谢您的跟进。我今天学了些新东西!枚举类和模板变量看起来不错。我认为我的回答有点“离题”,因为它是 C 风味的。
d
desperado_98
#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 的字符串向量和映射的辅助类被初始化。但如果你愿意,你也会被预先初始化。 –


虽然这段代码可能会回答这个问题,但最好在不介绍其他代码的情况下解释它是如何解决问题的,以及为什么要使用它。从长远来看,纯代码答案没有用处。
嘿伙计们,对不起,我的英语说得不太好。
自动 ENUM_MAKE 宏生成“枚举类”和具有“枚举反射功能”的辅助类。 / 为了减少错误,一次性只用一个 ENUM_MAKE 定义了 Everything。此代码的优点是自动创建反射和仔细查看宏代码,易于理解的代码。 'enum to string' , 'string to enum' 性能都是算法 O(1)。缺点是第一次使用时,枚举 relection 的字符串向量和映射的辅助类被初始化。但如果你愿意,你也会被预先初始化。
嗨desperado_98。感谢您的贡献。请编辑您的答案并在其中插入您的评论内容。如果您使用一些元编程技巧和constexpr,编译器可以在编译时计算您的示例。我的意思是函数 toName()toType() 可以在编译期间评估,而不是在执行期间(运行时)。请在您的回答中采用 C++11/14/17 风格。干杯;-)
此外:您的宏与 enum class MyEnum : short { A, B, C }; 兼容吗?
M
Mia Shani

我的解决方案是没有宏使用。

优点:

你清楚地看到你在做什么

访问是使用哈希映射,对许多有价值的枚举非常有用

无需考虑顺序或非连续值

枚举到字符串和字符串到枚举的翻译,而添加的枚举值必须添加到一个额外的位置

缺点:

您需要将所有枚举值复制为文本

哈希映射中的访问必须考虑字符串大小写

如果添加值很痛苦,则维护 - 必须同时添加枚举和直接翻译映射

所以......直到 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];

c
cibercitizen1

好吧,还有另一种选择。一个典型的用例是您需要 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 的特性,以避免不得不手动编写此类:-/
t
tensor5375

我的答案就在这里。

您可以同时获取枚举值名称和这些索引作为字符串的双端队列。

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

当您需要枚举类类型值时,获得的结果需要从 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;
}

u
user1095108

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

因此,您生成了一个类型,您可以将其转换为整数和/或字符串。


S
Some Guy

我对与此一起提出的所有花哨的框架(宏、模板和类)不太满意,因为我认为使用它们会使代码更难理解,并且会增加编译时间并隐藏错误。一般来说,我想要一个简单的解决方案来解决这个问题。添加额外的 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 宏可以使代码更加简洁,还可以避免在字符串文字中输入名称时出现的拼写错误的潜在问题。 (如果程序员输入了一个无效的名字,将会导致编译器错误。)