Easy way to use variables of enum types as string in C?

前端 未结 19 2023
太阳男子
太阳男子 2020-11-22 08:47

Here\'s what I am trying to do:

typedef enum { ONE, TWO, THREE } Numbers;

I am trying to write a function that would do a switch case sim

相关标签:
19条回答
  • 2020-11-22 09:15

    Check out the ideas at Mu Dynamics Research Labs - Blog Archive. I found this earlier this year - I forget the exact context where I came across it - and have adapted it into this code. We can debate the merits of adding an E at the front; it is applicable to the specific problem addressed, but not part of a general solution. I stashed this away in my 'vignettes' folder - where I keep interesting scraps of code in case I want them later. I'm embarrassed to say that I didn't keep a note of where this idea came from at the time.

    Header: paste1.h

    /*
    @(#)File:           $RCSfile: paste1.h,v $
    @(#)Version:        $Revision: 1.1 $
    @(#)Last changed:   $Date: 2008/05/17 21:38:05 $
    @(#)Purpose:        Automated Token Pasting
    */
    
    #ifndef JLSS_ID_PASTE_H
    #define JLSS_ID_PASTE_H
    
    /*
     * Common case when someone just includes this file.  In this case,
     * they just get the various E* tokens as good old enums.
     */
    #if !defined(ETYPE)
    #define ETYPE(val, desc) E##val,
    #define ETYPE_ENUM
    enum {
    #endif /* ETYPE */
    
       ETYPE(PERM,  "Operation not permitted")
       ETYPE(NOENT, "No such file or directory")
       ETYPE(SRCH,  "No such process")
       ETYPE(INTR,  "Interrupted system call")
       ETYPE(IO,    "I/O error")
       ETYPE(NXIO,  "No such device or address")
       ETYPE(2BIG,  "Arg list too long")
    
    /*
     * Close up the enum block in the common case of someone including
     * this file.
     */
    #if defined(ETYPE_ENUM)
    #undef ETYPE_ENUM
    #undef ETYPE
    ETYPE_MAX
    };
    #endif /* ETYPE_ENUM */
    
    #endif /* JLSS_ID_PASTE_H */
    

    Example source:

    /*
    @(#)File:           $RCSfile: paste1.c,v $
    @(#)Version:        $Revision: 1.2 $
    @(#)Last changed:   $Date: 2008/06/24 01:03:38 $
    @(#)Purpose:        Automated Token Pasting
    */
    
    #include "paste1.h"
    
    static const char *sys_errlist_internal[] = {
    #undef JLSS_ID_PASTE_H
    #define ETYPE(val, desc) desc,
    #include "paste1.h"
        0
    #undef ETYPE
    };
    
    static const char *xerror(int err)
    {
        if (err >= ETYPE_MAX || err <= 0)
            return "Unknown error";
        return sys_errlist_internal[err];
    }
    
    static const char*errlist_mnemonics[] = {
    #undef JLSS_ID_PASTE_H
    #define ETYPE(val, desc) [E ## val] = "E" #val,
    #include "paste1.h"
    #undef ETYPE
    };
    
    #include <stdio.h>
    
    int main(void)
    {
        int i;
    
        for (i = 0; i < ETYPE_MAX; i++)
        {
            printf("%d: %-6s: %s\n", i, errlist_mnemonics[i], xerror(i));
        }
        return(0);
    }
    

    Not necessarily the world's cleanest use of the C pre-processor - but it does prevent writing the material out multiple times.

    0 讨论(0)
  • 2020-11-22 09:15

    I have created a simple templated class streamable_enum that uses stream operators << and >> and is based on the std::map<Enum, std::string>:

    #ifndef STREAMABLE_ENUM_HPP
    #define STREAMABLE_ENUM_HPP
    
    #include <iostream>
    #include <string>
    #include <map>
    
    template <typename E>
    class streamable_enum
    {
    public:
        typedef typename std::map<E, std::string> tostr_map_t;
        typedef typename std::map<std::string, E> fromstr_map_t;
    
        streamable_enum()
        {}
    
        streamable_enum(E val) :
            Val_(val)
        {}
    
        operator E() {
            return Val_;
        }
    
        bool operator==(const streamable_enum<E>& e) {
            return this->Val_ == e.Val_;
        }
    
        bool operator==(const E& e) {
            return this->Val_ == e;
        }
    
        static const tostr_map_t& to_string_map() {
            static tostr_map_t to_str_(get_enum_strings<E>());
            return to_str_;
        }
    
        static const fromstr_map_t& from_string_map() {
            static fromstr_map_t from_str_(reverse_map(to_string_map()));
            return from_str_;
        }
    private:
        E Val_;
    
        static fromstr_map_t reverse_map(const tostr_map_t& eToS) {
            fromstr_map_t sToE;
            for (auto pr : eToS) {
                sToE.emplace(pr.second, pr.first);
            }
            return sToE;
        }
    };
    
    template <typename E>
    streamable_enum<E> stream_enum(E e) {
        return streamable_enum<E>(e);
    }
    
    template <typename E>
    typename streamable_enum<E>::tostr_map_t get_enum_strings() {
        // \todo throw an appropriate exception or display compile error/warning
        return {};
    }
    
    template <typename E>
    std::ostream& operator<<(std::ostream& os, streamable_enum<E> e) {
        auto& mp = streamable_enum<E>::to_string_map();
        auto res = mp.find(e);
        if (res != mp.end()) {
            os << res->second;
        } else {
            os.setstate(std::ios_base::failbit);
        }
        return os;
    }
    
    template <typename E>
    std::istream& operator>>(std::istream& is, streamable_enum<E>& e) {
        std::string str;
        is >> str;
        if (str.empty()) {
            is.setstate(std::ios_base::failbit);
        }
        auto& mp = streamable_enum<E>::from_string_map();
        auto res = mp.find(str);
        if (res != mp.end()) {
            e = res->second;
        } else {
            is.setstate(std::ios_base::failbit);
        }
        return is;
    }
    
    #endif
    

    Usage:

    #include "streamable_enum.hpp"
    
    using std::cout;
    using std::cin;
    using std::endl;
    
    enum Animal {
        CAT,
        DOG,
        TIGER,
        RABBIT
    };
    
    template <>
    streamable_enum<Animal>::tostr_map_t get_enum_strings<Animal>() {
        return {
            { CAT, "Cat"},
            { DOG, "Dog" },
            { TIGER, "Tiger" },
            { RABBIT, "Rabbit" }
        };
    }
    
    int main(int argc, char* argv []) {
        cout << "What animal do you want to buy? Our offering:" << endl;
        for (auto pr : streamable_enum<Animal>::to_string_map()) {          // Use from_string_map() and pr.first instead
            cout << " " << pr.second << endl;                               // to have them sorted in alphabetical order
        }
        streamable_enum<Animal> anim;
        cin >> anim;
        if (!cin) {
            cout << "We don't have such animal here." << endl;
        } else if (anim == Animal::TIGER) {
            cout << stream_enum(Animal::TIGER) << " was a joke..." << endl;
        } else {
            cout << "Here you are!" << endl;
        }
    
        return 0;
    }
    
    0 讨论(0)
  • 2020-11-22 09:16

    There is definitely a way to do this -- use X() macros. These macros use the C preprocessor to construct enums, arrays and code blocks from a list of source data. You only need to add new items to the #define containing the X() macro. The switch statement would expand automatically.

    Your example can be written as follows:

     // Source data -- Enum, String
     #define X_NUMBERS \
        X(ONE,   "one") \
        X(TWO,   "two") \
        X(THREE, "three")
    
     ...
    
     // Use preprocessor to create the Enum
     typedef enum {
      #define X(Enum, String)       Enum,
       X_NUMBERS
      #undef X
     } Numbers;
    
     ...
    
     // Use Preprocessor to expand data into switch statement cases
     switch(num)
     {
     #define X(Enum, String) \
         case Enum:  strcpy(num_str, String); break;
     X_NUMBERS
     #undef X
    
         default: return 0; break;
     }
     return 1;
    

    There are more efficient ways (i.e. using X Macros to create an string array and enum index), but this is the simplest demo.

    0 讨论(0)
  • 2020-11-22 09:16

    Making something both a C identifier and a string

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

    I thought that a solution like Boost.Fusion one for adapting structs and classes would be nice, they even had it at some point, to use enums as a fusion sequence.

    So I made just some small macros to generate the code to print the enums. This is not perfect and has nothing to see with Boost.Fusion generated boilerplate code, but can be used like the Boost Fusion macros. I want to really do generate the types needed by Boost.Fusion to integrate in this infrastructure which allows to print names of struct members, but this will happen later, for now this is just macros :

    #ifndef SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
    #define SWISSARMYKNIFE_ENUMS_ADAPT_ENUM_HPP
    
    #include <swissarmyknife/detail/config.hpp>
    
    #include <string>
    #include <ostream>
    #include <boost/preprocessor/cat.hpp>
    #include <boost/preprocessor/stringize.hpp>
    #include <boost/preprocessor/seq/for_each.hpp>
    
    
    #define SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C(                     \
        R, unused, ENUMERATION_ENTRY)                                               \
        case ENUMERATION_ENTRY:                                                     \
          return BOOST_PP_STRINGIZE(ENUMERATION_ENTRY);                             \
        break;                                                                      
    
    /**
     * \brief Adapts ENUM to reflectable types.
     *
     * \param ENUM_TYPE To be adapted
     * \param ENUMERATION_SEQ Sequence of enum states
     */
    #define SWISSARMYKNIFE_ADAPT_ENUM(ENUM_TYPE, ENUMERATION_SEQ)                   \
        inline std::string to_string(const ENUM_TYPE& enum_value) {                 \
          switch (enum_value) {                                                     \
          BOOST_PP_SEQ_FOR_EACH(                                                    \
              SWISSARMYKNIFE_ADAPT_ENUM_EACH_ENUMERATION_ENTRY_C,                   \
              unused, ENUMERATION_SEQ)                                              \
            default:                                                                \
              return BOOST_PP_STRINGIZE(ENUM_TYPE);                                 \
          }                                                                         \
        }                                                                           \
                                                                                    \
        inline std::ostream& operator<<(std::ostream& os, const ENUM_TYPE& value) { \
          os << to_string(value);                                                   \
          return os;                                                                \
        }
    
    #endif
    

    The old answer below is pretty bad, please don't use that. :)

    Old answer:

    I've been searching a way which solves this problem without changing too much the enums declaration syntax. I came to a solution which uses the preprocessor to retrieve a string from a stringified enum declaration.

    I'm able to define non-sparse enums like this :

    SMART_ENUM(State, 
        enum State {
            RUNNING,
            SLEEPING, 
            FAULT, 
            UNKNOWN
        })
    

    And I can interact with them in different ways:

    // With a stringstream
    std::stringstream ss;
    ss << State::FAULT;
    std::string myEnumStr = ss.str();
    
    //Directly to stdout
    std::cout << State::FAULT << std::endl;
    
    //to a string
    std::string myStr = State::to_string(State::FAULT);
    
    //from a string
    State::State myEnumVal = State::from_string(State::FAULT);
    

    Based on the following definitions :

    #define SMART_ENUM(enumTypeArg, ...)                                                     \
    namespace enumTypeArg {                                                                  \
        __VA_ARGS__;                                                                         \
        std::ostream& operator<<(std::ostream& os, const enumTypeArg& val) {                 \
                os << swissarmyknife::enums::to_string(#__VA_ARGS__, val);                   \
                return os;                                                                   \
        }                                                                                    \
                                                                                         \
        std::string to_string(const enumTypeArg& val) {                                      \
                return swissarmyknife::enums::to_string(#__VA_ARGS__, val);                  \
        }                                                                                    \
                                                                                         \
        enumTypeArg from_string(const std::string &str) {                                    \
                return swissarmyknife::enums::from_string<enumTypeArg>(#__VA_ARGS__, str);   \
        }                                                                                    \
    }                                                                                        \
    
    
    namespace swissarmyknife { namespace enums {
    
        static inline std::string to_string(const std::string completeEnumDeclaration, size_t enumVal) throw (std::runtime_error) {
            size_t begin = completeEnumDeclaration.find_first_of('{');
            size_t end = completeEnumDeclaration.find_last_of('}');
            const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );
    
            size_t count = 0;
            size_t found = 0;
            do {
                found = identifiers.find_first_of(",}", found+1);
    
                if (enumVal == count) {
                    std::string identifiersSubset = identifiers.substr(0, found);
                    size_t beginId = identifiersSubset.find_last_of("{,");
                    identifiersSubset = identifiersSubset.substr(beginId+1);
                    boost::algorithm::trim(identifiersSubset);
                    return identifiersSubset;
                }
    
                ++count;
            } while (found != std::string::npos);
    
            throw std::runtime_error("The enum declaration provided doesn't contains this state.");
        }                                                  
    
        template <typename EnumType>
        static inline EnumType from_string(const std::string completeEnumDeclaration, const std::string &enumStr) throw (std::runtime_error) {
            size_t begin = completeEnumDeclaration.find_first_of('{');
            size_t end = completeEnumDeclaration.find_last_of('}');
            const std::string identifiers = completeEnumDeclaration.substr(begin + 1, end );
    
            size_t count = 0;
            size_t found = 0;
            do {
                found = identifiers.find_first_of(",}", found+1);
    
                std::string identifiersSubset = identifiers.substr(0, found);
                size_t beginId = identifiersSubset.find_last_of("{,");
                identifiersSubset = identifiersSubset.substr(beginId+1);
                boost::algorithm::trim(identifiersSubset);
    
                if (identifiersSubset == enumStr) {
                    return static_cast<EnumType>(count);
                }
    
                ++count;
            } while (found != std::string::npos);
    
            throw std::runtime_error("No valid enum value for the provided string");
        }                      
    
    }}
    

    When I'll need support for sparse enum and when I'll have more time I'll improve the to_string and from_string implementations with boost::xpressive, but this will costs in compilation time because of the important templating performed and the executable generated is likely to be really bigger. But this has the advantage that it will be more readable and maintanable than this ugly manual string manipulation code. :D

    Otherwise I always used boost::bimap to perform such mappings between enums value and string, but it has to be maintained manually.

    0 讨论(0)
  • 2020-11-22 09:18

    The technique from Making something both a C identifier and a string? can be used here.

    As usual with such preprocessor stuff, writing and understanding the preprocessor part can be hard, and includes passing macros to other macros and involves using # and ## operators, but using it is real easy. I find this style very useful for long enums, where maintaining the same list twice can be really troublesome.

    Factory code - typed only once, usually hidden in the header:

    enumFactory.h:

    // expansion macro for enum value definition
    #define ENUM_VALUE(name,assign) name assign,
    
    // expansion macro for enum to string conversion
    #define ENUM_CASE(name,assign) case name: return #name;
    
    // expansion macro for string to enum conversion
    #define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;
    
    /// declare the access function and define enum values
    #define DECLARE_ENUM(EnumType,ENUM_DEF) \
      enum EnumType { \
        ENUM_DEF(ENUM_VALUE) \
      }; \
      const char *GetString(EnumType dummy); \
      EnumType Get##EnumType##Value(const char *string); \
    
    /// define the access function names
    #define DEFINE_ENUM(EnumType,ENUM_DEF) \
      const char *GetString(EnumType value) \
      { \
        switch(value) \
        { \
          ENUM_DEF(ENUM_CASE) \
          default: return ""; /* handle input error */ \
        } \
      } \
      EnumType Get##EnumType##Value(const char *str) \
      { \
        ENUM_DEF(ENUM_STRCMP) \
        return (EnumType)0; /* handle input error */ \
      } \
    

    Factory used

    someEnum.h:

    #include "enumFactory.h"
    #define SOME_ENUM(XX) \
        XX(FirstValue,) \
        XX(SecondValue,) \
        XX(SomeOtherValue,=50) \
        XX(OneMoreValue,=100) \
    
    DECLARE_ENUM(SomeEnum,SOME_ENUM)
    

    someEnum.cpp:

    #include "someEnum.h"
    DEFINE_ENUM(SomeEnum,SOME_ENUM)
    

    The technique can be easily extended so that XX macros accepts more arguments, and you can also have prepared more macros to substitute for XX for different needs, similar to the three I have provided in this sample.

    Comparison to X-Macros using #include / #define / #undef

    While this is similar to X-Macros others have mentioned, I think this solution is more elegant in that it does not require #undefing anything, which allows you to hide more of the complicated stuff is in the factory the header file - the header file is something you are not touching at all when you need to define a new enum, therefore new enum definition is a lot shorter and cleaner.

    0 讨论(0)
提交回复
热议问题