views:

9373

answers:

20

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.

A: 

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.

James Curran
+9  A: 

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 };
char* array[] 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)

gbjbaanb
+1  A: 

Answer about Macro based factory moved into http://stackoverflow.com/questions/147267/easy-way-to-use-variables-of-enum-types-as-string-in-c#202511 - after the question was updated, it is no longer relevant here.

Suma
+1  A: 

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
mpez0
+15  A: 

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.
Marcin Koziuk
nifty,although I don't like the extra file
Ronny
+1, but likely to mess with Intellisense, and things like it...
Matt Cruikshank
Just make sure your build process doesn't prepend #pragma( once ) before every include file...
xtofl
The problem with this solution is that it changes the header defining the enum, which I cannot do. So this is not enough for my problem.
Edu Felipe
+1  A: 

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

MSalters
+26  A: 

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.

Avdi
Excellent! Worked as a charm with a simple python script. Thanks.
Edu Felipe
+1, GCCXML looks very nice! (Although I almost -1ed as I initially misread this as a suggestion to use the above verbose XML syntax to encode your enum -- a solution which reeks of overengineering!)
j_random_hacker
any change you can post the python script?
phillipwei
+1  A: 

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

Ates Goral
+1  A: 

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.
bltxd
+1  A: 

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.

Avdi
+4  A: 

QT is able to pull that of (thanks to the meta object compiler): http://labs.trolltech.com/blogs/2008/10/09/coding-tip-pretty-printing-enum-values/

Ronny
+1  A: 

Already answered here.

plinth
No, it was not. My question was how to do it without changing the source file.
Edu Felipe
+1  A: 

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.

Nick
+4  A: 

@hydroo: Without the extra file:

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

#define MAKE_UNION(VAR) VAR,
enum MetaSyntacticVariable{
    SOME_UNION(MAKE_UNION)
}

#define MAKE_STRINGS(VAR) #VAR,
const char* const MetaSyntacticVariableNames{
    SOME_UNION(MAKE_STRINGS)
}
Jasper Bekkers
A: 

Interesting to see the number of ways. here's one i used recently:

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.

carleeto
Inheriting a stl container is not a good idea
fnieto
It's perfectly legal. I do it all the time.
wrang-wrang
+1  A: 

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&amp;filename=enum%5Frev4.6.zip&amp;directory=&amp; 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.

Alexis
A: 

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.

Programmer_P
Could you please put this on a repository somewhere (github, google code or bitbucket) and post the link here, instead of mediafire? I would help people wanting to understand it :)
Edu Felipe
A: 

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

Programmer_P
I have added your second link to your previous answer. You may now delete this. (Couldn't you add the second link as a comment?)
badp
A: 

Here's the new link: http://dl.dropbox.com/u/8029532/ConvertEnumToStrings.zip

@Edu Felipe: Sorry, but I checked out the sites you mentioned, and none I particularly liked. Is there any free site on the web for that where you don't have to signup?

I did post my .zip file at Google code though, but I can't post the link in this same post because of the "one link" restriction. That's also why I posted different links in separate posts above.

I couldn't find a way to post the source where it can be browsed and read online, which I'm guessing is what you want? Anyway, I didn't go over the sites in any great depth, because I don't have the time, but hopefully you can suggest another site.

Cheers, and thanks for the interest in my program. Honestly, when I posted here, I wasn't expecting anyone to respond.

Programmer_P
A: 

Ok, here's the Google Code link:

http://code.google.com/p/convertenumtostrings/

Programmer_P