1

This is an extension of the following question which already has great answers.

However, in all answers where the expansion is carried out by the pre-processor the enums are declared in a special way under the same project. But what if the header file that declares these enums is not your own or the enums are declared out of id order.

In my case, I have an SDK that I'm using where large tables of enums are already declared in a separate header file like the following:

#define NODE_GRAPH_TYPE(_name, _enum)   _name = _enum,
enum NodeGraphType
{
    // next available ID value is 7
    NODE_GRAPH_TYPE(GT_UNKNOWN          , 0)
    NODE_GRAPH_TYPE(GT_STANDARD         , 1)
    NODE_GRAPH_TYPE(GT_APP_SETTINGS     , 3)
    NODE_GRAPH_TYPE(GT_GEOMETRYARCHIVE  , 2)
    NODE_GRAPH_TYPE(GT_SCRIPTED         , 5)
    NODE_GRAPH_TYPE(GT_SCRIPT_STORAGE   , 6)
    NODE_GRAPH_TYPE(GT_PROJECT_SETTINGS , 4)
};

What I want to do is to try to avoid is this type of thing:

function NODE_GRAPH_NAME(int type)
{
  switch(type)
  {
    case: GT_PROJECT_SETTINGS:
      return "GT_PROJECT_SETTINGS";
    // ...
  };
}

or

static const char* NODE_GRAPH_NAME[] = {
  "GT_UNKNOWN",
  /// ...    
}

The best I've come up with this this so far:

#define STRINGIFY(_x) #_x

static const char* NODE_GRAPH_NAME[] = {
  STRINGIFY(GT_UNKNOWN),
  STRINGIFY(GT_STANDARD),
  STRINGIFY(GT_GEOMETRYARCHIVE),
  STRINGIFY(GT_APP_SETTINGS)
  // etc
};

But it still requires me to duplicate all the tables of enums and not only that, but them in order and double check there are no holes.

Is there perhaps a more elegant and automated way to go about this? If it helps, I'm actually compiling this under C++. But the library I'm linking to is pure C. So you could do something fancy with templates perhaps.


Based on Thomas Matthews answer below, this is what I came up with the following:

Which isn't entirely automatic, but isn't too difficult to work with. In redefining NODE_GRAPH_TYPE I can copy verbatim the table from the SDK header and place it into the NODE_GRAPH_TYPES table definition. And it works. Then I can create a lookup function to locate the item I require. The nice thing about this is that holes don't matter and neither do out of order definitions.

#undef NODE_GRAPH_TYPE
#define NODE_GRAPH_TYPE(_name, _enum) { _enum, #_name },

static const Enum_Entry NODE_GRAPH_TYPES[] = 
{
   NODE_GRAPH_TYPE(GT_UNKNOWN          , 0)

    NODE_GRAPH_TYPE(GT_STANDARD         , 1)
    NODE_GRAPH_TYPE(GT_APP_SETTINGS     , 3)
    NODE_GRAPH_TYPE(GT_GEOMETRYARCHIVE  , 2)
    NODE_GRAPH_TYPE(GT_SCRIPTED         , 5)
    NODE_GRAPH_TYPE(GT_SCRIPT_STORAGE   , 6)
    NODE_GRAPH_TYPE(GT_PROJECT_SETTINGS , 4)
};

static const unsigned int NODE_GRAPH_TYPES_COUNT = 
    sizeof(NODE_GRAPH_TYPES) / sizeof(NODE_GRAPH_TYPES[0]);

const char* NODE_GRAPH_TYPE_NAME(int id)
{    
    for (int i = 0; i < NODE_GRAPH_TYPES_COUNT; ++i){
        if (PIN_TYPES[i].enum_value == id)
            return PIN_TYPES[i].enum_text;
    }
    return "Unknown";
} 
Community
  • 1
  • 1
hookenz
  • 36,432
  • 45
  • 177
  • 286

4 Answers4

3

You can't convert an enum to text or text to enum automatically, as enum identifiers only exist during compilation and not during run-time.

The best methods are table lookup or std::map.

I use a table lookup since I can make the table static and const and have it initialized before main.

struct Enum_Entry
{
  int enum_value;
  const char * enum_text;
};

static const Enum_Entry enum_table[] = 
{
  {GT_UNKNOWN, "GT_UNKNOWN"},
  //...
};
static const unsigned int table_capacity =
    sizeof(enum_table) / sizeof(enum_table[0]);

By having both the enum value and the text in the same table, the one table can be used for converting text to enum and enum to text.
When using a std::map, one of the conversion directions is not very efficient.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
2

In C, at least, you can use a variant on your proposed solution that does not require your enum values to be without holes or to be expressed in order. C has designated initializers by which you can initialize specified elements of your array. To wit:

#define STRINGIFY(x) #x

enum c { X, Y = 3, Z = 1 };

static const char * E_NAME[] = {
   [X] = STRINGIFY(X),
   [Y] = STRINGIFY(Y),
   [Z] = STRINGIFY(Z)
};

const char *e_name(enum c x) {
    return E_NAME[x];
}

If you want to avoid repeating the list of enum constants, that would be amenable to an X macro treatment:

#define STRINGIFY(x) #x

#define ENUM_ELEMENTS E(X,), E(Y,=3), E(Z,=1)

#define E(c, i) c i
enum c {
    ENUM_ELEMENTS
};
#undef E

#define E(c, i) [c] = STRINGIFY(c)
static const char *E_NAME[] = {
    ENUM_ELEMENTS
};
#undef E

const char *e_name(enum c x) {
    return E_NAME[x];
}

Whether the X macro version helps is debatable; certainly it would be more of an advantage if the number of enum elements were greater.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
1

You can build this in this way:

#define NODEGRAPHTYPES \
    NODE_GRAPH_TYPE(GT_UNKNOWN          , 0)\
    NODE_GRAPH_TYPE(GT_STANDARD         , 1)\
    NODE_GRAPH_TYPE(GT_APP_SETTINGS     , 3)\
    NODE_GRAPH_TYPE(GT_GEOMETRYARCHIVE  , 2)\
    NODE_GRAPH_TYPE(GT_SCRIPTED         , 5)\
    NODE_GRAPH_TYPE(GT_SCRIPT_STORAGE   , 6)\
    NODE_GRAPH_TYPE(GT_PROJECT_SETTINGS , 4)

Then you can re-use this when defining an enum, and when defining the enum-to-text conversion like so:

enum NodeGraphType {
#define NODE_GRAPH_TYPE(NAME, VALUE) NAME = VALUE,
    NODEGRAPHTYPES
#undef NODE_GRAPH_TYPE
};

static const char *NodeGraphName(enum NodeGraphType type)
{
    switch (type) {
#define NODE_GRAPH_TYPE(NAME, VALUE) case NAME: return #NAME;
        NODEGRAPHTYPES
#undef NODE_GRAPH_TYPE
    }
    return "*Unknown*";
}
hookenz
  • 36,432
  • 45
  • 177
  • 286
nos
  • 223,662
  • 58
  • 417
  • 506
  • The problem with this is I'd be meddling with or redefining an existing header. This is a lot of work when the header is already very large. The enum is already defined in the SDK header file so I'll get errors right off the bat. – hookenz Feb 08 '17 at 21:26
  • Sorry about the accidental edit. But thanks for you answer. It could still prove useful for someone. – hookenz Feb 08 '17 at 21:52
0

Your existing header file can be leveraged to generate an X-macro like file. It is relatively easy to generate an intermediate file that consists of just has the NODE_GRAPH_TYPE lines based on the example you have provided.

grep '^ *NODE_GRAPH_TYPE' node_graph.h > node_graphs.x

Then, you can create a include this intermediate file with your own definition of NODE_GRAPH_TYPE to define your table the way you want. Below illustrates the function call as in your first example.

static const char * NODE_GRAPH_NAME(enum NodeGraphType)
{
    switch(type)
    {
#   define NODE_GRAPH_TYPE(x, y) case x: return #x;
#   include "node_graphs.x"
#   undef NODE_GRAPH_TYPE
    default: break;
    }
    return NULL;
}
jxh
  • 69,070
  • 8
  • 110
  • 193