与其他类似的问题不同,这个问题是关于如何使用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++代码……


当前回答

(类似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

其他回答

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

更多示例请查看home repository 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]内。

By default MAGIC_ENUM_RANGE_MIN = -128, MAGIC_ENUM_RANGE_MAX = 128. If need another range for all enum types by default, redefine the macro MAGIC_ENUM_RANGE_MIN and MAGIC_ENUM_RANGE_MAX. MAGIC_ENUM_RANGE_MIN must be less or equals than 0 and must be greater than INT16_MIN. MAGIC_ENUM_RANGE_MAX must be greater than 0 and must be less than INT16_MAX. If need another range for specific enum type, add specialization enum_range for necessary enum type. #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; }; }

早在2011年,我花了一个周末的时间对一个基于宏的解决方案进行微调,最终从未使用过它。

我目前的程序是启动Vim,在一个空的开关体中复制枚举数,启动一个新的宏,将第一个枚举数转换为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在评论中发布了另一种方法。

你可以使用一个反射库,比如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"

几天前我也遇到了同样的问题。没有一些奇怪的宏魔法,我找不到任何c++解决方案,所以我决定写一个CMake代码生成器来生成简单的开关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文件(使用include_directories命令提供的include目录),读取它们并执行一些regex来生成类和函数。

注意:constexpr在c++中意味着内联,所以使用USE_CONSTEXPR选项将只生成一个头类!

例子:

- includes a。h:

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

更新:

脚本现在还支持作用域枚举(枚举类|struct)和 我将它与一些我经常使用的其他脚本一起移动到一个单独的repo: https://github.com/mensinda/cmakeBuildTools

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

生成器代码在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()