enum to string in modern C++11 / C++14 / C++17 and future C++20

后端 未结 28 1931
逝去的感伤
逝去的感伤 2020-11-22 16:57

Contrary to all other similar questions, this question is about using the new C++ features.

  • 2008 c Is there a simple way to convert C++ enum to string?
  • <
相关标签:
28条回答
  • 2020-11-22 17:11

    This is similar to Yuri Finkelstein; but does not required boost. I am using a map so you can assign any value to the enums, any order.

    Declaration of enum class as:

    DECLARE_ENUM_WITH_TYPE(TestEnumClass, int32_t, ZERO = 0x00, TWO = 0x02, ONE = 0x01, THREE = 0x03, FOUR);
    

    The following code will automatically create the enum class and overload:

    • '+' '+=' for std::string
    • '<<' for streams
    • '~' just to convert to string (Any unary operator will do, but I personally don't like it for clarity)
    • '*' to get the count of enums

    No boost required, all required functions provided.

    Code:

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

    Example:

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

    You can run the code here

    0 讨论(0)
  • 2020-11-22 17:11

    I am not sure if this approach is already covered in one of the other answers (actually it is, see below). I encountered the problem many times and didnt find a solution that did not use obfuscated macros or third party libraries. Hence I decided to write my own obfuscated macro version.

    What I want to enable is the equivalent of

    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
    }
    

    which should print

    ONE
    TWO
    13
    

    I am not a fan of macros. However, unless c++ natively supports converting enums to strings one has to use some sort of code generation and/or macros (and I doubt this will happen too soon). I am using a 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
    

    Most of it is defining and undefining symbols that the user will pass as parameter to the X-marco via an include. The usage is like this

    #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

    Note that I didnt include choosing the underlying type yet. I didnt need it so far, but it should be straight forward to modify to code to enable that.

    Only after writing this I realized that it is rather similar to eferions answer. Maybe I read it before and maybe it was the main source of inspiration. I was always failing in understanding X-macros until I wrote my own ;).

    0 讨论(0)
  • 2020-11-22 17:13

    You could use a reflection library, like 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"
    
    0 讨论(0)
  • 2020-11-22 17:18

    Magic Enum header-only library provides static reflection for enums (to string, from string, iteration) for 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
    };
    

    For more examples check home repository https://github.com/Neargye/magic_enum.

    Where is the drawback?

    This library uses a compiler-specific hack (based on __PRETTY_FUNCTION__ / __FUNCSIG__), which works on Clang >= 5, MSVC >= 15.3 and GCC >= 9.

    Enum value must be in range [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;
      };
      }
      
    0 讨论(0)
  • 2020-11-22 17:18

    Just generate your enums. Writing a generator for that purpose is about five minutes' work.

    Generator code in java and python, super easy to port to any language you like, including C++.

    Also super easy to extend by whatever functionality you want.

    example input:

    First = 5
    Second
    Third = 7
    Fourth
    Fifth=11
    

    generated header:

    #include <iosfwd>
    
    enum class Hallo
    {
        First = 5,
        Second = 6,
        Third = 7,
        Fourth = 8,
        Fifth = 11
    };
    
    std::ostream & operator << (std::ostream &, const Hallo&);
    

    generated cpp file

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

    And the generator, in a very terse form as a template for porting and extension. This example code really tries to avoid overwriting any files but still use it at your own risk.

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

    And a port to Python 3.5 because different enough to be potentially helpful

    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()
    
    0 讨论(0)
  • 2020-11-22 17:18

    Well, yet another option. A typical use case is where you need constants for the HTTP verbs as well as using its string version values.

    The example:

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

    The VERB class:

    // -----------------------------------------------------------
    // -----------------------------------------------------------
    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
    
    0 讨论(0)
提交回复
热议问题