ChatGPT解决这个技术问题 Extra ChatGPT

How to easily map c++ enums to strings

I have a bunch of enum types in some library header files that I'm using, and I want to have a way of converting enum values to user strings - and vice-versa.

RTTI won't do it for me, because the 'user strings' need to be a bit more readable than the enumerations.

A brute force solution would be a bunch of functions like this, but I feel that's a bit too C-like.

enum MyEnum {VAL1, VAL2,VAL3};

String getStringFromEnum(MyEnum e)
{
  switch e
  {
  case VAL1: return "Value 1";
  case VAL2: return "Value 2";
  case VAL1: return "Value 3";
  default: throw Exception("Bad MyEnum");
  }
}

I have a gut feeling that there's an elegant solution using templates, but I can't quite get my head round it yet.

UPDATE: Thanks for suggestions - I should have made clear that the enums are defined in a third-party library header, so I don't want to have to change the definition of them.

My gut feeling now is to avoid templates and do something like this:

char * MyGetValue(int v, char *tmp); // implementation is trivial

#define ENUM_MAP(type, strings) char * getStringValue(const type &T) \
 { \
 return MyGetValue((int)T, strings); \
 }

; enum eee {AA,BB,CC}; - exists in library header file 
; enum fff {DD,GG,HH}; 

ENUM_MAP(eee,"AA|BB|CC")
ENUM_MAP(fff,"DD|GG|HH")

// To use...

    eee e;
    fff f;
    std::cout<< getStringValue(e);
    std::cout<< getStringValue(f);
possible none of them solves op's question, all solution above are tried by op already, he need something like reflection

C
Community

If you want the enum names themselves as strings, see this post. Otherwise, a std::map<MyEnum, char const*> will work nicely. (No point in copying your string literals to std::strings in the map)

For extra syntactic sugar, here's how to write a map_init class. The goal is to allow

std::map<MyEnum, const char*> MyMap;
map_init(MyMap)
    (eValue1, "A")
    (eValue2, "B")
    (eValue3, "C")
;

The function template <typename T> map_init(T&) returns a map_init_helper<T>. map_init_helper<T> stores a T&, and defines the trivial map_init_helper& operator()(typename T::key_type const&, typename T::value_type const&). (Returning *this from operator() allows the chaining of operator(), like operator<< on std::ostreams)

template<typename T> struct map_init_helper
{
    T& data;
    map_init_helper(T& d) : data(d) {}
    map_init_helper& operator() (typename T::key_type const& key, typename T::mapped_type const& value)
    {
        data[key] = value;
        return *this;
    }
};

template<typename T> map_init_helper<T> map_init(T& item)
{
    return map_init_helper<T>(item);
}

Since the function and helper class are templated, you can use them for any map, or map-like structure. I.e. it can also add entries to std::unordered_map

If you don't like writing these helpers, boost::assign offers the same functionality out of the box.


You are right to refer to another question. People should take a look in the "related questions" before posting...
@xtofl : The "related questions" shown here are totally different to the related questions listed when I posted the question!
@MSalters, a std::map is a useful way of handling the implementation, but I'm looking for some ways of reducing boilerplate code that could require.
@MSalters, it would be nice to be able to accept multiple arguments for operator[]. but sadly, one cannot do that. x[a, b] evaluate to x[b] . the (a, b) expression makes use of the comma operator. so it is equivalent to ["A"]["B"]["C"] in your code. you could change it to say [eValue1]["A"][eValu..
the function call operator would be a good candidate too: map_init(MyMap)(eValue1, "A")(eValue2, "B").... then it is equivalent to boost::assign : insert(MyMap)(eValue1, "A")(eValue2, "B")... (boost.org/doc/libs/1_35_0/libs/assign/doc/index.html)
A
Alastair

MSalters solution is a good one but basically re-implements boost::assign::map_list_of. If you have boost, you can use it directly:

#include <boost/assign/list_of.hpp>
#include <boost/unordered_map.hpp>
#include <iostream>

using boost::assign::map_list_of;

enum eee { AA,BB,CC };

const boost::unordered_map<eee,const char*> eeeToString = map_list_of
    (AA, "AA")
    (BB, "BB")
    (CC, "CC");

int main()
{
    std::cout << " enum AA = " << eeeToString.at(AA) << std::endl;
    return 0;
}

How would you use this where eeeToString is a data member of a class? I'm getting "Error: data member initialization is not allowed"
@User: Class data members are initialized in constructors, usually in the initializer list.
Is there a way to make this work for all enums. I have multiple enum declarations and don't want the map to only work for type eee in your case.
I tried using a template but then got and error: error: template declaration of 'const boost::unordered::unordered_map<T, const char*> enumToString'.
Actually this answer is largely obsolete with C++11.
j
jfs

Auto-generate one form from another.

Source:

enum {
  VALUE1, /* value 1 */
  VALUE2, /* value 2 */
};

Generated:

const char* enum2str[] = {
  "value 1", /* VALUE1 */
  "value 2", /* VALUE2 */
};

If enum values are large then a generated form could use unordered_map<> or templates as suggested by Constantin.

Source:

enum State{
  state0 = 0, /* state 0 */
  state1 = 1, /* state 1 */
  state2 = 2, /* state 2 */
  state3 = 4, /* state 3 */

  state16 = 0x10000, /* state 16 */
};

Generated:

template <State n> struct enum2str { static const char * const value; };
template <State n> const char * const enum2str<n>::value = "error";

template <> struct enum2str<state0> { static const char * const value; };
const char * const enum2str<state0>::value = "state 0";

Example:

#include <iostream>

int main()
{
  std::cout << enum2str<state16>::value << std::endl;
  return 0;
}

While fastest, it isn't as easy as @MSalters.
It is if you have a bit of perl/python to read a list of strings from a text file and generate a .h file with the static char at compile time. ="Write programs to write programs"
@mgb: perl/python are not the only options almost any template engine in any language will do (in this case one is generating both forms from a template).
@jf. Yes, the important point was to build static data tables at compile time automatically. I would probably prefer just generating a dumb static array.
Will this work if State is not known at compile time? I'm pretty sure it won't - in theory the compiler would have to instantiate the enum2str template with all possible values of the enum, which I'm pretty sure gcc (at least) won't do.
C
Community

I suggest a mix of using X-macros are the best solution and the following template functions:

To borrow off marcinkoziukmyopenidcom and extended

enum Colours {
#   define X(a) a,
#   include "colours.def"
#   undef X
    ColoursCount
};

char const* const colours_str[] = {
#   define X(a) #a,
#   include "colours.def"
#   undef X
    0
};

template <class T> T str2enum( const char* );
template <class T> const char* enum2str( T );

#define STR2ENUM(TYPE,ARRAY) \
template <> \
TYPE str2enum<TYPE>( const char* str ) \
    { \
    for( int i = 0; i < (sizeof(ARRAY)/sizeof(ARRAY[0])); i++ ) \
        if( !strcmp( ARRAY[i], str ) ) \
            return TYPE(i); \
    return TYPE(0); \
    }

#define ENUM2STR(TYPE,ARRAY) \
template <> \
const char* enum2str<TYPE>( TYPE v ) \
    { \
    return ARRAY[v]; \
    }

#define ENUMANDSTR(TYPE,ARRAY)\
    STR2ENUM(TYPE,ARRAY) \
    ENUM2STR(TYPE,ARRAY)

ENUMANDSTR(Colours,colours_str)

colour.def

X(Red)
X(Green)
X(Blue)
X(Cyan)
X(Yellow)
X(Magenta)

Is there a way to make the enum string array definition generic? (I don't know how to handle a X-Macro inside a macro and I don't handle the template easily)
stackoverflow.com/questions/70455489/… Very similar, but a bit improved and using standard preprocesor...
D
Debdatta Basu

I remember having answered this elsewhere on StackOverflow. Repeating it here. Basically it's a solution based on variadic macros, and is pretty easy to use:

#define AWESOME_MAKE_ENUM(name, ...) enum class name { __VA_ARGS__, __COUNT}; \
inline std::ostream& operator<<(std::ostream& os, name value) { \
std::string enumName = #name; \
std::string str = #__VA_ARGS__; \
int len = str.length(); \
std::vector<std::string> strings; \
std::ostringstream temp; \
for(int i = 0; i < len; i ++) { \
if(isspace(str[i])) continue; \
        else if(str[i] == ',') { \
        strings.push_back(temp.str()); \
        temp.str(std::string());\
        } \
        else temp<< str[i]; \
} \
strings.push_back(temp.str()); \
os << enumName << "::" << strings[static_cast<int>(value)]; \
return os;} 

To use it in your code, simply do:

AWESOME_MAKE_ENUM(Animal,
    DOG,
    CAT,
    HORSE
);
auto dog = Animal::DOG;
std::cout<<dog;

Just change the enum class declaration to enum to work on pre c++11.
You are right it works (the auto is also only c++11). Nice solution ! It would be perfect if you could also set a value for some enums
I guess I seen in boost something like that
C
Community

I use this solution which I reproduce below:

#define MACROSTR(k) #k

#define X_NUMBERS \
       X(kZero  ) \
       X(kOne   ) \
       X(kTwo   ) \
       X(kThree ) \
       X(kFour  ) \
       X(kMax   )

enum {
#define X(Enum)       Enum,
    X_NUMBERS
#undef X
} kConst;

static char *kConstStr[] = {
#define X(String) MACROSTR(String),
    X_NUMBERS
#undef X
};

int main(void)
{
    int k;
    printf("Hello World!\n\n");

    for (k = 0; k < kMax; k++)
    {
        printf("%s\n", kConstStr[k]);
    }

    return 0;
}

This is basic X Macros, and I'm staggered that this is the first answer here to suggest it! +1
j
jose.angel.jimenez

I have spent more time researching this topic that I'd like to admit. Luckily there are great open source solutions in the wild.

These are two great approaches, even if not well known enough (yet),

wise_enum

Standalone smart enum library for C++11/14/17. It supports all of the standard functionality that you would expect from a smart enum class in C++.

Limitations: requires at least C++11.

Better Enums

Reflective compile-time enum library with clean syntax, in a single header file, and without dependencies.

Limitations: based on macros, can't be used inside a class.


u
uIM7AI9S

I know I'm late to party, but for everyone else who comes to visit this page, u could try this, it's easier than everything there and makes more sense:

namespace texs {
    typedef std::string Type;
    Type apple = "apple";
    Type wood = "wood";
}

Are you suggesting using strings and not using enums at all ? That doesn't really solve the problem.
C
Constantin

If you want to get string representations of MyEnum variables, then templates won't cut it. Template can be specialized on integral values known at compile-time.

However, if that's what you want then try:

#include <iostream>

enum MyEnum { VAL1, VAL2 };

template<MyEnum n> struct StrMyEnum {
    static char const* name() { return "Unknown"; }
};

#define STRENUM(val, str) \
  template<> struct StrMyEnum<val> { \
    static char const* name() { return str; }};

STRENUM(VAL1, "Value 1");
STRENUM(VAL2, "Value 2");

int main() {
  std::cout << StrMyEnum<VAL2>::name();
}

This is verbose, but will catch errors like the one you made in question - your case VAL1 is duplicated.


Actually the method name() is not necessary. See my answer.
R
Richard Harrison

I'd be tempted to have a map m - and embedd this into the enum.

setup with m[MyEnum.VAL1] = "Value 1";

and all is done.


S
Smi

I've required this functionality several times for debugging/analyzing code from others. For this, I've written a Perl script which generates a class with several overloaded toString methods. Each toString method takes an Enum as an argument and returns const char*.

Of course, the script doesn't parse C++ for enums itself, but uses ctags for generating symbol table.

The Perl script is here: http://heinitz-it.de/download/enum2string/enum2string.pl.html


m
muqker

Your answers inspired me to write some macros myself. My requirements were the following:

only write each value of the enum once, so there are no double lists to maintain don't keep the enum values in a separate file that is later #included, so I can write it wherever I want don't replace the enum itself, I still want to have the enum type defined, but in addition to it I want to be able to map every enum name to the corresponding string (to not affect legacy code) the searching should be fast, so preferably no switch-case, for those huge enums

This code creates a classic enum with some values. In addition it creates as std::map which maps each enum value to it's name (i.e. map[E_SUNDAY] = "E_SUNDAY", etc.)

Ok, here is the code now:

EnumUtilsImpl.h:

map<int, string> & operator , (map<int, string> & dest, 
                               const pair<int, string> & keyValue) {
    dest[keyValue.first] = keyValue.second; 
    return dest;
}

#define ADD_TO_MAP(name, value) pair<int, string>(name, #name)

EnumUtils.h // this is the file you want to include whenever you need to do this stuff, you will use the macros from it:

#include "EnumUtilsImpl.h"
#define ADD_TO_ENUM(name, value) \
    name value

#define MAKE_ENUM_MAP_GLOBAL(values, mapName) \
    int __makeMap##mapName() {mapName, values(ADD_TO_MAP); return 0;}  \
    int __makeMapTmp##mapName = __makeMap##mapName();

#define MAKE_ENUM_MAP(values, mapName) \
    mapName, values(ADD_TO_MAP);

MyProjectCodeFile.h // this is an example of how to use it to create a custom enum:

#include "EnumUtils.h*

#define MyEnumValues(ADD) \
    ADD(val1, ), \
    ADD(val2, ), \
    ADD(val3, = 100), \
    ADD(val4, )

enum MyEnum {
    MyEnumValues(ADD_TO_ENUM)
};

map<int, string> MyEnumStrings;
// this is how you initialize it outside any function
MAKE_ENUM_MAP_GLOBAL(MyEnumValues, MyEnumStrings); 

void MyInitializationMethod()
{ 
    // or you can initialize it inside one of your functions/methods
    MAKE_ENUM_MAP(MyEnumValues, MyEnumStrings); 
}

Cheers.


O
OlivierB

Here is an attempt to get << and >> stream operators on enum automatically with an one line macro command only...

Definitions:

#include <string>
#include <iostream>
#include <stdexcept>
#include <algorithm>
#include <iterator>
#include <sstream>
#include <vector>

#define MAKE_STRING(str, ...) #str, MAKE_STRING1_(__VA_ARGS__)
#define MAKE_STRING1_(str, ...) #str, MAKE_STRING2_(__VA_ARGS__)
#define MAKE_STRING2_(str, ...) #str, MAKE_STRING3_(__VA_ARGS__)
#define MAKE_STRING3_(str, ...) #str, MAKE_STRING4_(__VA_ARGS__)
#define MAKE_STRING4_(str, ...) #str, MAKE_STRING5_(__VA_ARGS__)
#define MAKE_STRING5_(str, ...) #str, MAKE_STRING6_(__VA_ARGS__)
#define MAKE_STRING6_(str, ...) #str, MAKE_STRING7_(__VA_ARGS__)
#define MAKE_STRING7_(str, ...) #str, MAKE_STRING8_(__VA_ARGS__)
#define MAKE_STRING8_(str, ...) #str, MAKE_STRING9_(__VA_ARGS__)
#define MAKE_STRING9_(str, ...) #str, MAKE_STRING10_(__VA_ARGS__)
#define MAKE_STRING10_(str) #str

#define MAKE_ENUM(name, ...) MAKE_ENUM_(, name, __VA_ARGS__)
#define MAKE_CLASS_ENUM(name, ...) MAKE_ENUM_(friend, name, __VA_ARGS__)

#define MAKE_ENUM_(attribute, name, ...) name { __VA_ARGS__ }; \
    attribute std::istream& operator>>(std::istream& is, name& e) { \
        const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
        std::string str; \
        std::istream& r = is >> str; \
        const size_t len = sizeof(name##Str)/sizeof(name##Str[0]); \
        const std::vector<std::string> enumStr(name##Str, name##Str + len); \
        const std::vector<std::string>::const_iterator it = std::find(enumStr.begin(), enumStr.end(), str); \
        if (it != enumStr.end())\
            e = name(it - enumStr.begin()); \
        else \
            throw std::runtime_error("Value \"" + str + "\" is not part of enum "#name); \
        return r; \
    }; \
    attribute std::ostream& operator<<(std::ostream& os, const name& e) { \
        const char* name##Str[] = { MAKE_STRING(__VA_ARGS__) }; \
        return (os << name##Str[e]); \
    }

Usage:

// Declare global enum
enum MAKE_ENUM(Test3, Item13, Item23, Item33, Itdsdgem43);

class Essai {
public:
    // Declare enum inside class
    enum MAKE_CLASS_ENUM(Test, Item1, Item2, Item3, Itdsdgem4);

};

int main() {
    std::cout << Essai::Item1 << std::endl;

    Essai::Test ddd = Essai::Item1;
    std::cout << ddd << std::endl;

    std::istringstream strm("Item2");
    strm >> ddd;

    std::cout << (int) ddd << std::endl;
    std::cout << ddd << std::endl;
}

Not sure about the limitations of this scheme though... comments are welcome!


D
Daniel

By using designated array initializers your string array is independent of the order of elements in the enum:

enum Values {
    Val1,
    Val2
};

constexpr string_view v_name[] = {
    [Val1] = "Value 1",
    [Val2] = "Value 2"
}

This code results in: array designators are nonstandard in C++C/C++(2901)
@Xoyce Array designators are standard since C99.
m
moogs

in the header:

enum EFooOptions
 {
FooOptionsA = 0, EFooOptionsMin = 0,
FooOptionsB,
FooOptionsC,
FooOptionsD 
EFooOptionsMax
};
extern const wchar* FOO_OPTIONS[EFooOptionsMax];

in the .cpp file:

const wchar* FOO_OPTIONS[] = {
    L"One",
    L"Two",
    L"Three",
    L"Four"
};

Caveat: Don't handle bad array index. :) But you can easily add a function to verify the enum before getting the string from the array.


Indeed a very non-DRY-SPOT solution.
now that you mention DRY. the .h and .cpp file automatomagically generated from some other input file. I'd love to see better solutions (that don't resort to unnecessary complexity)
j
jamk

I just wanted to show this possible elegant solution using macros. This doesn t solve the problem but I think it is a good way to rethik about the problem.

#define MY_LIST(X) X(value1), X(value2), X(value3)

enum eMyEnum
    {
    MY_LIST(PLAIN)
    };

const char *szMyEnum[] =
    {
    MY_LIST(STRINGY)
    };


int main(int argc, char *argv[])
{

std::cout << szMyEnum[value1] << value1 <<" " <<  szMyEnum[value2] << value2 << std::endl;

return 0;
}

---- EDIT ----

After some internet research and some own experements I came to the following solution:

//this is the enum definition
#define COLOR_LIST(X) \
  X( RED    ,=21)      \
  X( GREEN  )      \
  X( BLUE   )      \
  X( PURPLE , =242)      \
  X( ORANGE )      \
  X( YELLOW )

//these are the macros
#define enumfunc(enums,value) enums,
#define enumfunc2(enums,value) enums value,
#define ENUM2SWITCHCASE(enums) case(enums): return #enums;

#define AUTOENUM(enumname,listname) enum enumname{listname(enumfunc2)};
#define ENUM2STRTABLE(funname,listname) char* funname(int val) {switch(val) {listname(ENUM2SWITCHCASE) default: return "undef";}}
#define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int values[] = {listname(enumfunc)};int N = sizeof(values)/sizeof(int);ENUM2STRTABLE(enum2str,listname)};

//here the enum and the string enum map table are generated
AUTOENUM(testenum,COLOR_LIST)
ENUM2STRTABLE(testfunenum,COLOR_LIST)
ENUM2STRUCTINFO(colorinfo,COLOR_LIST)//colorinfo structur {int values[]; int N; char * enum2str(int);}

//debug macros
#define str(a) #a
#define xstr(a) str(a)


int main( int argc, char** argv )
{
testenum x = YELLOW;
std::cout << testfunenum(GREEN) << "   " << testfunenum(PURPLE) << PURPLE << "  " << testfunenum(x);

for (int i=0;i< colorinfo::N;i++)
std::cout << std::endl << colorinfo::values[i] <<  "  "<< colorinfo::enum2str(colorinfo::values[i]);

  return EXIT_SUCCESS;
}

I just wanted to post it maybe someone could find this solution useful. There is no need of templates classes no need of c++11 and no need of boost so this could also be used for simple C.

---- EDIT2 ----

the information table can produce some problems when using more than 2 enums (compiler problem). The following workaround worked:

#define ENUM2STRUCTINFO(spacename,listname) namespace spacename { int spacename##_##values[] = {listname(enumfunc)};int spacename##_##N = sizeof(spacename##_##values)/sizeof(int);ENUM2STRTABLE(spacename##_##enum2str,listname)};

M
Madwyn
typedef enum {
    ERR_CODE_OK = 0,
    ERR_CODE_SNAP,

    ERR_CODE_NUM
} ERR_CODE;

const char* g_err_msg[ERR_CODE_NUM] = {
    /* ERR_CODE_OK   */ "OK",
    /* ERR_CODE_SNAP */ "Oh, snap!",
};

Above is my simple solution. One benefit of it is the 'NUM' which controls the size of the message array, it also prevents out of boundary access (if you use it wisely).

You can also define a function to get the string:

const char* get_err_msg(ERR_CODE code) {
    return g_err_msg[code];
}

Further to my solution, I then found the following one quite interesting. It generally solved the sync problem of the above one.

Slides here: http://www.slideshare.net/arunksaha/touchless-enum-tostring-28684724

Code here: https://github.com/arunksaha/enum_to_string


J
Jerzy Jamroz
#include <vector>
#include <string>

//Split one comma-separated value string to vector
std::vector<std::string> split(std::string csv, char separator){/*trivial*/}

//Initializer
#define ENUMIFY(name, ...)                                                                               \
  struct name                                                                                             \
  {                                                                                                       \
    enum Enum                                                                                       \
    {                                                                                                     \
      __VA_ARGS__                                                                                         \
    };                                                                                                    \
    static const std::vector<std::string>& Names()                                                        \
    {                                                                                                     \
      const static std::vector<std::string> _{split(#__VA_ARGS__, ',')}; \
      return _;                                                                                           \
    };                                                                                                    \
  };

Declaration:

ENUMIFY(States, INIT, ON, OFF, RUNNING)

Then all the enums are available, plus their strings are vectorized:

std::string enum_str = States::Names()[States::ON];

Another option is to use a static vector directly without the function wrapper.


Last edit changed enum class to enum. IMHO this violates SO's editing rules because it deviates from author's intent. The question might be 12 years old but I think it is OK to give an answer using C++11 idioms.
With enum class the access of the actual enum (which is written as States::ON) would need to be States::Enum::ON which seems a bit redundant since the enum is already namespaced inside the struct. You may be right though, maybe the better edit would be to change the enum access line?
It is to give an idea, it can be with enum class (my preference) and casting or just enum. The good point is that it is trivial and you define items just once. Another option is to use a static vector directly without the function wrapper.
f
fengshun

This is my solution, I refer to some other designs, but mine is more complete and simple to use.

// file: enum_with_string.h
#pragma once

#include <map>
#include <string>
#include <vector>

namespace EnumString {

template <typename T>
static inline void split_string_for_each(const std::string &str,
                                         const std::string &delimiter,
                                         const T &foreach_function,
                                         ssize_t max_number = -1) {
  ssize_t num = 0;
  std::string::size_type start;
  std::string::size_type end = -1;
  while (true) {
    start = str.find_first_not_of(delimiter, end + 1);
    if (start == std::string::npos) break;  // over

    end = str.find_first_of(delimiter, start + 1);

    if (end == std::string::npos) {
      foreach_function(num, str.substr(start));
      break;
    }
    foreach_function(num, str.substr(start, end - start));
    ++num;

    if (max_number > 0 && num == max_number) break;
  }
}

/**
 * Strip function, delete the specified characters on both sides of the string.
 */
inline std::string &strip(std::string &s,
                          const std::string &characters = " \t\r\n") {
  s.erase(0, s.find_first_not_of(characters));
  return s.erase(s.find_last_not_of(characters) + 1);
}

static inline std::map<int, std::string> ParserEnumDefine(
    const std::string &define_str) {
  int cur_num = 0;
  std::string cur_item_str;
  std::map<int, std::string> result_map;
  split_string_for_each(define_str, ",", [&](int num, const std::string &str) {
    split_string_for_each(
        str, "=",
        [&](int num, const std::string &str) {
          if (num == 0) cur_item_str = str;
          if (num == 1) cur_num = std::stoi(str);
        },
        2);
    result_map.emplace(cur_num, strip(cur_item_str));
    cur_num++;
  });
  return result_map;
}

}  // namespace EnumString

/**
 * Example:
 * @code
 * @endcode
 */
#define ENUM_WITH_STRING(Name, ...)                                     \
  enum class Name { __VA_ARGS__, __COUNT };                             \
  static inline const std::string &to_string(Name value) {              \
    static const auto map = EnumString::ParserEnumDefine(#__VA_ARGS__); \
    static const std::string cannot_converted =                         \
        "Cannot be converted to string";                                \
    int int_value = (int)value;                                         \
    if (map.count(int_value))                                           \
      return map.at(int_value);                                         \
    else                                                                \
      return cannot_converted;                                          \
  }

You can use it like this:

#include <iostream>
#include "enum_with_string.h"
ENUM_WITH_STRING(Animal, dog, cat, monkey = 50, fish, human = 100, duck)
int main() {
  std::cout << to_string(Animal::dog) << std::endl;
  std::cout << to_string(Animal::cat) << std::endl;
  std::cout << to_string(Animal::monkey) << std::endl;
  std::cout << to_string(Animal::fish) << std::endl;
  std::cout << to_string(Animal::human) << std::endl;
  std::cout << to_string(Animal::duck) << std::endl;
}

I have a github gist.


T
Tom

There is very simple way to repeat enum definitions like this:

#ifndef ENUM_ITEMS
...
enum myEnum
{
#ifndef ENUM_ITEM
#define ENUM_ITEM(i) i
#endif // !ENUM_ITEM
#endif // !ENUM_ITEMS trick: ENUM_ITEM(i) = ENUM_ITEMS ? #i : i
    ENUM_ITEM(DEFINITION),
    ...
    ENUM_ITEM(DEFINITION_N)
#ifndef ENUM_ITEMS
};
...
#endif // !ENUM_ITEMS

And then you can reuse it by including file again

#define ENUM_ITEMS
#define ENUM_ITEM(i) i
enum myEnum
{
#include "myCpp.cpp"
};
#undef ENUM_ITEM
    static const char* myEnum[] =
    {
#define ENUM_ITEM(i) #i
#include "myCpp.cpp"
// Include full file with defined ENUM_ITEMS => get enum items without code around
    };
    int max = sizeof(myEnum) / sizeof(char*);

Nice is Go To Definition will find proper line in this case...

And what about quite portable Enum class implementation ? It is not much optimized for easy understanding.

#define FOREACH_FRUIT(item) \
        item(apple)   \
        item(orange)  \
        item(grape, 5)   \
        item(banana)  \

No need to repeat or update copy of definition.

class EnumClass
{
#define GENERATE_ENUM(ENUM, ...) ENUM,
#define GENERATE_STRINGS(STRING, ...) { #STRING, ##__VA_ARGS__ },
#define GENERATE_SIZE(...) + 1
public:
    enum Enum {
        FOREACH_FRUIT(GENERATE_ENUM) // apple, orange, grape, banana,
    } _;
    EnumClass(Enum init)
    {
        _ = init; // grape(2)
        _EnumItem build[itemsNo] = { FOREACH_FRUIT(GENERATE_STRINGS) }; // _EnumItem build[itemsNo] = { { "apple"  }, { "orange"  }, { "grape",5 }, { "banana"  }, };
        int pos = 0;
        for (int i = 0; i < itemsNo; i++)
        {
            items[i].Name = build[i].Name;
            if (0 == build[i].No) {
                items[i].No = pos;
                for (int j = i; j--;)
                {
                    if (items[j].No == pos)
                        throw "Existing item # !";
                }
                pos++;
            }
            else {
                int destPos = build[i].No;
                if (destPos < pos) {
                    for (int j = 0; j < i; j++)
                    {
                        if (items[j].No == destPos)
                            throw "Existing item # !";
                    }
                }
                items[i].No = destPos;
                pos = destPos + 1;
            }
        }
    }
    operator int()
    {
        return items[_].No;
    }
    operator char*()
    {
        return items[_].Name;
    }
    EnumClass& operator ++(int)
    {
        if (_ == itemsNo - 1) {
            throw "Out of Enum options !";
        }
        _ = static_cast<EnumClass::Enum>(_ + 1);
        return *this;
    }
    EnumClass& operator --(int)
    {
        if (0 == _) {
            throw "Out of Enum options !";
        }
        _ = static_cast<EnumClass::Enum>(_ - 1);
        return *this;
    }
    EnumClass operator =(int right)
    {
        for (int i = 0; i < itemsNo; i++)
        {
            if (items[i].No == right)
            {
                _ = static_cast<EnumClass::Enum>(i);
                return *this;
            }
        }
        throw "Enum option does not exist !";
    }
    EnumClass operator =(char *right)
    {
        for (int i = 0; i < itemsNo; i++)
        {
            if (!strcmp(items[i].Name, right))
            {
                _ = static_cast<EnumClass::Enum>(i);
                return *this;
            }
        }
        throw "Enum option does not exist !";
    }
protected:
    static const int itemsNo = FOREACH_FRUIT(GENERATE_SIZE); // + 1 + 1 + 1 + 1; 
    struct _EnumItem {
        char *Name;
        int No;
    } items[itemsNo]; // { Name = "apple" No = 0 }, { Name = "orange" No = 1 } ,{ Name = "grape" No = 5 } ,{ Name = "banana" No = 6 }

#undef GENERATE_ENUM
#undef GENERATE_STRINGS
#undef GENERATE_SIZE
};

Now you can do any common operations + check definitions & runtime operations:

int main()
{
    EnumClass ec(EnumClass::grape);
    ec = "banana"; // ec {_=banana (3)...}
    ec--; // ec {_=grape (2)...}
    char *name = ec;
    int val = ec; // 5
    printf("%s(%i)", name, val); // grape(5)
    return 0;
}

printf problem ... "The compiler does not know, technically, which type is required."


佚名

I recently had the same issue with a vendor library (Fincad). Fortunately, the vendor provided xml doucumentation for all the enums. I ended up generating a map for each enum type and providing a lookup function for each enum. This technique also allows you to intercept a lookup outside the range of the enum.

I'm sure swig could do something similar for you, but I'm happy to provide the code generation utils which are written in ruby.

Here is a sample of the code:

std::map<std::string, switches::FCSW2::type> init_FCSW2_map() {
        std::map<std::string, switches::FCSW2::type> ans;
        ans["Act365Fixed"] = FCSW2::Act365Fixed;
        ans["actual/365 (fixed)"] = FCSW2::Act365Fixed;
        ans["Act360"] = FCSW2::Act360;
        ans["actual/360"] = FCSW2::Act360;
        ans["Act365Act"] = FCSW2::Act365Act;
        ans["actual/365 (actual)"] = FCSW2::Act365Act;
        ans["ISDA30360"] = FCSW2::ISDA30360;
        ans["30/360 (ISDA)"] = FCSW2::ISDA30360;
        ans["ISMA30E360"] = FCSW2::ISMA30E360;
        ans["30E/360 (30/360 ISMA)"] = FCSW2::ISMA30E360;
        return ans;
}
switches::FCSW2::type FCSW2_lookup(const char* fincad_switch) {
        static std::map<std::string, switches::FCSW2::type> switch_map = init_FCSW2_map();
        std::map<std::string, switches::FCSW2::type>::iterator it = switch_map.find(fincad_switch);
        if(it != switch_map.end()) {
                return it->second;
        } else {
                throw FCSwitchLookupError("Bad Match: FCSW2");
        }
}

Seems like you want to go the other way (enum to string, rather than string to enum), but this should be trivial to reverse.

-Whit


a) Does anyone else find this absolutely unreadable? A few typedefs and using declarations would vastly improve readability. b) local static declarations are not threadsafe. c) use const string& instead of char*, d) what about including the value that couldn't be found in the exception thrown?
佚名

See if the following syntax suits you:

// WeekEnd enumeration
enum WeekEnd
{
    Sunday = 1,
    Saturday = 7
};

// String support for WeekEnd
Begin_Enum_String( WeekEnd )
{
    Enum_String( Sunday );
    Enum_String( Saturday );
}
End_Enum_String;

// Convert from WeekEnd to string
const std::string &str = EnumString<WeekEnd>::From( Saturday );
// str should now be "Saturday"

// Convert from string to WeekEnd
WeekEnd w;
EnumString<WeekEnd>::To( w, "Sunday" );
// w should now be Sunday

If it does, then you might want to check out this article:
http://www.gamedev.net/reference/snippets/features/cppstringizing/


r
rmawatson

this right old mess is my effort based on bits and peices from SO. The for_each would have to be expanded to support more than 20 enum values. Tested it on visual studio 2019,clang and gcc. c++11

#define _enum_expand(arg) arg
#define _enum_select_for_each(_,_0, _1, _2,_3,_4, _5, _6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,N, ...) N
#define _enum_for_each_0(_call, arg0,arg1,...)
#define _enum_for_each_1(_call, arg0,arg1) _call(arg0,arg1)
#define _enum_for_each_2(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_1(_call,arg0, __VA_ARGS__))
#define _enum_for_each_3(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_2(_call,arg0, __VA_ARGS__))
#define _enum_for_each_4(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_3(_call,arg0, __VA_ARGS__))
#define _enum_for_each_5(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_4(_call,arg0, __VA_ARGS__))
#define _enum_for_each_6(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_5(_call,arg0, __VA_ARGS__))
#define _enum_for_each_7(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_6(_call,arg0, __VA_ARGS__))
#define _enum_for_each_8(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_7(_call,arg0, __VA_ARGS__))
#define _enum_for_each_9(_call, arg0,arg1, ...) _call(arg0,arg1)  _enum_expand(_enum_for_each_8(_call,arg0, __VA_ARGS__))
#define _enum_for_each_10(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_9(_call,arg0, __VA_ARGS__))
#define _enum_for_each_11(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_10(_call,arg0, __VA_ARGS__))
#define _enum_for_each_12(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_11(_call,arg0, __VA_ARGS__))
#define _enum_for_each_13(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_12(_call,arg0, __VA_ARGS__))
#define _enum_for_each_14(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_13(_call,arg0, __VA_ARGS__))
#define _enum_for_each_15(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_14(_call,arg0, __VA_ARGS__))
#define _enum_for_each_16(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_15(_call,arg0, __VA_ARGS__))
#define _enum_for_each_17(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_16(_call,arg0, __VA_ARGS__))
#define _enum_for_each_18(_call, arg0,arg1, ...) _call(arg0,arg1) _enum_expand(_enum_for_each_17(_call,arg0, __VA_ARGS__))
#define _enum_for_each_19(_call, arg0,arg1, ...) _call(arg) _enum_expand(_enum_for_each_18(_call,arg0, __VA_ARGS__))
#define _enum_for_each(arg, ...) \
    _enum_expand(_enum_select_for_each(_, ##__VA_ARGS__, \
    _enum_for_each_19, _enum_for_each_18, _enum_for_each_17, _enum_for_each_16, _enum_for_each_15, \
    _enum_for_each_14, _enum_for_each_13, _enum_for_each_12, _enum_for_each_11, _enum_for_each_10, \
    _enum_for_each_9,  _enum_for_each_8,  _enum_for_each_7,  _enum_for_each_6,  _enum_for_each_5,  \
    _enum_for_each_4,  _enum_for_each_3,  _enum_for_each_2,  _enum_for_each_1,  _enum_for_each_0)(arg, ##__VA_ARGS__))

#define _enum_strip_args_1(arg0) arg0
#define _enum_strip_args_2(arg0, arg1) arg0, arg1
#define _enum_make_args(...) (__VA_ARGS__)

#define _enum_elem_arity1_1(arg) arg,
#define _enum_elem_arity1( ...) _enum_expand(_enum_elem_arity1_1 __VA_ARGS__)
#define _enum_elem_arity2_1(arg0,arg1) arg0 = arg1,
#define _enum_elem_arity2( ...) _enum_expand(_enum_elem_arity2_1 __VA_ARGS__)

#define _enum_elem_select_arity_2(_0, _1, NAME,...) NAME
#define _enum_elem_select_arity_1(...) _enum_expand(_enum_elem_select_arity_2(__VA_ARGS__, _enum_elem_arity2,_enum_elem_arity1,_))
#define _enum_elem_select_arity(enum_type,...) _enum_expand(_enum_elem_select_arity_1 __VA_ARGS__)(__VA_ARGS__)

#define _enum_str_arity1_1(enum_type,arg) { enum_type::arg,#arg },
#define _enum_str_arity1(enum_type,...) _enum_expand(_enum_str_arity1_1 _enum_make_args( enum_type, _enum_expand(_enum_strip_args_1 __VA_ARGS__)))
#define _enum_str_arity2_1(enum_type,arg,value) { enum_type::arg,#arg },
#define _enum_str_arity2(enum_type, ...) _enum_expand(_enum_str_arity2_1 _enum_make_args( enum_type, _enum_expand(_enum_strip_args_2 __VA_ARGS__)))
#define _enum_str_select_arity_2(_0, _1, NAME,...) NAME
#define _enum_str_select_arity_1(...) _enum_expand(_enum_str_select_arity_2(__VA_ARGS__, _enum_str_arity2,_enum_str_arity1,_))
#define _enum_str_select_arity(enum_type,...) _enum_expand(_enum_str_select_arity_1 __VA_ARGS__)(enum_type,__VA_ARGS__)

#define error_code_enum(enum_type,...)  enum class enum_type {              \
    _enum_expand(_enum_for_each(_enum_elem_select_arity,enum_type, ##__VA_ARGS__))};  \
    namespace _ ## enum_type ## _detail { \
        template <typename> struct _ ## enum_type ## _error_code{ \
            static const std::map<enum_type, const char*> enum_type ## _map; \
        }; \
            template <typename T> \
            const std::map<enum_type, const char*> _ ## enum_type ## _error_code<T>::enum_type ## _map = { \
                _enum_expand(_enum_for_each(_enum_str_select_arity,enum_type,  ##__VA_ARGS__)) \
        }; \
    } \
    inline const char* get_error_code_name(const enum_type& value) { \
        return _ ## enum_type ## _detail::_ ## enum_type ## _error_code<enum_type>::enum_type ## _map.find(value)->second; \
    } 

error_code_enum(myenum,
    (one, 1),
    (two)
);

which produces the following code

enum class myenum { 
    one = 1,
    two,
};
namespace _myenum_detail {
    template <typename>
    struct _myenum_error_code {
        static const std::map<myenum, const char*> myenum_map;
    };
    template <typename T>
    const std::map<myenum, const char*> _myenum_error_code<T>::myenum_map = {
        { myenum::one, "one" }, 
        { myenum::two, "two" },
    };
}
inline const char* get_error_code_name(const myenum& value) { 
    return _myenum_detail::_myenum_error_code<myenum>::myenum_map.find(value)->second; 
}

Such a shame the hoops you have to jump though with the preprocessor to do this in one of the most used programming languages in the world...