与其他类似的问题不同,这个问题是关于如何使用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++中打印变量类型是否可能解释的解决方案感到惊讶?,使用的思想从Can I obtain c++ type name in a constexpr way?使用这种技术,可以构造一个类似的函数来获取枚举值为string:

#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上进行了测试,但是应该可以通过对整数常量进行一些调整来适应其他编译器。当然,它仍然在底层使用宏,但至少有一个宏不需要访问枚举实现。

其他回答

如果你的枚举看起来像

enum MyEnum
{
  AAA = -8,
  BBB = '8',
  CCC = AAA + BBB
};

你可以移动枚举的内容到一个新文件:

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 MyEnum
{
  #define ITEM(X,Y) X=Y,
  #include "enum_definition_file"
};

最后你可以生成关于这个枚举的实用函数:

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++元素,但它可以用来生成大量代码,而不需要太多的工作和维护。

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

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

在类/struct (struct默认为public成员)和重载操作符中使用enum的解决方案:

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;

这将输出“red 12”。你可以重载<<使蓝色输出成为一个字符串(尽管这可能会导致歧义,所以不可能),但它不会与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;
};

我采用了@antron的想法,并以不同的方式实现:生成一个真正的枚举类。

这个实现满足了最初问题中列出的所有要求,但目前只有一个真正的限制:它假设枚举值要么没有提供,要么如果提供了,必须从0开始,并且无间隙地按顺序递增。

这并不是一个内在的限制——只是我不使用特别的enum值。如果需要,可以用传统的开关/案例实现替换向量查找。

解决方案使用一些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); \
    } 
#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宏生成“枚举类”和“枚举反射函数”辅助类。

为了减少错误,Everything只定义一个ENUM_MAKE。

这种代码的优点是自动创建用于反射和查看宏代码,易于理解的代码。'enum to string', 'string to enum'性能都是算法O(1)。

缺点是第一次使用时,枚举relection的string vector和map的helper类被初始化。 但是如果你想,你也会被预初始化。- - - - - -