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

oli*_*bre 309 c++ string enums c++17 c++20

与所有其他类似问题相反,这个问题是关于使用新的C++功能.

看了很多答案后,我还没找到:

一个例子通常比长期解释更好.
您可以在Coliru上编译并运行此代码段.
(另一个前例子也可用)

#include <map>
#include <iostream>

struct MyClass
{
    enum class MyEnum : char {
        AAA = -8,
        BBB = '8',
        CCC = AAA + BBB
    };
};

// Replace magic() by some faster compile-time generated code
// (you're allowed to replace the return type with std::string
// if that's easier for you)
const char* magic (MyClass::MyEnum e)
{
    const std::map<MyClass::MyEnum,const char*> MyEnumStrings {
        { MyClass::MyEnum::AAA, "MyClass::MyEnum::AAA" },
        { MyClass::MyEnum::BBB, "MyClass::MyEnum::BBB" },
        { MyClass::MyEnum::CCC, "MyClass::MyEnum::CCC" }
    };
    auto   it  = MyEnumStrings.find(e);
    return it == MyEnumStrings.end() ? "Out of range" : it->second;
}

int main()
{
   std::cout << magic(MyClass::MyEnum::AAA) <<'\n';
   std::cout << magic(MyClass::MyEnum::BBB) <<'\n';
   std::cout << magic(MyClass::MyEnum::CCC) <<'\n';
}
Run Code Online (Sandbox Code Playgroud)

约束

  • 请不要重复其他答案基本链接.
  • 请避免膨胀的基于宏的答案,或尝试尽可能减少#define开销.
  • 请不要手动enum- > string映射.

很高兴有

  • 支持enum从不同于零的数字开始的值
  • 支持负的enum
  • 支持零碎的enum价值观
  • 支持class enum(C++ 11)
  • 支持class enum : <type>任何允许<type>(C++ 11)
  • 编译时(不是运行时)转换为字符串,
    或至少在运行时快速执行(例如,std::map这不是一个好主意......)
  • constexpr (C++ 11,在C++中放松14)
  • noexcept (C++ 11)
  • 片段C++ 14/C++ 17友好
  • C++ 最先进的技术

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

ant*_*ron 84

(better_enums库的方法)

有一种方法可以在当前的C++中对字符串进行枚举,如下所示:

ENUM(Channel, char, Red = 1, Green, Blue)

// "Same as":
// enum class Channel : char { Red = 1, Green, Blue };
Run Code Online (Sandbox Code Playgroud)

用法:

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...
Run Code Online (Sandbox Code Playgroud)

可以进行所有操作constexpr.您还可以实现@ecatmur在答案中提到的C++ 17反射建议.

  • 只有一个宏.我相信这是最小的可能,因为预处理器stringization(#)是在当前C++中将令牌转换为字符串的唯一方法.
  • 该宏非常不引人注目 - 常量声明(包括初始值设定项)被粘贴到内置枚举声明中.这意味着它们具有与内置枚举中相同的语法和含义.
  • 重复被淘汰.
  • 至少在C++ 11中,实现是最自然和最有用的constexpr.它也可以用于C++ 98 + __VA_ARGS__.它绝对是现代C++.

宏的定义有些牵连,所以我在几个方面回答这个问题.

  • 这个答案的大部分是我认为适合StackOverflow空间约束的实现.
  • 还有一篇CodeProject文章描述了长篇教程中实现的基础知识.[ 我应该把它搬到这里吗?我觉得这个问题太过分了.
  • 有一个功能齐全的库"Better Enums",它在单个头文件中实现宏.它还实现了N4428类型属性查询,这是C++ 17反映提案N4113的当前版本.因此,至少对于通过此宏声明的枚举,您现在可以在C++ 11/C++ 14中使用建议的C++ 17枚举反射.

将这个答案扩展到库的功能是很简单的 - 这里没有遗漏任何"重要".然而,它非常繁琐,并且存在编译器可移植性问题.

免责声明:我是CodeProject文章和图书馆的作者.

您可以在Wandbox中尝试使用此答案,以及N4428在线实时生成的代码.图书馆文档还概述了如何将其用作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 */ }
};
Run Code Online (Sandbox Code Playgroud)

问题是:

  1. 我们最终会得到像{Red = 1, Green, Blue}values数组的初始化器一样的东西.这不是有效的C++,因为Red它不是可赋值的表达式.这可以通过将每个常量转换为T具有赋值运算符的类型来解决,但会删除赋值:{(T)Red = 1, (T)Green, (T)Blue}.
  2. 同样,我们最终将{"Red = 1", "Green", "Blue"}作为names数组的初始化器.我们需要修剪掉" = 1".我不知道在编译时这么做的好方法,所以我们将其推迟到运行时间.因此,_to_string不会constexpr,但_from_string仍然可以constexpr,因为我们可以在与未修剪的字符串进行比较时将空格和等号作为终止符.
  3. 以上两者都需要一个"映射"宏,它可以将另一个宏应用于每个元素__VA_ARGS__.这是非常标准的.这个答案包括一个最多可以处理8个元素的简单版本.
  4. 如果宏要真正自包含,则需要声明不需要单独定义的静态数据.在实践中,这意味着阵列需要特殊处理.有两种可能的解决方案:(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;                                             \
    }                                                                 \
};
Run Code Online (Sandbox Code Playgroud)

// 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), "");
Run Code Online (Sandbox Code Playgroud)

Red正如您所料,上面的程序打印出来.存在一定程度的类型安全性,因为您无法在不初始化的情况下创建枚举,并且从中删除其中一个案例switch将导致编译器发出警告(取决于您的编译器和标志).另请注意,"Red"在编译期间已转换为枚举.

  • 您在此处发布的代码中的“_trimmed_names()”似乎存在内存泄漏(“new char[length + 1]”,但您没有将“initialized”设置为 true)。我错过了什么吗?我在你的github代码中没有看到同样的问题。 (2认同)
  • `to_string` 可以从 C++17 返回一个 `string_view`,它不需要 null 终止,并成为 constexpr。 (2认同)

eca*_*mur 71

对于C++ 17 C++ 20,您将对Reflection Study Group(SG7)的工作感兴趣.有一系列平行的论文涵盖措辞(P0194)和理由,设计和演变(P0385).(链接解析每个系列中的最新论文.)

从P0194r2(2016-10-15)开始,语法将使用建议的reflexpr关键字:

meta::get_base_name_v<
  meta::get_element_m<
    meta::get_enumerators_m<reflexpr(MyEnum)>,
    0>
  >
Run Code Online (Sandbox Code Playgroud)

例如(改编自Matus Choclik的clalex的reflexpr分支):

#include <reflexpr>
#include <iostream>

enum MyEnum { AAA = 1, BBB, CCC = 99 };

int main()
{
  auto name_of_MyEnum_0 = 
    std::meta::get_base_name_v<
      std::meta::get_element_m<
        std::meta::get_enumerators_m<reflexpr(MyEnum)>,
        0>
    >;

  // prints "AAA"
  std::cout << name_of_MyEnum_0 << std::endl;
}
Run Code Online (Sandbox Code Playgroud)

静态反思未能进入C++ 17(而是在2016年11月在Issaquah举行的标准会议上提交的可能最终草案中),但有信心它将进入C++ 20; 来自Herb Sutter的旅行报告:

特别是,反思研究小组审查了最新的合并静态反思提案,并发现它准备在下次会议上进入主要的进化组,开始考虑TS或下一个标准的统一静态反思提案.

  • 这样一个概念上简单的任务需要 3 个级别的嵌套模板参数,这一事实非常过度设计。我确信这有特定的技术原因。但这并不意味着最终结果是用户友好的。我喜欢 C++,代码对我来说很有意义。但是我每天共事的其他程序员中有 90% 都因为这样的代码而避开 C++。我对没有看到任何更简单的内置解决方案感到失望。 (7认同)
  • @antron抱歉你的编辑被拒绝了; 如果我及时看到它,我会批准它.我没有见过N4428,所以谢谢你的提醒. (2认同)
  • 没问题,谢谢合并吧.我有点想知道它被拒绝的原因.我看到"不会让它更准确"的样板原因,但它显然更加准确. (2认同)
  • @einpoklum 它不需要虚拟机。编译器可以在编译时构建字符串映射。这些字符串与枚举数的名称相同。为什么要多想这个? (2认同)
  • 似乎即将在标准中包含反射TS的当前估计是** C ++ 23 **:https://herbsutter.com/2018/04/02/trip-report-winter-iso-c-standards -会议杰克逊维尔/ (2认同)
  • @Sz 这些天我很享受 .NET 生态系统出色的 C# 语言、框架、包管理和工具支持。我懒得再回到 C++ 了! (2认同)

小智 24

这与Yuri Finkelstein相似; 但不需要提升.我正在使用地图,因此您可以为枚举,任何订单分配任何值.

枚举类声明为:

DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
Run Code Online (Sandbox Code Playgroud)

以下代码将自动创建枚举类和重载:

  • 对于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;
}
Run Code Online (Sandbox Code Playgroud)

例:

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;
}
Run Code Online (Sandbox Code Playgroud)

You can run the code here

  • 我们可以在这个宏定义中换行吗? (2认同)
  • 这个实现有什么理由使用`std::map`(O(log(n))索引)而不是`std::unordered_map`(O(1)索引)? (2认同)

Nea*_*gye 19

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
};
Run Code Online (Sandbox Code Playgroud)

有关更多示例,请查看主存储库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 = -128MAGIC_ENUM_RANGE_MAX = 128

  • 如果默认情况下所有枚举类型都需要另一个范围,请重新定义宏MAGIC_ENUM_RANGE_MINMAGIC_ENUM_RANGE_MAX

  • 如果需要用于特定枚举类型的其他范围,请为必要的枚举类型添加特殊化enum_range。

    #include <magic_enum.hpp>
    
    enum number { one = 100, two = 200, three = 300 };
    
    namespace magic_enum {
    template <>
      struct enum_range<number> {
        static constexpr int min = 100;
        static constexpr int max = 300;
    };
    }
    
    Run Code Online (Sandbox Code Playgroud)

  • 为什么要限制范围?是为了限制某种递归深度,还是因为某种编译时线性搜索? (8认同)
  • @EmileCormier 范围限制是必要的,因为库必须探测范围内的每个可能值以查看它是否对应于枚举器。它为 [-128, 127] 范围内的每个值实例化一个“is_valid”函数模板。这可能会导致编译时间过长,因此默认情况下该范围相当保守。这是该技术的简化版本:https://godbolt.org/z/GTxfva (7认同)
  • 对我来说,最重要的缺点是它默默地失败:https://godbolt.org/z/TTMx1v 对值的大小有限制,但是当不满足约束时,没有编译错误,没有异常,只有空返回字符串。 (3认同)
  • @acegs 如果枚举到字符串的最小/最大范围之外的值将返回空字符串。 (2认同)

Sta*_*ked 17

早在2011年,我花了一个周末微调基于宏的解决方案,最终从未使用它.

我当前的过程是启动Vim,在空的开关体中复制枚举器,启动一个新的宏,将第一个枚举器转换为case语句,将光标移动到下一行的开头,停止宏并生成剩余的情况通过在其他枚举器上运行宏来声明语句.

Vim宏比C++宏更有趣.

现实生活中的例子:

enum class EtherType : uint16_t
{
    ARP   = 0x0806,
    IPv4  = 0x0800,
    VLAN  = 0x8100,
    IPv6  = 0x86DD
};
Run Code Online (Sandbox Code Playgroud)

我会创建这个:

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);
}
Run Code Online (Sandbox Code Playgroud)

这就是我的方式.

然而,对枚举字符串化的原生支持会好得多.我很想看到C++ 17中反射工作组的结果.

@sehe在评论中发布了另一种方法.


Pap*_*ter 12

我不知道你是否会喜欢这个,我对这个解决方案并不满意,但它是一个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;
}
Run Code Online (Sandbox Code Playgroud)

这种方法最糟糕的是维持痛苦,但维持其他类似的方法也是一种痛苦,不是吗?

关于这个方法的好处:

  • 使用变量tempates(C++ 14功能)
  • 使用模板特化时,我们可以"检测"何时使用无效值(但我不确定这是否有用).
  • 它看起来很整洁.
  • 名称查找在编译时完成.

Live example

编辑

Misterious 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{};
Run Code Online (Sandbox Code Playgroud)

然后,可变参数模板技巧:

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 ...);
}
Run Code Online (Sandbox Code Playgroud)

这里的" 最佳技巧 "是为地图使用变量模板,其中包含每个枚举条目的值和名称; 这个地图在每个翻译单元中都是相同的,并且在任何地方都有相同的名称,所以如果我们调用这样的initialize函数是非常简单和整洁的:

initialize
(
    MyEnum::AAA, "AAA",
    MyEnum::BBB, "BBB",
    MyEnum::CCC, "CCC"
);
Run Code Online (Sandbox Code Playgroud)

我们将名称命名为每个MyEnum条目,并且可以在运行时使用:

std::cout << enum_values<MyEnum>[MyEnum::AAA] << '\n';
Run Code Online (Sandbox Code Playgroud)

但是可以通过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);
}
Run Code Online (Sandbox Code Playgroud)

现在正确,operator <<我们可以这样使用枚举:

std::cout << MyEnum::AAA << '\n';
Run Code Online (Sandbox Code Playgroud)

这也很难维护并且可以改进,但希望你能得到这个想法.

Live example


efe*_*ion 7

如果你enum看起来像

enum MyEnum
{
  AAA = -8,
  BBB = '8',
  CCC = AAA + BBB
};
Run Code Online (Sandbox Code Playgroud)

您可以将内容移动enum到新文件:

AAA = -8,
BBB = '8',
CCC = AAA + BBB
Run Code Online (Sandbox Code Playgroud)

然后值可以被宏包围:

// 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
Run Code Online (Sandbox Code Playgroud)

下一步可能包括enum再次的项目:

enum MyEnum
{
  #define ITEM(X,Y) X=Y,
  #include "enum_definition_file"
};
Run Code Online (Sandbox Code Playgroud)

最后,您可以生成关于此的实用程序函数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");
}
Run Code Online (Sandbox Code Playgroud)

该解决方案可以应用于较旧的C++标准,并且它不使用现代C++元素,但它可以用于生成大量代码而无需太多的努力和维护.

  • 不需要单独的文件。这本质上是一个[x-macro](https://en.wikipedia.org/wiki/X_Macro)。 (2认同)

Men*_*nse 5

几天前我遇到了同样的问题.没有一些奇怪的宏魔法,我找不到任何C++解决方案,所以我决定编写一个CMake代码生成器来生成简单的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)>
)
Run Code Online (Sandbox Code Playgroud)

该函数搜索文件系统中的包含文件(使用include_directories命令提供的include目录),读取它们并执行一些正则表达式以生成类和函数.

注意:constexpr意味着在C++中内联,因此使用USE_CONSTEXPR选项将生成一个仅头类!

例:

./includes/ah:

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;
Run Code Online (Sandbox Code Playgroud)

./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")
Run Code Online (Sandbox Code Playgroud)

产生:

./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
Run Code Online (Sandbox Code Playgroud)

./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>";
   }
}
}
Run Code Online (Sandbox Code Playgroud)

更新:

该脚本现在还支持作用域枚举(enum class | struct),并将其移动到一个单独的repo,其中包含我经常使用的其他脚本:https://github.com/mensinda/cmakeBuildTools


归档时间:

查看次数:

139493 次

最近记录:

6 年,3 月 前