Is there a simple way to convert C++ enum to string?

后端 未结 30 2250
我在风中等你
我在风中等你 2020-11-22 10:37

Suppose we have some named enums:

enum MyEnum {
      FOO,
      BAR = 0x50
};

What I googled for is a script (any language) that scans all

相关标签:
30条回答
  • 2020-11-22 11:10

    The following ruby script attempts to parse the headers and builts the required sources alongside the original headers.

    #! /usr/bin/env ruby
    
    # Let's "parse" the headers
    # Note that using a regular expression is rather fragile
    # and may break on some inputs
    
    GLOBS = [
      "toto/*.h",
      "tutu/*.h",
      "tutu/*.hxx"
    ]
    
    enums = {}
    GLOBS.each { |glob|
      Dir[glob].each { |header|
        enums[header] = File.open(header, 'rb') { |f|
          f.read
        }.scan(/enum\s+(\w+)\s+\{\s*([^}]+?)\s*\}/m).collect { |enum_name, enum_key_and_values|
          [
            enum_name, enum_key_and_values.split(/\s*,\s*/).collect { |enum_key_and_value|
              enum_key_and_value.split(/\s*=\s*/).first
            }
          ]
        }
      }
    }
    
    
    # Now we build a .h and .cpp alongside the parsed headers
    # using the template engine provided with ruby
    require 'erb'
    
    template_h = ERB.new <<-EOS
    #ifndef <%= enum_name %>_to_string_h_
    #define <%= enum_name %>_to_string_h_ 1
    
    #include "<%= header %>"
    char* enum_to_string(<%= enum_name %> e);
    
    #endif
    EOS
    
    template_cpp = ERB.new <<-EOS
    #include "<%= enum_name %>_to_string.h"
    
    char* enum_to_string(<%= enum_name %> e)
    {
      switch (e)
      {<% enum_keys.each do |enum_key| %>
        case <%= enum_key %>: return "<%= enum_key %>";<% end %>
        default: return "INVALID <%= enum_name %> VALUE";
      }
    }
    EOS
    
    enums.each { |header, enum_name_and_keys|
      enum_name_and_keys.each { |enum_name, enum_keys|
        File.open("#{File.dirname(header)}/#{enum_name}_to_string.h", 'wb') { |built_h|
          built_h.write(template_h.result(binding))
        }
    
        File.open("#{File.dirname(header)}/#{enum_name}_to_string.cpp", 'wb') { |built_cpp|
          built_cpp.write(template_cpp.result(binding))
        }
      }
    }
    

    Using regular expressions makes this "parser" quite fragile, it may not be able to handle your specific headers gracefully.

    Let's say you have a header toto/a.h, containing definitions for enums MyEnum and MyEnum2. The script will build:

    toto/MyEnum_to_string.h
    toto/MyEnum_to_string.cpp
    toto/MyEnum2_to_string.h
    toto/MyEnum2_to_string.cpp
    

    More robust solutions would be:

    • Build all sources defining enums and their operations from another source. This means you'll define your enums in a XML/YML/whatever file which is much easier to parse than C/C++.
    • Use a real compiler such as suggested by Avdi.
    • Use preprocessor macros with or without templates.
    0 讨论(0)
  • 2020-11-22 11:12

    I just re-invented this wheel today, and thought I'd share it.

    This implementation does not require any changes to the code that defines the constants, which can be enumerations or #defines or anything else that devolves to an integer - in my case I had symbols defined in terms of other symbols. It also works well with sparse values. It even allows multiple names for the same value, returning the first one always. The only downside is that it requires you to make a table of the constants, which might become out-of-date as new ones are added for example.

    struct IdAndName
    {
       int          id;
       const char * name;
       bool operator<(const IdAndName &rhs) const { return id < rhs.id; }
    };
    #define ID_AND_NAME(x) { x, #x }
    
    const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end)
    {
       if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id)
          std::stable_sort(table_begin, table_end);
    
       IdAndName searchee = { id, NULL };
       IdAndName *p = std::lower_bound(table_begin, table_end, searchee);
       return (p == table_end || p->id != id) ? NULL : p->name;
    }
    
    template<int N>
    const char * IdToName(int id, IdAndName (&table)[N])
    {
       return IdToName(id, &table[0], &table[N]);
    }
    

    An example of how you'd use it:

    static IdAndName WindowsErrorTable[] =
    {
       ID_AND_NAME(INT_MAX),               // flag value to indicate unsorted table
       ID_AND_NAME(NO_ERROR),
       ID_AND_NAME(ERROR_INVALID_FUNCTION),
       ID_AND_NAME(ERROR_FILE_NOT_FOUND),
       ID_AND_NAME(ERROR_PATH_NOT_FOUND),
       ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES),
       ID_AND_NAME(ERROR_ACCESS_DENIED),
       ID_AND_NAME(ERROR_INVALID_HANDLE),
       ID_AND_NAME(ERROR_ARENA_TRASHED),
       ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY),
       ID_AND_NAME(ERROR_INVALID_BLOCK),
       ID_AND_NAME(ERROR_BAD_ENVIRONMENT),
       ID_AND_NAME(ERROR_BAD_FORMAT),
       ID_AND_NAME(ERROR_INVALID_ACCESS),
       ID_AND_NAME(ERROR_INVALID_DATA),
       ID_AND_NAME(ERROR_INVALID_DRIVE),
       ID_AND_NAME(ERROR_CURRENT_DIRECTORY),
       ID_AND_NAME(ERROR_NOT_SAME_DEVICE),
       ID_AND_NAME(ERROR_NO_MORE_FILES)
    };
    
    const char * error_name = IdToName(GetLastError(), WindowsErrorTable);
    

    The IdToName function relies on std::lower_bound to do quick lookups, which requires the table to be sorted. If the first two entries in the table are out of order, the function will sort it automatically.

    Edit: A comment made me think of another way of using the same principle. A macro simplifies the generation of a big switch statement.

    #define ID_AND_NAME(x) case x: return #x
    
    const char * WindowsErrorToName(int id)
    {
        switch(id)
        {
            ID_AND_NAME(ERROR_INVALID_FUNCTION);
            ID_AND_NAME(ERROR_FILE_NOT_FOUND);
            ID_AND_NAME(ERROR_PATH_NOT_FOUND);
            ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES);
            ID_AND_NAME(ERROR_ACCESS_DENIED);
            ID_AND_NAME(ERROR_INVALID_HANDLE);
            ID_AND_NAME(ERROR_ARENA_TRASHED);
            ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY);
            ID_AND_NAME(ERROR_INVALID_BLOCK);
            ID_AND_NAME(ERROR_BAD_ENVIRONMENT);
            ID_AND_NAME(ERROR_BAD_FORMAT);
            ID_AND_NAME(ERROR_INVALID_ACCESS);
            ID_AND_NAME(ERROR_INVALID_DATA);
            ID_AND_NAME(ERROR_INVALID_DRIVE);
            ID_AND_NAME(ERROR_CURRENT_DIRECTORY);
            ID_AND_NAME(ERROR_NOT_SAME_DEVICE);
            ID_AND_NAME(ERROR_NO_MORE_FILES);
            default: return NULL;
        }
    }
    
    0 讨论(0)
  • 2020-11-22 11:12

    QT is able to pull that of (thanks to the meta object compiler):

    QNetworkReply::NetworkError error;
    
    error = fetchStuff();
    
    if (error != QNetworkReply::NoError) {
    
        QString errorValue;
    
        QMetaObject meta = QNetworkReply::staticMetaObject;
    
        for (int i=0; i < meta.enumeratorCount(); ++i) {
    
            QMetaEnum m = meta.enumerator(i);
    
            if (m.name() == QLatin1String("NetworkError")) {
    
                errorValue = QLatin1String(m.valueToKey(error));
    
                break;
    
            }
    
        }
    
        QMessageBox box(QMessageBox::Information, "Failed to fetch",
    
                    "Fetching stuff failed with error '%1`").arg(errorValue),
    
                    QMessageBox::Ok);
    
        box.exec();
    
        return 1;
    
    }
    

    In Qt every class that has the Q_OBJECT macro will automatically have a static member "staticMetaObject" of the type QMetaObject. You can then find all sorts of cool things like the properties, signals, slots and indeed enums.

    Source

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

    Suma's macro solution is nice. You don't need to have two different macro's, though. C++ wil happily include a header twice. Just leave out the include guard.

    So you'd have an foobar.h defining just

    ENUM(Foo, 1)
    ENUM(Bar, 2)
    

    and you would include it like this:

    #define ENUMFACTORY_ARGUMENT "foobar.h"
    #include "enumfactory.h"
    

    enumfactory.h will do 2 #include ENUMFACTORY_ARGUMENTs. In the first round, it expands ENUM like Suma's DECLARE_ENUM; in the second round ENUM works like DEFINE_ENUM.

    You can include enumfactory.h multiple times, too, as long as you pass in different #define's for ENUMFACTORY_ARGUMENT

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

    That's pretty much the only way it can be done (an array of string could work also).

    The problem is, once a C program is compiled, the binary value of the enum is all that is used, and the name is gone.

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

    This was my solution with BOOST:

    #include <boost/preprocessor.hpp>
    
    #define X_STR_ENUM_TOSTRING_CASE(r, data, elem)                                 \
        case elem : return BOOST_PP_STRINGIZE(elem);
    
    #define X_ENUM_STR_TOENUM_IF(r, data, elem)                                     \
        else if(data == BOOST_PP_STRINGIZE(elem)) return elem;
    
    #define STR_ENUM(name, enumerators)                                             \
        enum name {                                                                 \
            BOOST_PP_SEQ_ENUM(enumerators)                                          \
        };                                                                          \
                                                                                    \
        inline const QString enumToStr(name v)                                      \
        {                                                                           \
            switch (v)                                                              \
            {                                                                       \
                BOOST_PP_SEQ_FOR_EACH(                                              \
                    X_STR_ENUM_TOSTRING_CASE,                                       \
                    name,                                                           \
                    enumerators                                                     \
                )                                                                   \
                                                                                    \
                default:                                                            \
                    return "[Unknown " BOOST_PP_STRINGIZE(name) "]";                \
            }                                                                       \
        }                                                                           \
                                                                                    \
        template <typename T>                                                       \
        inline const T strToEnum(QString v);                                        \
                                                                                    \
        template <>                                                                 \
        inline const name strToEnum(QString v)                                      \
        {                                                                           \
            if(v=="")                                                               \
                throw std::runtime_error("Empty enum value");                       \
                                                                                    \
            BOOST_PP_SEQ_FOR_EACH(                                                  \
                X_ENUM_STR_TOENUM_IF,                                               \
                v,                                                                  \
                enumerators                                                         \
            )                                                                       \
                                                                                    \
            else                                                                    \
                throw std::runtime_error(                                           \
                            QString("[Unknown value %1 for enum %2]")               \
                                .arg(v)                                             \
                                .arg(BOOST_PP_STRINGIZE(name))                      \
                                    .toStdString().c_str());                        \
        }
    

    To create enum, declare:

    STR_ENUM
    (
        SERVICE_RELOAD,
            (reload_log)
            (reload_settings)
            (reload_qxml_server)
    )
    

    For conversions:

    SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
    QString serviceReloadStr = enumToStr(reload_log);
    
    0 讨论(0)
提交回复
热议问题