6

I have an enumeration, which contains hundreds of entries.

I will be getting the value of the enumeration as a string. Is there any way to convert the string into an enum value? Otherwise, I will end up using hundreds of if statements.

Consider

enum Colors { Red, Green, Blue, Yellow ... } there are more than 100 entries

I will be getting "Red" in a string variable,

String color = "Red"; // "Red" would be generated dynamically.

Normally we access the enum in the following way, Colors::Red, Colors::Blue etc... is there any way in which we can access it in a way something like this:

Colors::color; // i.e enumtype::stringVariable

In many posts here, it’s given that we can use map, but again while constructing map we will end up in using hundreds of ifs.

Is there any way to avoid this?

Ry-
  • 218,210
  • 55
  • 464
  • 476
Gokul Kulkarni
  • 2,069
  • 3
  • 26
  • 42

4 Answers4

8

Here's a C way of doing it, similar to Paddy's C++ map. The macro guarantees that the name and corresponding enum are tied together.

enum Colors { NoColor, Red, Green, Blue, Yellow };

enum Colors get_color(const char *s)
{
    const struct {
        char *name;
        enum Colors color;
    } colormap[] = {
#define Color(x) {#x, x}
        Color(Red),
        Color(Green),
        Color(Blue),
        Color(Yellow)
#undef Color
    };
    for (size_t i = 0; i < sizeof colormap / sizeof colormap[0]; ++i) {
        if (!strcmp(s, colormap[i].name)) {
            return colormap[i].color;
        }
    }
    return NoColor;
}


EDIT As @sh1 suggested in a comment (which has now gone), you could use an X-macro to define the list of colors. This avoids defining the list twice. Here's the above example rewritten using an X-macro - thanks to sh1 for the hint:
#define COLORS  X(Red), X(Green), X(Blue), X(Yellow),

enum Colors {
    NoColor, 
#define X(x) x
    COLORS
#undef X
};

enum Colors get_color(const char *s)
{
    const struct {
        char *name;
        enum Colors color;
    } colormap[] = {
#define X(x) {#x, x}
        COLORS
#undef X
    };
...etc
William Morris
  • 3,554
  • 2
  • 23
  • 24
  • Love this solution! Would it be a good idea to undefine `Color(x)` right after the `Color(Yellow)` statement to prevent unintended results in other parts of the code? – Floris Aug 11 '13 at 13:26
5

Use the X-macro technique. Transcribing almost directly from Wikipedia:

#define LIST_OF_COLORS \
    X(Red) \
    X(Green) \
    X(Blue) \
    X(Yellow)

#define X(name) name,
enum Colors { LIST_OF_COLORS };
#undef X

#define X(name) #name,
char const * const ColorName[] = { LIST_OF_COLORS };
#undef X

Because enums automatically assign values counting from zero, and we can't accidentally repeat the list in a different order when we create the name array, using the enum as the index into the ColorName array will always point directly to the corresponding word, and you don't have to search when mapping in that direction. So:

printf("%s\n", ColorName[Red]);

Will print:

Red

And going the other way:

enum Color strtoColor(char const *name)
{
    for (int i = 0; i < sizeof(ColorName) / sizeof(*ColorName); i++)
        if (strcmp(ColorName[i], name) == 0)
            return (enum Color)i;
    return -1;
}

EDIT

If you are using C++, then, Using X-macro on paddy's answer:

static std::map<string, enum Colors> colorMap;
void InitColorMap()
{
#define X(name) colorMap[#name] = name;
    LIST_OF_COLORS
#undef X
}

Or, stealing from this answer, in C++11:

static std::map<string, enum Colors> colorMap =
{
#define X(name) { #name, name },
    LIST_OF_COLORS
#undef X
};

... or whatever. That's not my language.

Community
  • 1
  • 1
sh1
  • 4,324
  • 17
  • 30
  • It's occurred to me that it's more useful to `#define LIST_OF_FOO(X) X(foo1) X(foo2)...`, because then you can instantiate things like `LIST_OF_FOO(STRINGIZE)` and define `STRINGIZE` just once for multiple lists. – sh1 Jul 18 '19 at 04:34
  • Just [confirmed](https://stackoverflow.com/q/57087772/2417578) that my previous comment is legal, and the question illustrates how to get more compact code when you have multiple enums to describe. I should edit this answer, but I doubt I'll find the time. – sh1 Jul 18 '19 at 06:17
0

This is a bit of a hack, but if you are using pure C you could do the following:

char* stringArray[]={"Red", "Green", "Blue", "Yellow"};

Then you can find the matching string by looping over the array until you find a match:

for(ii = 0; ii < size(stringArray); ii++) {
  if (strcmp(inputString, stringArray[ii]) == 0) {
    // you found it!
  }
}
Floris
  • 45,857
  • 6
  • 70
  • 122
0

It's not clear whether you're using C or C++ here.

One way to sort this out is to use a macro with the stringize operator. Something like this:

#define RegisterColor(c) colorMap[#c] = (c)

In this case, I've assumed something like a C++ map:

enum Colors { .... };
static std::map<string, enum Colors> colorMap;

void InitColorMap()
{
    RegisterColor(Red);
    RegisterColor(Green);
    RegisterColor(Blue);
    RegisterColor(Yellow);
    // ...
}

There is a little repetition, but if you need the enum you can't avoid that.

It's easy enough to use the same principle in C, using your own data structure.

paddy
  • 60,864
  • 6
  • 61
  • 103
  • 1
    If you're using a `std::map` there's no reason to involve the preprocessor at all, just set them `colorMap["Red"] = Red;`. – Ed S. Aug 06 '13 at 02:26
  • 1
    Sure, but now 'Red' appears thrice instead of twice. – paddy Aug 06 '13 at 02:29