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

C++StringEnumsScripting

C++ Problem Overview


Suppose we have some named enums:

enum MyEnum {
      FOO,
      BAR = 0x50
};

What I googled for is a script (any language) that scans all the headers in my project and generates a header with one function per enum.

char* enum_to_string(MyEnum t);

And a implementation with something like this:

char* enum_to_string(MyEnum t){
      switch(t){
         case FOO:
            return "FOO";
         case BAR:
            return "BAR";
         default:
            return "INVALID ENUM";
      }
 }

The gotcha is really with typedefed enums, and unnamed C style enums. Does anybody know something for this?

EDIT: The solution should not modify my source, except for the generated functions. The enums are in an API, so using the solutions proposed until now is just not an option.

C++ Solutions


Solution 1 - C++

X-macros are the best solution. Example:

#include <iostream>

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

std::ostream& operator<<(std::ostream& os, enum Colours c)
{
    if (c >= ColoursCount || c < 0) return os << "???";
    return os << colours_str[c];
}

int main()
{
    std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}

colours.def:

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

However, I usually prefer the following method, so that it's possible to tweak the string a bit.

#define X(a, b) a,
#define X(a, b) b,

X(Red, "red")
X(Green, "green")
// etc.

Solution 2 - C++

You may want to check out GCCXML.

Running GCCXML on your sample code produces:

<GCC_XML>
  <Namespace id="_1" name="::" members="_3 " mangled="_Z2::"/>
  <Namespace id="_2" name="std" context="_1" members="" mangled="_Z3std"/>
  <Enumeration id="_3" name="MyEnum" context="_1" location="f0:1" file="f0" line="1">
    <EnumValue name="FOO" init="0"/>
    <EnumValue name="BAR" init="80"/>
  </Enumeration>
  <File id="f0" name="my_enum.h"/>
</GCC_XML>

You could use any language you prefer to pull out the Enumeration and EnumValue tags and generate your desired code.

Solution 3 - C++

@hydroo: Without the extra file:

#define SOME_ENUM(DO) \
    DO(Foo) \
    DO(Bar) \
    DO(Baz)

#define MAKE_ENUM(VAR) VAR,
enum MetaSyntacticVariable{
    SOME_ENUM(MAKE_ENUM)
};

#define MAKE_STRINGS(VAR) #VAR,
const char* const MetaSyntacticVariableNames[] = {
    SOME_ENUM(MAKE_STRINGS)
};

Solution 4 - C++

What I tend to do is create a C array with the names in the same order and position as the enum values.

eg.

enum colours { red, green, blue };
const char *colour_names[] = { "red", "green", "blue" };

then you can use the array in places where you want a human-readable value, eg

colours mycolour = red;
cout << "the colour is" << colour_names[mycolour];

You could experiment a little with the stringizing operator (see # in your preprocessor reference) that will do what you want, in some circumstances- eg:

#define printword(XX) cout << #XX;
printword(red);

will print "red" to stdout. Unfortunately it won't work for a variable (as you'll get the variable name printed out)

Solution 5 - C++

I have an incredibly simple to use macro that does this in a completely DRY fashion. It involves variadic macros and some simple parsing magic. Here goes:

#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 this in your code, simply do:

AWESOME_MAKE_ENUM(Animal,
    DOG,
    CAT,
    HORSE
);

Solution 6 - C++

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

QNetworkReply::NetworkError error;

error = fetchStuff();

if (error != QNetworkReply::NoError) {

    QString errorValue;

    QMetaObject meta = QNetworkReply::staticMetaObject;

    for (int i=0; i < meta.enumeratorCount(); ++i) {

        QMetaEnum m = meta.enumerator(i);

        if (m.name() == QLatin1String("NetworkError")) {

            errorValue = QLatin1String(m.valueToKey(error));

            break;

        }

    }

    QMessageBox box(QMessageBox::Information, "Failed to fetch",

                "Fetching stuff failed with error '%1`").arg(errorValue),

                QMessageBox::Ok);

    box.exec();

    return 1;

}

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

Source

Solution 7 - C++

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

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

struct IdAndName
{
   int          id;
   const char * name;
   bool operator<(const IdAndName &rhs) const { return id < rhs.id; }
};
#define ID_AND_NAME(x) { x, #x }

const char * IdToName(int id, IdAndName *table_begin, IdAndName *table_end)
{
   if ((table_end - table_begin) > 1 && table_begin[0].id > table_begin[1].id)
      std::stable_sort(table_begin, table_end);

   IdAndName searchee = { id, NULL };
   IdAndName *p = std::lower_bound(table_begin, table_end, searchee);
   return (p == table_end || p->id != id) ? NULL : p->name;
}

template<int N>
const char * IdToName(int id, IdAndName (&table)[N])
{
   return IdToName(id, &table[0], &table[N]);
}

An example of how you'd use it:

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

const char * error_name = IdToName(GetLastError(), WindowsErrorTable);

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

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

#define ID_AND_NAME(x) case x: return #x

const char * WindowsErrorToName(int id)
{
    switch(id)
    {
        ID_AND_NAME(ERROR_INVALID_FUNCTION);
        ID_AND_NAME(ERROR_FILE_NOT_FOUND);
        ID_AND_NAME(ERROR_PATH_NOT_FOUND);
        ID_AND_NAME(ERROR_TOO_MANY_OPEN_FILES);
        ID_AND_NAME(ERROR_ACCESS_DENIED);
        ID_AND_NAME(ERROR_INVALID_HANDLE);
        ID_AND_NAME(ERROR_ARENA_TRASHED);
        ID_AND_NAME(ERROR_NOT_ENOUGH_MEMORY);
        ID_AND_NAME(ERROR_INVALID_BLOCK);
        ID_AND_NAME(ERROR_BAD_ENVIRONMENT);
        ID_AND_NAME(ERROR_BAD_FORMAT);
        ID_AND_NAME(ERROR_INVALID_ACCESS);
        ID_AND_NAME(ERROR_INVALID_DATA);
        ID_AND_NAME(ERROR_INVALID_DRIVE);
        ID_AND_NAME(ERROR_CURRENT_DIRECTORY);
        ID_AND_NAME(ERROR_NOT_SAME_DEVICE);
        ID_AND_NAME(ERROR_NO_MORE_FILES);
        default: return NULL;
    }
}

Solution 8 - C++

This can be done in C++11

#include <map>
enum MyEnum { AA, BB, CC, DD };

static std::map< MyEnum, const char * > info = {
   {AA, "This is an apple"},
   {BB, "This is a book"},
   {CC, "This is a coffee"},
   {DD, "This is a door"}
};

void main()
{
    std::cout << info[AA] << endl
              << info[BB] << endl
              << info[CC] << endl
              << info[DD] << endl;
}

Solution 9 - C++

#define stringify( name ) # name

enum MyEnum {
    ENUMVAL1
};
...stuff...

stringify(EnumName::ENUMVAL1);  // Returns MyEnum::ENUMVAL1

Further discussion on this method

Preprocessor directive tricks for newcomers

Solution 10 - C++

Interesting to see the number of ways. here's one i used a long time ago:

in file myenummap.h:

#include <map>
#include <string>
enum test{ one, two, three, five=5, six, seven };
struct mymap : std::map<unsigned int, std::string>
{
  mymap()
  {
    this->operator[]( one ) = "ONE";
    this->operator[]( two ) = "TWO";
    this->operator[]( three ) = "THREE";
    this->operator[]( five ) = "FIVE";
    this->operator[]( six ) = "SIX";
    this->operator[]( seven ) = "SEVEN";
  };
  ~mymap(){};
};

in main.cpp

#include "myenummap.h"

...
mymap nummap;
std::cout<< nummap[ one ] << std::endl;

Its not const, but its convenient.

Here's another way that uses C++11 features. This is const, doesn't inherit an STL container and is a little tidier:

#include <vector>
#include <string>
#include <algorithm>
#include <iostream>

//These stay together and must be modified together
enum test{ one, two, three, five=5, six, seven };
std::string enum_to_str(test const& e)
{
    typedef std::pair<int,std::string> mapping;
    auto m = [](test const& e,std::string const& s){return mapping(static_cast<int>(e),s);}; 
    std::vector<mapping> const nummap = 
	{ 
		m(one,"one"), 
		m(two,"two"), 
		m(three,"three"),
		m(five,"five"),
		m(six,"six"),
		m(seven,"seven"),
	};
    for(auto i  : nummap)
	{
		if(i.first==static_cast<int>(e))
		{
			return i.second;
		}
	}
	return "";
}

int main()
{
// 	std::cout<< enum_to_str( 46 ) << std::endl; //compilation will fail
	std::cout<< "Invalid enum to string : [" << enum_to_str( test(46) ) << "]"<<std::endl; //returns an empty string
 	std::cout<< "Enumval five to string : ["<< enum_to_str( five ) << "] "<< std::endl; //works
    return 0;
}

Solution 11 - C++

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

So you'd have an foobar.h defining just

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

and you would include it like this:

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

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

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

Solution 12 - C++

#include <stdarg.h>
#include <algorithm>
#include <string> 
#include <vector>
#include <sstream>
#include <map>

#define SMART_ENUM(EnumName, ...)									\
class EnumName														\
{																	\
private:															\
	static std::map<int, std::string> nameMap;	 					\
public:																\
	enum {__VA_ARGS__};												\
private:															\
	static std::map<int, std::string> initMap() 					\
	{																\
		using namespace std;										\
																	\
		int val = 0;												\
		string buf_1, buf_2, str = #__VA_ARGS__;					\
		replace(str.begin(), str.end(), '=', ' ');					\
		stringstream stream(str);									\
		vector<string> strings;										\
		while (getline(stream, buf_1, ',')) 						\
			strings.push_back(buf_1);								\
		map<int, string> tmp;										\
		for(vector<string>::iterator it = strings.begin();			\
											   it != strings.end(); \
											   ++it)				\
		{															\
			buf_1.clear(); buf_2.clear();							\
			stringstream localStream(*it);							\
			localStream>> buf_1 >> buf_2;							\
			if(buf_2.size() > 0)									\
				val = atoi(buf_2.c_str());							\
			tmp[val++] = buf_1;										\
		}															\
		return tmp;													\
	}																\
public:																\
	static std::string toString(int aInt)							\
	{																\
		return nameMap[aInt];										\
	}																\
};																	\
std::map<int, std::string> 											\
EnumName::nameMap = EnumName::initMap();

Usage:

SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
cout<<MyEnum::toString(MyEnum::TWO);
cout<<MyEnum::toString(10);

Solution 13 - C++

Note that your conversion function should ideally be returning a const char *.

If you can afford to put your enums in their separate header files, you could perhaps do something like this with macros (oh, this will be ugly):

#include "enum_def.h"
#include "colour.h"
#include "enum_conv.h"
#include "colour.h"

Where enum_def.h has:

#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) enum NAME {
#define ENUM_ADD(NAME, VALUE) NAME = VALUE,
#define ENUM_END };

And enum_conv.h has:

#undef ENUM_START
#undef ENUM_ADD
#undef ENUM_END
#define ENUM_START(NAME) const char *##NAME##_to_string(NAME val) { switch (val) {
#define ENUM_ADD(NAME, VALUE) case NAME: return #NAME;
#define ENUM_END default: return "Invalid value"; } }

And finally, colour.h has:

ENUM_START(colour)
ENUM_ADD(red,   0xff0000)
ENUM_ADD(green, 0x00ff00)
ENUM_ADD(blue,  0x0000ff)
ENUM_END

And you can use the conversion function as:

printf("%s", colour_to_string(colour::red));

This is ugly, but it's the only way (at the preprocessor level) that lets you define your enum just in a single place in your code. Your code is therefore not prone to errors due to modifications to the enum. Your enum definition and the conversion function will always be in sync. However, I repeat, this is ugly :)

Solution 14 - C++

Another answer: in some contexts, it makes sense to define your enumeration in a non-code format, like a CSV, YAML, or XML file, and then generate both the C++ enumeration code and the to-string code from the definition. This approach may or may not be practical in your application, but it's something to keep in mind.

Solution 15 - C++

This is a modification to @user3360260 answer. It has the following new features

  • MyEnum fromString(const string&) support
  • compiles with VisualStudio 2012
  • the enum is an actual POD type (not just const declarations), so you can assign it to a variable.
  • added C++ "range" feature (in form of vector) to allow "foreach" iteration over enum

Usage:

SMART_ENUM(MyEnum, ONE=1, TWO, THREE, TEN=10, ELEVEN)
MyEnum foo = MyEnum::TWO;
cout << MyEnum::toString(foo);  // static method
cout << foo.toString();         // member method
cout << MyEnum::toString(MyEnum::TWO);
cout << MyEnum::toString(10);
MyEnum foo = myEnum::fromString("TWO");

// C++11 iteration over all values
for( auto x : MyEnum::allValues() )
{
  cout << x.toString() << endl;
}

Here's the code

#define SMART_ENUM(EnumName, ...)                                   \
class EnumName                                                      \
{                                                                   \
public:                                                             \
    EnumName() : value(0) {}                                        \
    EnumName(int x) : value(x) {}                                   \
public:                                                             \
    enum {__VA_ARGS__};                                             \
private:                                                            \
    static void initMap(std::map<int, std::string>& tmp)                     \
    {                                                               \
        using namespace std;                                        \
                                                                    \
        int val = 0;                                                \
        string buf_1, buf_2, str = #__VA_ARGS__;                    \
        replace(str.begin(), str.end(), '=', ' ');                  \
        stringstream stream(str);                                   \
        vector<string> strings;                                     \
        while (getline(stream, buf_1, ','))                         \
            strings.push_back(buf_1);                               \
        for(vector<string>::iterator it = strings.begin();          \
                                                it != strings.end(); \
                                                ++it)                \
        {                                                           \
            buf_1.clear(); buf_2.clear();                           \
            stringstream localStream(*it);                          \
            localStream>> buf_1 >> buf_2;                           \
            if(buf_2.size() > 0)                                    \
                val = atoi(buf_2.c_str());                          \
            tmp[val++] = buf_1;                                     \
        }                                                           \
    }                                                               \
    int value;                                                      \
public:                                                             \
    operator int () const { return value; }                         \
    std::string toString(void) const {                              \
            return toString(value);                                 \
    }                                                               \
    static std::string toString(int aInt)                           \
    {                                                               \
        return nameMap()[aInt];                                     \
    }                                                               \
    static EnumName fromString(const std::string& s)                \
    {                                                               \
        auto it = find_if(nameMap().begin(), nameMap().end(), [s](const std::pair<int,std::string>& p) { \
            return p.second == s;                                   \
        });                                                         \
        if (it == nameMap().end()) {                                \
        /*value not found*/                                         \
            throw EnumName::Exception();                            \
        } else {                                                    \
            return EnumName(it->first);                             \
        }                                                           \
    }                                                               \
    class Exception : public std::exception {};                     \
    static std::map<int,std::string>& nameMap() {                   \
      static std::map<int,std::string> nameMap0;                    \
      if (nameMap0.size() ==0) initMap(nameMap0);                   \
      return nameMap0;                                              \
    }                                                               \
    static std::vector<EnumName> allValues() {                      \
      std::vector<EnumName> x{ __VA_ARGS__ };                       \
      return x;                                                     \
    }                                                               \
    bool operator<(const EnumName a) const { return (int)*this < (int)a; } \
};         

Note that the conversion toString is a fast has lookup, while the conversion fromString is a slow linear search. But strings are so expensive anyways(and the associated file IO), I didn't feel the need to optimize or use a bimap.

Solution 16 - C++

Here a one-file solution (based on elegant answer by @Marcin:

#include <iostream>

#define ENUM_TXT \
X(Red) \
X(Green) \
X(Blue) \
X(Cyan) \
X(Yellow) \
X(Magenta) \

enum Colours {
#   define X(a) a,
ENUM_TXT
#   undef X
    ColoursCount
};

char const* const colours_str[] = {
#   define X(a) #a,
ENUM_TXT
#   undef X
    0
};

std::ostream& operator<<(std::ostream& os, enum Colours c)
{
    if (c >= ColoursCount || c < 0) return os << "???";
    return os << colours_str[c] << std::endl;
}

int main()
{
    std::cout << Red << Blue << Green << Cyan << Yellow << Magenta << std::endl;
}

Solution 17 - C++

I do this with separate side-by-side enum wrapper classes which are generated with macros. There are several advantages:

  • Can generate them for enums I don't define (eg: OS platform header enums)
  • Can incorporate range checking into the wrapper class
  • Can do "smarter" formatting with bit field enums

The downside, of course, is that I need to duplicate the enum values in the formatter classes, and I don't have any script to generate them. Other than that, though, it seems to work pretty well.

Here's an example of an enum from my codebase, sans all the framework code which implements the macros and templates, but you can get the idea:

enum EHelpLocation
{
	HELP_LOCATION_UNKNOWN	= 0, 
	HELP_LOCAL_FILE			= 1, 
	HELP_HTML_ONLINE		= 2, 
};
class CEnumFormatter_EHelpLocation : public CEnumDefaultFormatter< EHelpLocation >
{
public:
	static inline CString FormatEnum( EHelpLocation eValue )
	{
		switch ( eValue )
		{
			ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCATION_UNKNOWN );
			ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_LOCAL_FILE );
			ON_CASE_VALUE_RETURN_STRING_OF_VALUE( HELP_HTML_ONLINE );
		default:
			return FormatAsNumber( eValue );
		}
	}
};
DECLARE_RANGE_CHECK_CLASS( EHelpLocation, CRangeInfoSequential< HELP_HTML_ONLINE > );
typedef ESmartEnum< EHelpLocation, HELP_LOCATION_UNKNOWN, CEnumFormatter_EHelpLocation, CRangeInfo_EHelpLocation > SEHelpLocation;

The idea then is instead of using EHelpLocation, you use SEHelpLocation; everything works the same, but you get range checking and a 'Format()' method on the enum variable itself. If you need to format a stand-alone value, you can use CEnumFormatter_EHelpLocation::FormatEnum(...).

Hope this is helpful. I realize this also doesn't address the original question about a script to actually generate the other class, but I hope the structure helps someone trying to solve the same problem, or write such a script.

Solution 18 - C++

It's unreleased software but it seems BOOST_ENUM from Frank Laub could fit the bill. The part I like about it is that you can define an enum within the scope of a class which most of the Macro based enums usually don't let you do. It is located in the Boost Vault at: http://www.boostpro.com/vault/index.php?action=downloadfile&filename=enum_rev4.6.zip&directory=& It hasn't seen any development since 2006 so I don't know how well it compiles with the new Boost releases. Look under libs/test for an example of usage.

Solution 19 - C++

This was my solution with BOOST:

#include <boost/preprocessor.hpp>

#define X_STR_ENUM_TOSTRING_CASE(r, data, elem)                                 \
    case elem : return BOOST_PP_STRINGIZE(elem);

#define X_ENUM_STR_TOENUM_IF(r, data, elem)                                     \
    else if(data == BOOST_PP_STRINGIZE(elem)) return elem;

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

To create enum, declare:

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

For conversions:

SERVICE_RELOAD serviceReloadEnum = strToEnum<SERVICE_RELOAD>("reload_log");
QString serviceReloadStr = enumToStr(reload_log);

Solution 20 - C++

I want to post this in case someone finds it useful.

In my case, I simply need to generate ToString() and FromString() functions for a single C++11 enum from a single .hpp file.

I wrote a python script that parses the header file containing the enum items and generates the functions in a new .cpp file.

You can add this script in CMakeLists.txt with execute_process, or as a pre-build event in Visual Studio. The .cpp file will be automatically generated, without the need to manually update it each time a new enum item is added.

generate_enum_strings.py

# This script is used to generate strings from C++ enums

import re
import sys
import os

fileName = sys.argv[1]
enumName = os.path.basename(os.path.splitext(fileName)[0])

with open(fileName, 'r') as f:
    content = f.read().replace('\n', '')

searchResult = re.search('enum(.*)\{(.*?)\};', content)
tokens = searchResult.group(2)
tokens = tokens.split(',')
tokens = map(str.strip, tokens)
tokens = map(lambda token: re.search('([a-zA-Z0-9_]*)', token).group(1), tokens)

textOut = ''
textOut += '\n#include "' + enumName + '.hpp"\n\n'
textOut += 'namespace myns\n'
textOut += '{\n'
textOut += '    std::string ToString(ErrorCode errorCode)\n'
textOut += '    {\n'
textOut += '        switch (errorCode)\n'
textOut += '        {\n'

for token in tokens:
    textOut += '        case ' + enumName + '::' + token + ':\n'
    textOut += '            return "' + token + '";\n'

textOut += '        default:\n'
textOut += '            return "Last";\n'
textOut += '        }\n'
textOut += '    }\n'
textOut += '\n'
textOut += '    ' + enumName + ' FromString(const std::string &errorCode)\n'
textOut += '    {\n'
textOut += '        if ("' + tokens[0] + '" == errorCode)\n'
textOut += '        {\n'
textOut += '            return ' + enumName + '::' + tokens[0] + ';\n'
textOut += '        }\n'

for token in tokens[1:]:
    textOut += '        else if("' + token + '" == errorCode)\n'
    textOut += '        {\n'
    textOut += '            return ' + enumName + '::' + token + ';\n'
    textOut += '        }\n'
    
textOut += '\n'
textOut += '        return ' + enumName + '::Last;\n'
textOut += '    }\n'
textOut += '}\n'

fileOut = open(enumName + '.cpp', 'w')
fileOut.write(textOut)

Example:

ErrorCode.hpp

#pragma once

#include <string>
#include <cstdint>

namespace myns
{
    enum class ErrorCode : uint32_t
    {
        OK = 0,
        OutOfSpace,
        ConnectionFailure,
        InvalidJson,
        DatabaseFailure,
        HttpError,
        FileSystemError,
        FailedToEncrypt,
        FailedToDecrypt,
        EndOfFile,
        FailedToOpenFileForRead,
        FailedToOpenFileForWrite,
        FailedToLaunchProcess,

        Last
    };

    std::string ToString(ErrorCode errorCode);
    ErrorCode FromString(const std::string &errorCode);
}

Run python generate_enum_strings.py ErrorCode.hpp

Result:

ErrorCode.cpp

#include "ErrorCode.hpp"

namespace myns
{
    std::string ToString(ErrorCode errorCode)
    {
        switch (errorCode)
        {
        case ErrorCode::OK:
            return "OK";
        case ErrorCode::OutOfSpace:
            return "OutOfSpace";
        case ErrorCode::ConnectionFailure:
            return "ConnectionFailure";
        case ErrorCode::InvalidJson:
            return "InvalidJson";
        case ErrorCode::DatabaseFailure:
            return "DatabaseFailure";
        case ErrorCode::HttpError:
            return "HttpError";
        case ErrorCode::FileSystemError:
            return "FileSystemError";
        case ErrorCode::FailedToEncrypt:
            return "FailedToEncrypt";
        case ErrorCode::FailedToDecrypt:
            return "FailedToDecrypt";
        case ErrorCode::EndOfFile:
            return "EndOfFile";
        case ErrorCode::FailedToOpenFileForRead:
            return "FailedToOpenFileForRead";
        case ErrorCode::FailedToOpenFileForWrite:
            return "FailedToOpenFileForWrite";
        case ErrorCode::FailedToLaunchProcess:
            return "FailedToLaunchProcess";
        case ErrorCode::Last:
            return "Last";
        default:
            return "Last";
        }
    }

    ErrorCode FromString(const std::string &errorCode)
    {
        if ("OK" == errorCode)
        {
            return ErrorCode::OK;
        }
        else if("OutOfSpace" == errorCode)
        {
            return ErrorCode::OutOfSpace;
        }
        else if("ConnectionFailure" == errorCode)
        {
            return ErrorCode::ConnectionFailure;
        }
        else if("InvalidJson" == errorCode)
        {
            return ErrorCode::InvalidJson;
        }
        else if("DatabaseFailure" == errorCode)
        {
            return ErrorCode::DatabaseFailure;
        }
        else if("HttpError" == errorCode)
        {
            return ErrorCode::HttpError;
        }
        else if("FileSystemError" == errorCode)
        {
            return ErrorCode::FileSystemError;
        }
        else if("FailedToEncrypt" == errorCode)
        {
            return ErrorCode::FailedToEncrypt;
        }
        else if("FailedToDecrypt" == errorCode)
        {
            return ErrorCode::FailedToDecrypt;
        }
        else if("EndOfFile" == errorCode)
        {
            return ErrorCode::EndOfFile;
        }
        else if("FailedToOpenFileForRead" == errorCode)
        {
            return ErrorCode::FailedToOpenFileForRead;
        }
        else if("FailedToOpenFileForWrite" == errorCode)
        {
            return ErrorCode::FailedToOpenFileForWrite;
        }
        else if("FailedToLaunchProcess" == errorCode)
        {
            return ErrorCode::FailedToLaunchProcess;
        }
        else if("Last" == errorCode)
        {
            return ErrorCode::Last;
        }

        return ErrorCode::Last;
    }
}

Solution 21 - C++

Adding even more simplicity of use to Jasper Bekkers' fantastic answer:

Set up once:

#define MAKE_ENUM(VAR) VAR,
#define MAKE_STRINGS(VAR) #VAR,
#define MAKE_ENUM_AND_STRINGS(source, enumName, enumStringName) \
    enum enumName { \
    source(MAKE_ENUM) \
    };\
const char* const enumStringName[] = { \
    source(MAKE_STRINGS) \
    };

Then, for usage:

#define SOME_ENUM(DO) \
    DO(Foo) \
    DO(Bar) \
    DO(Baz)
...
MAKE_ENUM_AND_STRINGS(SOME_ENUM, someEnum, someEnumNames)

Solution 22 - C++

You could use a reflection library, like Ponder. You register the enums and then you can convert them back and forth with the API.

enum class MyEnum
{
    Zero = 0,
    One  = 1,
    Two  = 2
};

ponder::Enum::declare<MyEnum>()
    .value("Zero", MyEnum::Zero)
    .value("One",  MyEnum::One)
    .value("Two",  MyEnum::Two);

ponder::EnumObject zero(MyEnum::Zero);

zero.name(); // -> "Zero"

Solution 23 - C++

A problem with answer 0 is that the enum binary values do not necessarily start at 0 and are not necessarily contiguous.

When I need this, I usually:

  • pull the enum definition into my source
  • edit it to get just the names
  • do a macro to change the name to the case clause in the question, though typically on one line: case foo: return "foo";
  • add the switch, default and other syntax to make it legal

Solution 24 - C++

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

#! /usr/bin/env ruby

# Let's "parse" the headers
# Note that using a regular expression is rather fragile
# and may break on some inputs

GLOBS = [
  "toto/*.h",
  "tutu/*.h",
  "tutu/*.hxx"
]

enums = {}
GLOBS.each { |glob|
  Dir[glob].each { |header|
    enums[header] = File.open(header, 'rb') { |f|
      f.read
    }.scan(/enum\s+(\w+)\s+\{\s*([^}]+?)\s*\}/m).collect { |enum_name, enum_key_and_values|
      [
        enum_name, enum_key_and_values.split(/\s*,\s*/).collect { |enum_key_and_value|
          enum_key_and_value.split(/\s*=\s*/).first
        }
      ]
    }
  }
}


# Now we build a .h and .cpp alongside the parsed headers
# using the template engine provided with ruby
require 'erb'

template_h = ERB.new <<-EOS
#ifndef <%= enum_name %>_to_string_h_
#define <%= enum_name %>_to_string_h_ 1

#include "<%= header %>"
char* enum_to_string(<%= enum_name %> e);

#endif
EOS

template_cpp = ERB.new <<-EOS
#include "<%= enum_name %>_to_string.h"

char* enum_to_string(<%= enum_name %> e)
{
  switch (e)
  {<% enum_keys.each do |enum_key| %>
    case <%= enum_key %>: return "<%= enum_key %>";<% end %>
    default: return "INVALID <%= enum_name %> VALUE";
  }
}
EOS

enums.each { |header, enum_name_and_keys|
  enum_name_and_keys.each { |enum_name, enum_keys|
    File.open("#{File.dirname(header)}/#{enum_name}_to_string.h", 'wb') { |built_h|
      built_h.write(template_h.result(binding))
    }

    File.open("#{File.dirname(header)}/#{enum_name}_to_string.cpp", 'wb') { |built_cpp|
      built_cpp.write(template_cpp.result(binding))
    }
  }
}

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

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

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

More robust solutions would be:

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

Solution 25 - C++

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

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

Solution 26 - C++

Here is a CLI program I wrote to easily convert enums to strings. Its easy to use, and takes about 5 seconds to get it done (including the time to cd to the directory containing the program, then run it, passing to it the file containing the enum).

Download here: http://www.mediafire.com/?nttignoozzz

Discussion topic on it here: http://cboard.cprogramming.com/projects-job-recruitment/127488-free-program-im-sharing-convertenumtostrings.html

Run the program with the "--help" argument to get a description how to use it.

Solution 27 - C++

Not so long ago I made some trick to have enums properly displayed in QComboBox and to have definition of enum and string representations as one statement

#pragma once
#include <boost/unordered_map.hpp>

namespace enumeration
{

   struct enumerator_base : boost::noncopyable
   {
      typedef
         boost::unordered_map<int, std::wstring>
         kv_storage_t;
      typedef
         kv_storage_t::value_type
         kv_type;
      kv_storage_t const & kv() const
      {
         return storage_;
      }

      LPCWSTR name(int i) const
      {
         kv_storage_t::const_iterator it = storage_.find(i);
         if(it != storage_.end())
            return it->second.c_str();
         return L"empty";
      }

   protected:
      kv_storage_t storage_;
   };

   template<class T>
   struct enumerator;

   template<class D>
   struct enum_singleton : enumerator_base
   {
      static enumerator_base const & instance()
      {
         static D inst;
         return inst;
      }
   };
}

#define QENUM_ENTRY(K, V, N)  K, N storage_.insert(std::make_pair((int)K, V));

#define QBEGIN_ENUM(NAME, C)   \
enum NAME                     \
{                             \
   C                          \
}                             \
};                            \
}                             \

#define QEND_ENUM(NAME) \
};                     \
namespace enumeration  \
{                      \
template<>             \
struct enumerator<NAME>\
   : enum_singleton< enumerator<NAME> >\
{                      \
   enumerator()        \
   {

//usage
/*
QBEGIN_ENUM(test_t,
   QENUM_ENTRY(test_entry_1, L"number uno",
   QENUM_ENTRY(test_entry_2, L"number dos",
   QENUM_ENTRY(test_entry_3, L"number tres",
QEND_ENUM(test_t)))))
*/

Now you've got enumeration::enum_singleton<your_enum>::instance() able to convert enums to strings. If you replace kv_storage_t with boost::bimap, you will also be able to do backward conversion. Common base class for converter was introduced to store it in Qt object, because Qt objects couldn't be templates

Previous appearance

Solution 28 - C++

As variant, use simple lib > http://codeproject.com/Articles/42035/Enum-to-String-and-Vice-Versa-in-C

In the code

#include <EnumString.h>

enum FORM {
    F_NONE = 0,
    F_BOX,
    F_CUBE,
    F_SPHERE,
};

add lines

Begin_Enum_String( FORM )
{
    Enum_String( F_NONE );
    Enum_String( F_BOX );
    Enum_String( F_CUBE );
    Enum_String( F_SPHERE );
}
End_Enum_String;

Work fine, if values in enum are not dublicate.

Example usage

enum FORM f = ...
const std::string& str = EnumString< FORM >::From( f );

and vice versa

assert( EnumString< FORM >::To( f, str ) );

Solution 29 - C++

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!

Solution 30 - C++

#include <iostream>
#include <map>
#define IDMAP(x) (x,#x)

std::map<int , std::string> enToStr;
class mapEnumtoString
{
public:
    mapEnumtoString(){  }
    mapEnumtoString& operator()(int i,std::string str)
    {
        enToStr[i] = str;
        return *this;
    }
public:
   std::string operator [] (int i)
    {
        return enToStr[i];
    }

};
mapEnumtoString k;
mapEnumtoString& init()
{
    return k;
}

int main()
{

init()
    IDMAP(1)
    IDMAP(2)
    IDMAP(3)
    IDMAP(4)
    IDMAP(5);
std::cout<<enToStr[1];
std::cout<<enToStr[2];
std::cout<<enToStr[3];
std::cout<<enToStr[4];
std::cout<<enToStr[5];
}

Solution 31 - C++

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

The example:

int main () {

  VERB a = VERB::GET;
  VERB b = VERB::GET;
  VERB c = VERB::POST;
  VERB d = VERB::PUT;
  VERB e = VERB::DELETE;


  std::cout << a.toString() << std::endl;

  std::cout << a << std::endl;

  if ( a == VERB::GET ) {
	std::cout << "yes" << std::endl;
  }

  if ( a == b ) {
	std::cout << "yes" << std::endl;
  }

  if ( a != c ) {
	std::cout << "no" << std::endl;
  }

}

The VERB class:

// -----------------------------------------------------------
// -----------------------------------------------------------
class VERB {

private:

  // private constants
  enum Verb {GET_=0, POST_, PUT_, DELETE_};
  
  // private string values
  static const std::string theStrings[];

  // private value
  const Verb value;
  const std::string text;

  // private constructor
  VERB (Verb v) :
  value(v), text (theStrings[v])
  {
	// std::cout << " constructor \n";
  }

public:

  operator const char * ()  const { return text.c_str(); }

  operator const std::string ()  const { return text; }

  const std::string toString () const { return text; }

  bool operator == (const VERB & other) const { return (*this).value == other.value; }

  bool operator != (const VERB & other) const { return ! ( (*this) == other); }

  // ---

  static const VERB GET;
  static const VERB POST;
  static const VERB PUT;
  static const VERB DELETE;

};

const std::string VERB::theStrings[] = {"GET", "POST", "PUT", "DELETE"};

const VERB VERB::GET = VERB ( VERB::Verb::GET_ );
const VERB VERB::POST = VERB ( VERB::Verb::POST_ );
const VERB VERB::PUT = VERB ( VERB::Verb::PUT_ );
const VERB VERB::DELETE = VERB ( VERB::Verb::DELETE_ );
// end of file

Solution 32 - C++

Using compound ternary statements can be somewhat elegant for enums with few elements (one-liner). The expression only grows approximately linearly in length with the number of elements too.

Here's a good use case:

enum log_level {INFO, WARNING, ERROR};
...
void logger::write(const std::string log, const log_level l) {
    ...
    std::string s = (l == INFO) ? "INFO" : 
                    (l == WARNING) ? "WARNING" : 
                    (l == ERROR) ? "ERROR" : "UNKNOWN";
    ...
}
...

Of course, it's just another switch/if statement block, but it's a single line statement. And as a matter of terseness vs. simplicity, it meets somewhere in the middle. As a constant expression, it can be easily made into an inline function as well.

Solution 33 - C++

I came across this question when I was looking for a solution to my own problem for printing the "words" of the enumeration in C++. I came back to provide a simple solution which answers the presented question as worded. All that's required is to 'mirror' the enum list with a vector.

enum class genre { Fiction, NonFiction, Periodical, Biography, Children };

vector<string>genre_tbl { "Fiction", "NonFiction", "Periodical", "Biography", "Children" };

Because the enum as typed above will do the following by default;

Fiction = 0
NonFiction = 1
Periodical = 2
Biography = 3
Children = 4

This matches the vector positions which makes enum to string conversion pretty straight forward.

string s1 = genre_tbl[int(genre::fiction)];

For my problem I created a user defined class called Book with a member called Gen of type genre. The program needed to be able to print the genre as the word.

class book {...};
ostream& operator<<(ostream& os, genre g) { return os << genre_tbl[int(g)]; }

book b1;
b1.Gen = genre(0)
cout << b1.Gen;

For which "Fiction" will print to screen in this case.

Solution 34 - C++

This question is a duplicate of,

However, in none of the questions, I could find good answers.

After delving into the topic, I found two great open source solutions:

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.

Note: I am repeating the recommendation here. This question has a lot of traffic/views and really requires listing the solutions above.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionEdu FelipeView Question on Stackoverflow
Solution 1 - C++Marcin KoziukView Answer on Stackoverflow
Solution 2 - C++AvdiView Answer on Stackoverflow
Solution 3 - C++Jasper BekkersView Answer on Stackoverflow
Solution 4 - C++gbjbaanbView Answer on Stackoverflow
Solution 5 - C++Debdatta BasuView Answer on Stackoverflow
Solution 6 - C++Ronny BrendelView Answer on Stackoverflow
Solution 7 - C++Mark RansomView Answer on Stackoverflow
Solution 8 - C++sergeView Answer on Stackoverflow
Solution 9 - C++BenView Answer on Stackoverflow
Solution 10 - C++CarlView Answer on Stackoverflow
Solution 11 - C++MSaltersView Answer on Stackoverflow
Solution 12 - C++user3360260View Answer on Stackoverflow
Solution 13 - C++Ates GoralView Answer on Stackoverflow
Solution 14 - C++AvdiView Answer on Stackoverflow
Solution 15 - C++Mark LakataView Answer on Stackoverflow
Solution 16 - C++FractalSpaceView Answer on Stackoverflow
Solution 17 - C++NickView Answer on Stackoverflow
Solution 18 - C++AlexisView Answer on Stackoverflow
Solution 19 - C++lopesView Answer on Stackoverflow
Solution 20 - C++Alexandru IrimieaView Answer on Stackoverflow
Solution 21 - C++Francois BertrandView Answer on Stackoverflow
Solution 22 - C++NickView Answer on Stackoverflow
Solution 23 - C++mpez0View Answer on Stackoverflow
Solution 24 - C++bltxdView Answer on Stackoverflow
Solution 25 - C++James CurranView Answer on Stackoverflow
Solution 26 - C++Programmer_PView Answer on Stackoverflow
Solution 27 - C++kassakView Answer on Stackoverflow
Solution 28 - C++Andrii SyrokomskyiView Answer on Stackoverflow
Solution 29 - C++OlivierBView Answer on Stackoverflow
Solution 30 - C++user3510054View Answer on Stackoverflow
Solution 31 - C++cibercitizen1View Answer on Stackoverflow
Solution 32 - C++user7851115View Answer on Stackoverflow
Solution 33 - C++Joe CView Answer on Stackoverflow
Solution 34 - C++jose.angel.jimenezView Answer on Stackoverflow