138

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);
Roddy
  • 66,617
  • 42
  • 165
  • 277

24 Answers24

66

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.

Community
  • 1
  • 1
MSalters
  • 173,980
  • 10
  • 155
  • 350
  • You are right to refer to another question. People should take a look in the "related questions" before posting... – xtofl Oct 16 '08 at 11:31
  • 2
    @xtofl : The "related questions" shown here are totally different to the related questions listed when I posted the question! – Roddy Oct 16 '08 at 12:15
  • @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. – Roddy Oct 16 '08 at 12:17
  • @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.. – Johannes Schaub - litb Mar 05 '09 at 02:05
  • 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")... (http://www.boost.org/doc/libs/1_35_0/libs/assign/doc/index.html) – Johannes Schaub - litb Mar 05 '09 at 02:13
  • yeah - I could get this working but defining operator,(MyEnum, const char*) for that would be Evil++. Agree with boost::assign. – MSalters Mar 05 '09 at 15:23
  • +1 for the thought engaging explanation (about `map_init`) and a simpler solution – ϹοδεMεδιϲ May 23 '12 at 10:17
  • 1
    I don't think the `map_init` is needed in newer C++. Can just initialize the map using `std::map MyMap = {{eValue1, "A"},{eValue2, "B"},{eValue3, "C"}};` – Zitrax Jun 29 '17 at 14:11
  • @Zitrax: Agree, Alastair noted as much 2 years ago, in a comment on hos own answer. – MSalters Jun 29 '17 at 14:21
33

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;
}
Alastair
  • 4,475
  • 1
  • 26
  • 23
  • 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 Sep 08 '11 at 17:21
  • @User: Class data members are initialized in constructors, usually in the initializer list. – MSalters May 23 '12 at 12:58
  • 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. – jlcv Jan 26 '15 at 16:17
  • I tried using a template but then got and error: `error: template declaration of 'const boost::unordered::unordered_map enumToString'`. – jlcv Jan 26 '15 at 16:24
  • Not obvious what you want here - but possibly a C++11 template alias? e.g. `template using StringMap = std::unordered_map`. – Alastair Jan 27 '15 at 14:28
  • 4
    Actually this answer is largely obsolete with C++11. – Alastair Jan 27 '15 at 14:28
22

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;
}
jfs
  • 399,953
  • 195
  • 994
  • 1,670
  • While fastest, it isn't as easy as @MSalters. – kenny Dec 04 '08 at 11:42
  • 3
    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" – Martin Beckett Mar 05 '09 at 15:39
  • @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). – jfs Mar 05 '09 at 16:23
  • @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. – Martin Beckett Mar 05 '09 at 16:46
  • 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. – Alastair Nov 02 '12 at 15:40
  • @Alastair: that is why array, `unordered_map<>` are mentioned. You generate whatever is appropriate for a particular case. – jfs Nov 02 '12 at 17:10
  • Yeah, but it's not the size of the enum which is the determining factor here. I'm asking whether it is possible to write a `getStringFromEnum(MyEnum e)` function using this technique, regardless of the size of the enum...? – Alastair Nov 02 '12 at 19:35
  • @Alastair: yes, your own answer (with unordered_map) shows an example how a generated code could look like. – jfs Nov 02 '12 at 19:58
13

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)
Community
  • 1
  • 1
David Allan Finch
  • 1,414
  • 8
  • 20
  • 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) – Jonathan Oct 07 '16 at 15:07
  • https://stackoverflow.com/questions/70455489/strange-preprocessor-warnings Very similar, but a bit improved and using standard preprocesor... – Jan Dec 30 '21 at 13:07
11

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;
Debdatta Basu
  • 269
  • 3
  • 4
5

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;
}
Community
  • 1
  • 1
5

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.
jose.angel.jimenez
  • 2,127
  • 23
  • 17
5

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";
}
uIM7AI9S
  • 353
  • 4
  • 13
  • 6
    Are you suggesting using strings and not using enums at all ? That doesn't really solve the problem. – Roddy Mar 09 '20 at 12:21
4

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.

Constantin
  • 27,478
  • 10
  • 60
  • 79
2

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

  1. only write each value of the enum once, so there are no double lists to maintain

  2. don't keep the enum values in a separate file that is later #included, so I can write it wherever I want

  3. 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)

  4. 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.

muqker
  • 31
  • 2
2

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.

Richard Harrison
  • 19,247
  • 4
  • 40
  • 67
2

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!

OlivierB
  • 313
  • 3
  • 14
2

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

Smi
  • 13,850
  • 9
  • 56
  • 64
Valentin H
  • 7,240
  • 12
  • 61
  • 111
2

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"
}
Daniel
  • 403
  • 2
  • 15
1

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)};
jamk
  • 836
  • 1
  • 10
  • 24
1

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.

moogs
  • 8,122
  • 8
  • 44
  • 60
  • Indeed a very non-DRY-SPOT solution. – xtofl Oct 16 '08 at 11:28
  • 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) – moogs Oct 16 '08 at 11:35
1
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

Madwyn
  • 409
  • 3
  • 13
1
#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. – Adrian W Jun 04 '21 at 08:34
  • 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? – mattlangford Jun 04 '21 at 15:25
  • 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. – Jerzy Jamroz Jun 09 '21 at 18:37
1

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."

Jan
  • 2,178
  • 3
  • 14
  • 26
1

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.

shawn
  • 21
  • 3
0

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

  • 1
    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? – Alastair Dec 04 '08 at 11:28
0

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...

rmawatson
  • 1,909
  • 12
  • 20
0

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/

-1

Not efficient, but did the job. It can handle unlimited enum values.

For example

DEFINE_ENUM(Fruit, int32_t, PEAR = -100, APPLE, BANANA, ORANGE = 100, MANGO, STRAWBERRY = 75, WATERMELON = 100)

It generates

enum class Fruit : int32_t {
    PEAR = -100, APPLE, BANANA, ORANGE = 100, MANGO, STRAWBERRY = 75, WATERMELON = 100
};

[[nodiscard]] static inline std::string EnumFruitToString(const Fruit& value, bool includeEnumName = false) {
    using underlying = typename std::underlying_type<Fruit>::type;
    std::string values = "PEAR = -100, APPLE, BANANA, ORANGE = 100, MANGO, STRAWBERRY = 75, WATERMELON = 100";
    underlying integralValue = 0;
    std::vector<std::string> valuePairs = StringExtensions::Split(values, ", ");
    std::map<underlying, std::string> dictionary{};

    for (std::string str : valuePairs) {
        std::vector<std::string> keyAndValue = StringExtensions::Split(StringExtensions::Remove((includeEnumName ? "Fruit"s + "::"s : ""s) + str, " "), "=");

        if (keyAndValue.size() == 1) {
            dictionary.emplace(integralValue, keyAndValue[0]);
            integralValue += 1;
        } else {
            dictionary.emplace(StringExtensions::ToNumber<underlying>(keyAndValue[1]), keyAndValue[0]);
            integralValue = StringExtensions::ToNumber<underlying>(keyAndValue[1]) + 1;
        }
    }

    return StringExtensions::Trim(dictionary.at(static_cast<underlying>(value)));
}

Another example.

DEFINE_DEFAULT_ENUM(Animal, Dog, Cat, Monkey = 50, Fish, Human = 100, Duck, __COUNT)

It generates

enum class Animal : int {
    Dog, 
    Cat, 
    Monkey = 50, 
    Fish, 
    Human = 100, 
    Duck, 
    __COUNT
};

[[nodiscard]] static inline std::string EnumAnimalToString(const Animal& value, bool includeEnumName = false) {
    using underlying = typename std::underlying_type<Animal>::type;
    std::string values = "Dog, Cat, Monkey = 50, Fish, Human = 100, Duck, __COUNT";
    underlying integralValue = 0;
    std::vector<std::string> valuePairs = StringExtensions::Split(values, ", ");
    std::map<underlying, std::string> dictionary{};

    for (std::string str : valuePairs) {
        std::vector<std::string> keyAndValue = StringExtensions::Split(StringExtensions::Remove((includeEnumName ? "Animal"s + "::"s : ""s) + str, " "), "=");

        if (keyAndValue.size() == 1) {
            dictionary.emplace(integralValue, keyAndValue[0]);
            integralValue += 1;
        } else {
            dictionary.emplace(StringExtensions::ToNumber<underlying>(keyAndValue[1]), keyAndValue[0]);
            integralValue = StringExtensions::ToNumber<underlying>(keyAndValue[1]) + 1;
        }
    }

    return StringExtensions::Trim(dictionary.at(static_cast<underlying>(value)));
}

C++ Code
You can test/run in https://godbolt.org/z/PzMqfGoae

#include <iostream>
#include <string>
#include <iostream>
#include <type_traits>
#include <vector>
#include <map>
#include <sstream>

#define SPACE_STRING (" "s)
#define EQUAL_SIGN_STRING ("="s)
#define ARGUMENT_SEPARATOR_STRING (", "s)
#define NAMESPACE_SEPARATOR_STRING ("::"s)
#define EMPTY_STRING (""s)
using namespace std::string_literals;

#define TOSTR(value) std::to_string(value)


class StringExtensions
{
public:
    static std::string Remove(const std::string& data, const std::string& toRemove)
    {
        std::string result = data;
        size_t pos = 0;
        while ((pos = result.find(toRemove, pos)) != std::string::npos)
        {
            result.erase(pos, toRemove.length());
        }
        return result;
    }

    [[nodiscard]] static std::vector<std::string> Split(const std::string& data, const std::string& toFind)
    {
        std::vector<std::string> v;
        if (data.empty() || toFind.empty())
        {
            v.push_back(data);
            return v;
        }
        size_t ini = 0;
        size_t pos;
        while ((pos = data.find(toFind, ini)) != std::string::npos)
        {
            std::string s = data.substr(ini, pos - ini);
            if (!s.empty())
            {
                v.push_back(s);
            }
            ini = pos + toFind.length();
        }
        if (ini < data.length())
        {
            v.push_back(data.substr(ini));
        }
        return v;
    }

    [[nodiscard]] static std::string TrimStart(const std::string& data)
    {
        std::string s = data;
        s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch)
            { return !std::isspace(ch); }));
        return s;
    }

    [[nodiscard]] static std::string TrimEnd(const std::string& data)
    {
        std::string s = data;
        s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch)
            { return !std::isspace(ch); })
            .base(),
            s.end());
        return s;
    }

    [[nodiscard]] static std::string Trim(const std::string& data)
    {
        return TrimEnd(TrimStart(data));
    }

    template <typename T>
    [[nodiscard]]  static T ToNumber(const std::string& str) requires std::is_integral_v<T> {
        std::stringstream ss(str);
        T result;
        ss >> result;
        return result;
    }
private:
};

class EnumExtensions {
public:
    template <typename T>
    [[nodiscard]] static std::string ToEnumValueString(const T& enumValue)
        requires std::is_enum_v<T>
    {
        using UnderlyingType = typename std::underlying_type<T>::type;
        UnderlyingType integralValue = static_cast<UnderlyingType>(enumValue);
        return TOSTR(integralValue);
    }
};

#define DEFINE_ENUM(Name,UnderlyingT,...) \
    enum class Name : UnderlyingT { __VA_ARGS__ }; \
    [[nodiscard]] static inline std::string Enum##Name##ToString(const Name &value, bool includeEnumName=false) { \
        using underlying = typename std::underlying_type<Name>::type; \
        std::string values = #__VA_ARGS__; \
        underlying integralValue = 0; \
        std::vector<std::string> valuePairs = StringExtensions::Split(values, ARGUMENT_SEPARATOR_STRING); \
        std::map<underlying, std::string> dictionary{}; \
        for (std::string str : valuePairs) \
        { \
            std::vector<std::string> keyAndValue = StringExtensions::Split(StringExtensions::Remove((includeEnumName? #Name##s + NAMESPACE_SEPARATOR_STRING : EMPTY_STRING) + str, SPACE_STRING), EQUAL_SIGN_STRING); \
            if (keyAndValue.size() == 1) \
            { \
                dictionary.emplace(integralValue, keyAndValue[0]); \
                integralValue += 1; \
            } \
            else \
            { \
                dictionary.emplace(StringExtensions::ToNumber<underlying>(keyAndValue[1]), keyAndValue[0]); \
                integralValue = StringExtensions::ToNumber<underlying>(keyAndValue[1]) + 1; \
            } \
        } \
        return StringExtensions::Trim(dictionary.at(static_cast<underlying>(value))); \
    } \

#define DEFINE_DEFAULT_ENUM(Name,...) DEFINE_ENUM(Name,int,__VA_ARGS__)

namespace MyNamespace
{
    DEFINE_ENUM(Fruit, int32_t, PEAR = -100, APPLE, BANANA, ORANGE = 100, MANGO, STRAWBERRY = 75, WATERMELON = 100)
    DEFINE_DEFAULT_ENUM(Animal, Dog, Cat, Monkey = 50, Fish, Human = 100, Duck, __COUNT)
}



#define PRINT_ENUM(enumName,value,underlyingT,includeEnumName) std::cout << Enum##enumName##ToString(value, includeEnumName) << " --- " << EnumExtensions::ToEnumValueString(value) << std::endl

int main() {
    using namespace MyNamespace;

    //std::cout << EnumFruitToString(Fruit::PEAR, true) << " --- " << EnumExtensions::ToEnumValueString(Fruit::PEAR) << std::endl
    PRINT_ENUM(Fruit,Fruit::PEAR, int32_t, true);
    PRINT_ENUM(Fruit, Fruit::APPLE, int32_t, true);
    PRINT_ENUM(Fruit, Fruit::BANANA, int32_t, true);
    PRINT_ENUM(Fruit, Fruit::ORANGE, int32_t, true);
    PRINT_ENUM(Fruit, Fruit::MANGO, int32_t, true);
    PRINT_ENUM(Fruit, Fruit::STRAWBERRY, int32_t, true);
    PRINT_ENUM(Fruit, Fruit::WATERMELON, int32_t, true);

    //std::cout << EnumAnimalToString(Animal::Dog, true) << " --- " << EnumExtensions::ToEnumValueString(Animal::Dog) << std::endl
    PRINT_ENUM(Animal, Animal::Dog, int, true);
    PRINT_ENUM(Animal, Animal::Cat, int, true);
    PRINT_ENUM(Animal, Animal::Monkey, int, false);
    PRINT_ENUM(Animal, Animal::Fish, int, false);
    PRINT_ENUM(Animal, Animal::Human, int, false);
    PRINT_ENUM(Animal, Animal::Duck, int, false);
    PRINT_ENUM(Animal, Animal::__COUNT, int, false);
    
    std::cin.get();
    return 0;
}

Output

Fruit::PEAR --- -100
Fruit::APPLE --- -99
Fruit::BANANA --- -98
Fruit::ORANGE --- 100
Fruit::MANGO --- 101
Fruit::STRAWBERRY --- 75
Fruit::ORANGE --- 100
Animal::Dog --- 0
Animal::Cat --- 1
Monkey --- 50
Fish --- 51
Human --- 100
Duck --- 101
__COUNT --- 102

VisualC++

vc++

ClangX86-64 - Godbolt.org

godbolt



New update. Alternative. This syntax is a bit ugly. But generates more efficient code.

More complete/efficient code. But can handle up to 170 enum values, if you need more than 170 feel free to modify this code. This code generates extensions methods for manipulate enum.

  • ToString - returns the name of enum. If another enum has the same numeric value, the returned name is the first enum with value.
  • ToIntegralString - returns the numeric value to string.
  • ToIntegral - returns the numeric value of enum. The type is the underlying type of enum.
  • Parse - convert the string to enum value, it can throws an exception.
  • Parse - convert the numeric value to enum value, the numeric value type is the same unlying type of enum, it can throws an exception.
  • GetValues - return a vector with all enun values.

Syntax EZNUM_ENUM(EnumName,Var1,Oper1,Val1,Var2,Oper2,Val2,......,Var170,Oper170,Val170)
EZNUM_ENUM_UT(EnumName,UType,Var1,Oper1,Val1,Var2,Oper2,Val2,......,Var170,Oper170,Val170)

  • EnumName - Name of enum class.
  • UType - Name of underlying type.
  • VarN - Name of enum value.
  • OperN - Assignment operator can be EQ for equals or _ for no operator.
  • ValN - Underlying type value. If OperN is _ this value is ignored.

this macro needs groups of 3 - Var,Oper,Val examples:
X,_,_ it generates X
Y,EQ,2 | it generates Y = 2
Z,_,2 | it generates Z

For example

EZNUM_ENUM(MobaGame,
        Dota2, EQ, 100,
        LeagueOfLegends, EQ, 101,
        HeroesOfTheStorm, EQ, 102,
        Smite, EQ, 103,
        Vainglory, EQ, 104,
        ArenaOfValor, EQ, 105,
        Paragon, EQ, 106,
        HeroesOfNewerth, EQ, -100)

It generates

enum class MobaGame : int
{
    Dota2 = 100,
    LeagueOfLegends = 101,
    HeroesOfTheStorm = 102,
    Smite = 103,
    Vainglory = 104,
    ArenaOfValor = 105,
    Paragon = 106,
    HeroesOfNewerth = -100,
};
class MobaGameEnumExtensions
{
public:
    [[nodiscard]] static String ToIntegralString(const MobaGame &value)
    {
        using namespace Extensions;
        return EnumExtensions::ToIntegralString(value);
    }
    [[nodiscard]] static int ToIntegral(const MobaGame &value)
    {
        using namespace Extensions;
        return EnumExtensions::ToIntegral<MobaGame>(value);
    }
    [[nodiscard]] static std::string ToString(const MobaGame &value, bool includeEnumName = false)
    {
        using namespace Extensions;
        static const std::map<MobaGame, String> values = {
            {MobaGame::Dota2, "Dota2"},
            {MobaGame::LeagueOfLegends, "LeagueOfLegends"},
            {MobaGame::HeroesOfTheStorm, "HeroesOfTheStorm"},
            {MobaGame::Smite, "Smite"},
            {MobaGame::Vainglory, "Vainglory"},
            {MobaGame::ArenaOfValor, "ArenaOfValor"},
            {MobaGame::Paragon, "Paragon"},
            {MobaGame::HeroesOfNewerth, "HeroesOfNewerth"},
        };
        return includeEnumName ? "MobaGame::"s + values.at(value) : values.at(value);
    }
    [[nodiscard]] static MobaGame Parse(const int &value)
    {
        using namespace Exceptions;
        static const std::map<int, MobaGame> values = {
            {static_cast<int>(MobaGame::Dota2), MobaGame::Dota2},
            {static_cast<int>(MobaGame::LeagueOfLegends), MobaGame::LeagueOfLegends},
            {static_cast<int>(MobaGame::HeroesOfTheStorm), MobaGame::HeroesOfTheStorm},
            {static_cast<int>(MobaGame::Smite), MobaGame::Smite},
            {static_cast<int>(MobaGame::Vainglory), MobaGame::Vainglory},
            {static_cast<int>(MobaGame::ArenaOfValor), MobaGame::ArenaOfValor},
            {static_cast<int>(MobaGame::Paragon), MobaGame::Paragon},
            {static_cast<int>(MobaGame::HeroesOfNewerth), MobaGame::HeroesOfNewerth},
        };
        try
        {
            return values.at(value);
        }
        catch (...)
        {
            throw ParseException("MobaGame::Parse"s);
        }
    }
    [[nodiscard]] static MobaGame Parse(const String &value)
    {
        using namespace Exceptions;
        using namespace Extensions;
        static const std::map<String, MobaGame> values = {
            {"Dota2", MobaGame::Dota2},
            {"LeagueOfLegends", MobaGame::LeagueOfLegends},
            {"HeroesOfTheStorm", MobaGame::HeroesOfTheStorm},
            {"Smite", MobaGame::Smite},
            {"Vainglory", MobaGame::Vainglory},
            {"ArenaOfValor", MobaGame::ArenaOfValor},
            {"Paragon", MobaGame::Paragon},
            {"HeroesOfNewerth", MobaGame::HeroesOfNewerth},
            {"MobaGame::Dota2"s, MobaGame::Dota2},
            {"MobaGame::LeagueOfLegends"s, MobaGame::LeagueOfLegends},
            {"MobaGame::HeroesOfTheStorm"s, MobaGame::HeroesOfTheStorm},
            {"MobaGame::Smite"s, MobaGame::Smite},
            {"MobaGame::Vainglory"s, MobaGame::Vainglory},
            {"MobaGame::ArenaOfValor"s, MobaGame::ArenaOfValor},
            {"MobaGame::Paragon"s, MobaGame::Paragon},
            {"MobaGame::HeroesOfNewerth"s, MobaGame::HeroesOfNewerth},
            {IntegralExtensions::ToString(static_cast<int>(MobaGame::Dota2)), MobaGame::Dota2},
            {IntegralExtensions::ToString(static_cast<int>(MobaGame::LeagueOfLegends)), MobaGame::LeagueOfLegends},
            {IntegralExtensions::ToString(static_cast<int>(MobaGame::HeroesOfTheStorm)), MobaGame::HeroesOfTheStorm},
            {IntegralExtensions::ToString(static_cast<int>(MobaGame::Smite)), MobaGame::Smite},
            {IntegralExtensions::ToString(static_cast<int>(MobaGame::Vainglory)), MobaGame::Vainglory},
            {IntegralExtensions::ToString(static_cast<int>(MobaGame::ArenaOfValor)), MobaGame::ArenaOfValor},
            {IntegralExtensions::ToString(static_cast<int>(MobaGame::Paragon)), MobaGame::Paragon},
            {IntegralExtensions::ToString(static_cast<int>(MobaGame::HeroesOfNewerth)), MobaGame::HeroesOfNewerth},
        };
        try
        {
            return values.at(value);
        }
        catch (...)
        {
            throw ParseException("MobaGame::Parse"s);
        }
    }
    [[nodiscard]] static std::vector<MobaGame> GetValues()
    {
        return {
            MobaGame::Dota2,
            MobaGame::LeagueOfLegends,
            MobaGame::HeroesOfTheStorm,
            MobaGame::Smite,
            MobaGame::Vainglory,
            MobaGame::ArenaOfValor,
            MobaGame::Paragon,
            MobaGame::HeroesOfNewerth,
        };
    }
};
std::ostream &operator<<(std::ostream &os, const MobaGame &value)
{
    os << MobaGameEnumExtensions::ToString(value);
    return os;
}

Using this

EZNUM_ENUM_UT(MobaGame,int32_t
        Dota2, EQ, 100,
        LeagueOfLegends, EQ, 101,
        HeroesOfTheStorm, EQ, 102,
        Smite, EQ, 103,
        Vainglory, EQ, 104,
        ArenaOfValor, EQ, 105,
        Paragon, _, _,
        HeroesOfNewerth, _, _)

` It generates

enum class MobaGame : int32_t
{
    Dota2 = 100,
    LeagueOfLegends = 101,
    HeroesOfTheStorm = 102,
    Smite = 103,
    Vainglory = 104,
    ArenaOfValor = 105,
    Paragon,
    HeroesOfNewerth,
};
class MobaGameEnumExtensions
{
public:
    [[nodiscard]] static String ToIntegralString(const MobaGame &value)
    {
        using namespace Extensions;
        return EnumExtensions::ToIntegralString(value);
    }
    [[nodiscard]] static int32_t ToIntegral(const MobaGame &value)
    {
        using namespace Extensions;
        return EnumExtensions::ToIntegral<MobaGame>(value);
    }
    [[nodiscard]] static std::string ToString(const MobaGame &value, bool includeEnumName = false)
    {
        using namespace Extensions;
        static const std::map<MobaGame, String> values = {
            {MobaGame::Dota2, "Dota2"},
            {MobaGame::LeagueOfLegends, "LeagueOfLegends"},
            {MobaGame::HeroesOfTheStorm, "HeroesOfTheStorm"},
            {MobaGame::Smite, "Smite"},
            {MobaGame::Vainglory, "Vainglory"},
            {MobaGame::ArenaOfValor, "ArenaOfValor"},
            {MobaGame::Paragon, "Paragon"},
            {MobaGame::HeroesOfNewerth, "HeroesOfNewerth"},
        };
        return includeEnumName ? "MobaGame::"s + values.at(value) : values.at(value);
    }
    [[nodiscard]] static MobaGame Parse(const int32_t &value)
    {
        using namespace Exceptions;
        static const std::map<int32_t, MobaGame> values = {
            {static_cast<int32_t>(MobaGame::Dota2), MobaGame::Dota2},
            {static_cast<int32_t>(MobaGame::LeagueOfLegends), MobaGame::LeagueOfLegends},
            {static_cast<int32_t>(MobaGame::HeroesOfTheStorm), MobaGame::HeroesOfTheStorm},
            {static_cast<int32_t>(MobaGame::Smite), MobaGame::Smite},
            {static_cast<int32_t>(MobaGame::Vainglory), MobaGame::Vainglory},
            {static_cast<int32_t>(MobaGame::ArenaOfValor), MobaGame::ArenaOfValor},
            {static_cast<int32_t>(MobaGame::Paragon), MobaGame::Paragon},
            {static_cast<int32_t>(MobaGame::HeroesOfNewerth), MobaGame::HeroesOfNewerth},
        };
        try
        {
            return values.at(value);
        }
        catch (...)
        {
            throw ParseException("MobaGame::Parse"s);
        }
    }
    [[nodiscard]] static MobaGame Parse(const String &value)
    {
        using namespace Exceptions;
        using namespace Extensions;
        static const std::map<String, MobaGame> values = {
            {"Dota2", MobaGame::Dota2},
            {"LeagueOfLegends", MobaGame::LeagueOfLegends},
            {"HeroesOfTheStorm", MobaGame::HeroesOfTheStorm},
            {"Smite", MobaGame::Smite},
            {"Vainglory", MobaGame::Vainglory},
            {"ArenaOfValor", MobaGame::ArenaOfValor},
            {"Paragon", MobaGame::Paragon},
            {"HeroesOfNewerth", MobaGame::HeroesOfNewerth},
            {"MobaGame::Dota2"s, MobaGame::Dota2},
            {"MobaGame::LeagueOfLegends"s, MobaGame::LeagueOfLegends},
            {"MobaGame::HeroesOfTheStorm"s, MobaGame::HeroesOfTheStorm},
            {"MobaGame::Smite"s, MobaGame::Smite},
            {"MobaGame::Vainglory"s, MobaGame::Vainglory},
            {"MobaGame::ArenaOfValor"s, MobaGame::ArenaOfValor},
            {"MobaGame::Paragon"s, MobaGame::Paragon},
            {"MobaGame::HeroesOfNewerth"s, MobaGame::HeroesOfNewerth},
            {IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::Dota2)), MobaGame::Dota2},
            {IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::LeagueOfLegends)), MobaGame::LeagueOfLegends},
            {IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::HeroesOfTheStorm)), MobaGame::HeroesOfTheStorm},
            {IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::Smite)), MobaGame::Smite},
            {IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::Vainglory)), MobaGame::Vainglory},
            {IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::ArenaOfValor)), MobaGame::ArenaOfValor},
            {IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::Paragon)), MobaGame::Paragon},
            {IntegralExtensions::ToString(static_cast<int32_t>(MobaGame::HeroesOfNewerth)), MobaGame::HeroesOfNewerth},
        };
        try
        {
            return values.at(value);
        }
        catch (...)
        {
            throw ParseException("MobaGame::Parse"s);
        }
    }
    [[nodiscard]] static std::vector<MobaGame> GetValues()
    {
        return {
            MobaGame::Dota2,
            MobaGame::LeagueOfLegends,
            MobaGame::HeroesOfTheStorm,
            MobaGame::Smite,
            MobaGame::Vainglory,
            MobaGame::ArenaOfValor,
            MobaGame::Paragon,
            MobaGame::HeroesOfNewerth,
        };
    }
};
std::ostream &operator<<(std::ostream &os, const MobaGame &value)
{
    os << MobaGameEnumExtensions::ToString(value);
    return os;
}

Full code

You can test/run/any this code from https://github.com/JomaStackOverflowAnswers/EnumExtensionsCpp
https://godbolt.org/z/vx3YYTafh
https://replit.com/@JomaCorpFX/EnumExtensions

It can't be pasted here. Too long code.

Here. The main program part

namespace Enums
{
    EZNUM_ENUM_UT(Variables, int,
        X, _, _,
        Y, EQ, 25,
        Z, EQ, 75)

        EZNUM_ENUM_UT(Fruit, int32_t,
            PEAR, EQ, -100,
            APPLE, _, _,
            BANANA, _, _,
            ORANGE, EQ, 100,
            MANGO, _, _,
            STRAWBERRY, EQ, 75,
            WATERMELON, EQ, 100)

        EZNUM_ENUM(Animal,
            Dog, _, _,
            Cat, _, _,
            Monkey, EQ, 50,
            Fish, _, _,
            Human, EQ, 100,
            Duck, _, _,
            __COUNT, _, _)

        EZNUM_ENUM_UT(MathVars32, int32_t,
            X, _, _,
            Y, _, _,
            Z, EQ, 75)

        EZNUM_ENUM_UT(MathVars64, int64_t,
            X, _, _,
            Y, _, _,
            Z, EQ, 75)

        EZNUM_ENUM(Vowels,
            A, EQ, 75,
            E, _, _,
            I, EQ, 1500,
            O, EQ, -5,
            U, _, _)

        EZNUM_ENUM(MobaGame,
            Dota2, EQ, 100,
            LeagueOfLegends, EQ, 101,
            HeroesOfTheStorm, EQ, 102,
            Smite, EQ, 103,
            Vainglory, EQ, 104,
            ArenaOfValor, EQ, 105,
            Paragon, EQ, 106,
            HeroesOfNewerth, EQ, -100)
}



#define PRINT_VALUES(Name) std::cout << "EnumName: "s + #Name << std::endl; \
std::cout << StringExtensions::PadRight(EMPTY_STRING , 21 + 128, '_') << std::endl; \
for (Name element : Name##EnumExtensions::GetValues()) \
{ \
    std::cout << StringExtensions::PadRight(Name##EnumExtensions::ToString(element), 16) << " | " << \
        StringExtensions::PadRight(Name##EnumExtensions::ToString(element, true),32) << " | " << \
        StringExtensions::PadRight(Name##EnumExtensions::ToIntegralString(element),8) << " | " << \
        StringExtensions::PadRight(IntegralExtensions::ToString(Name##EnumExtensions::ToIntegral(element)),8) << " | " << \
        StringExtensions::PadRight(Name##EnumExtensions::ToString(Name##EnumExtensions::Parse(Name##EnumExtensions::ToString(element))),16) << " | " << \
        StringExtensions::PadRight(Name##EnumExtensions::ToString(Name##EnumExtensions::Parse(Name##EnumExtensions::ToString(element, true))),16) << " | " << \
        StringExtensions::PadRight(Name##EnumExtensions::ToString(Name##EnumExtensions::Parse(Name##EnumExtensions::ToIntegralString(element))),16) << " | " << \
        StringExtensions::PadRight(Name##EnumExtensions::ToString(Name##EnumExtensions::Parse(Name##EnumExtensions::ToIntegral(element))),16) << std::endl; \
} \
std::cout<< std::endl;




int main() {

    using namespace Enums;
    using namespace Extensions;
    PRINT_VALUES(Variables)
    PRINT_VALUES(Fruit)
    PRINT_VALUES(Animal)
    PRINT_VALUES(MathVars32)
    PRINT_VALUES(MathVars64)
    PRINT_VALUES(Vowels)
    PRINT_VALUES(MobaGame)
/*  std::cout << "EnumName: "s + "MobaGame" << std::endl;
    std::cout << StringExtensions::PadRight(EMPTY_STRING, 21 + 128, '_') << std::endl;
    for (MobaGame element : MobaGameEnumExtensions::GetValues())
    {
        std::cout << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(element), 16) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(element, true), 32) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToIntegralString(element), 8) << " | " << StringExtensions::PadRight(IntegralExtensions::ToString(MobaGameEnumExtensions::ToIntegral(element)), 8) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(MobaGameEnumExtensions::Parse(MobaGameEnumExtensions::ToString(element))), 16) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(MobaGameEnumExtensions::Parse(MobaGameEnumExtensions::ToString(element, true))), 16) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(MobaGameEnumExtensions::Parse(MobaGameEnumExtensions::ToIntegralString(element))), 16) << " | " << StringExtensions::PadRight(MobaGameEnumExtensions::ToString(MobaGameEnumExtensions::Parse(MobaGameEnumExtensions::ToIntegral(element))), 16) << std::endl;
    }
    std::cout << std::endl;*/
    std::cin.get();
    return 0;
}

Output

VisualC++

vc++

Godbolt.org

godbolt

Joma
  • 3,520
  • 1
  • 29
  • 32