与其他类似的问题不同,这个问题是关于如何使用c++的新特性。

2008 c Is there a simple way to convert C++ enum to string? 2008 c Easy way to use variables of enum types as string in C? 2008 c++ How to easily map c++ enums to strings 2008 c++ Making something both a C identifier and a string? 2008 c++ Is there a simple script to convert C++ enum to string? 2009 c++ How to use enums as flags in C++? 2011 c++ How to convert an enum type variable to a string? 2011 c++ Enum to String C++ 2011 c++ How to convert an enum type variable to a string? 2012 c How to convert enum names to string in c 2013 c Stringifying an conditionally compiled enum in C

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

优雅的方式使用c++ 11、c++ 14或c++ 17的新特性 或者在Boost中使用一些现成的东西 还有一些东西计划在c++ 20中实现

例子

举例往往比冗长的解释更好。 您可以在Coliru上编译和运行这个代码片段。 (另一个前面的例子也可用)

#include <map>
#include <iostream>

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

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

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

约束

请不要无价值的重复其他答案或基本链接。 请避免基于宏的臃肿答案,或尽量减少#define开销。 请不要手动enum ->字符串映射。

很高兴有

支持从不同于零的数字开始的enum值 支持负enum值 支持碎片enum值 支持类枚举(c++ 11) 支持类枚举:<类型>有任何允许的<类型> (c++ 11) 编译时(不是运行时)到字符串的转换, 或者至少在运行时快速执行(例如std::map不是一个好主意…) constexpr (c++ 11,然后在c++ 14/17/20中放松) noexcept (C + + 11) c++ 17/ c++ 20友好的代码片段

一个可能的想法是使用c++编译器功能,在编译时使用基于可变参数模板类和constexpr函数的元编程技巧来生成c++代码……


当前回答

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

这种方法最糟糕的地方是维护起来很痛苦,但维护其他一些类似的方法也很痛苦,不是吗?

这种方法的优点:

使用可变温度(c++ 14特性) 使用模板专门化,我们可以在使用无效值时“检测”(但我不确定这是否有用)。 看起来很整洁。 名称查找在编译时完成。

生活的例子

Edit

你是对的;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 ...);
}

这里的“最佳技巧”是为map使用变量template,其中包含每个枚举条目的值和名称;这个映射在每个翻译单元中都是相同的,并且到处都有相同的名称,所以非常简单明了,如果我们像这样调用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);
}

使用正确的操作符<<,现在我们可以这样使用枚举:

std::cout << MyEnum::AAA << '\n';

维护这一点也很麻烦,可以改进,但希望您能理解。

生活的例子

其他回答

下面的解决方案是基于给定enum的std::array<std::string,N>。

对于将enum转换为std::string,我们只需将enum转换为size_t,然后从数组中查找字符串。该操作是O(1),不需要堆分配。

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

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

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

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

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

这里有一些用法示例:http://coliru.stacked-crooked.com/a/e4212f93bee65076

编辑:重做我的解决方案,以便自定义Enum可以在类中使用。

这个要点提供了一个基于c++可变参数模板的简单映射。

这是一个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 < keyvalue…>可以双向使用:

fasion_names:把(in fashion: emo) fasion_names::把(“emo”)

这个例子可以在godbolt.org上找到

int main ()
{
  constexpr auto str = fasion_names::get(fasion::emo);
  constexpr auto fsn = fasion_names::get(str);
  return (int) fsn;
}

结果:gc -7 -std=c++1z -Ofast -S

main:
        mov     eax, 3
        ret

只要你愿意为每个可查询枚举编写单独的.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;

Code

该解决方案需要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

解释

此实现利用了这样一个事实,即枚举定义的带括号元素列表也可以用作类成员初始化的带括号初始化列表。

当ETRAITS在enumtrait .inl的上下文中计算时, 它展开为EnumTraits<>类的静态成员定义。

EDECL宏将每个枚举成员转换为初始化列表值,这些值随后被传递到成员构造函数中,以填充枚举信息。

EnumInitGuard类被设计为使用枚举初始化式值,然后折叠——留下一个纯枚举数据列表。

好处

c++式的语法 对枚举和枚举类的工作相同(*几乎) 适用于具有任何数字基础类型的enum类型 适用于具有自动、显式和分段初始化值的enum类型 大规模重命名工作(智能感知链接保留) 只有5个预处理器符号(3个全局的)

*与枚举相反,枚举类类型中引用同一枚举中的其他值的初始化式必须完全限定这些值

不利

每个可查询enum需要一个单独的.h/.cpp对 取决于错综复杂的宏和包括魔术 小的语法错误会演变成大得多的错误 定义类或命名空间作用域的枚举不是简单的 没有编译时初始化

评论

当打开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
}

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

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

视觉C + +。

编辑:检查下面的新版本

如上所述,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 不能在交换机情况下使用


可选择的解决方案

这种方法牺牲了线路编号(不是真的),但可以在开关情况下使用。

#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;}
};

勘误表

在GCC和clang上,# 0行与-迂腐冲突。

解决方案

要么从#第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;
}

真实的实现和使用

r3d体素- Enum r3dVoxel - ELoggingLevel

快速参考

这是一条直线