views:

5857

answers:

10

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);
+6  A: 

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;
}
J.F. Sebastian
While fastest, it isn't as easy as @MSalters.
kenny
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
@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).
J.F. Sebastian
@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
A: 

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
Indeed a very non-DRY-SPOT solution.
xtofl
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
+2  A: 

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
Actually the method name() is not necessary. See my answer.
J.F. Sebastian
A: 

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
+11  A: 

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)

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::tr1::unordered_map

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

MSalters
You are right to refer to another question. People should take a look in the "related questions" before posting...
xtofl
@xtofl : The "related questions" shown here are totally different to the related questions listed when I posted the question!
Roddy
@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
@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
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
yeah - I could get this working but defining operator,(MyEnum, const char*) for that would be Evil++. Agree with boost::assign.
MSalters
A: 

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

Alastair
+1  A: 

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)
David Allan Finch
+1  A: 

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
A: 

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  A: 

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

Valentin Heinitz

Valentin Heinitz