14

Is there a convienent way to take a string (input by user) and convert it to an Enumeration value? In this case, the string would be the name of the enumeration value, like so:

enum Day
{
    Sunday = 0,
    Monday = 1,
    ...
}

So that if the user gave the name of a Day, it would be able to parse that to the corresponding Enum value.

The trick is, I have over 500 values I'm working with, and they are spread out across multiple enumerations.

I know of the Enum.Parse Method in c#, so is there some form of this in c?

Nealon
  • 2,213
  • 6
  • 26
  • 40
  • 3
    How do you want to relate strings and enumerations? –  May 30 '13 at 19:51
  • the string would be the name of the enumeration value. – Nealon May 30 '13 at 19:54
  • 1
    Short answer: no, there's no "convenient" way, but you can probably get what you want through a tricky use of macros. See http://stackoverflow.com/questions/147267/easy-way-to-use-variables-of-enum-types-as-string-in-c – Taylor Brandstetter May 30 '13 at 19:58
  • 1
    or http://stackoverflow.com/questions/201593/is-there-a-simple-script-to-convert-c-enum-to-string?rq=1 – John Carter May 30 '13 at 20:00
  • Do you really have to use straight C? std::map would be *ideal* for this. – CmdrMoozy May 30 '13 at 20:17
  • haha, yeah. I'm modifying source code (10000+ lines) to implement new features. Already did it once in c# and it went quite smoothly. I think this ones gonna be ugly though – Nealon May 30 '13 at 20:20
  • You could still use a separate C++ source file to implement the `map`, and provide `extern "C"` interfaces to allow the C code to do the lookup. It is unclear to me how you want the implementation to scale. Are you willing to change how you declare your enumerations to make a solution work? – jxh May 30 '13 at 20:24
  • I don't think that is an option. Currently, as I understand it, the values for the enumerations are coming from a file that has one massive array (of the names, values, and properties of the soon to be enums). That file is generated in the prebuild. My boss recommended that I use that file, compare the strings of the names, and generate hash functions that way – Nealon May 30 '13 at 20:30
  • use gperf (too lazy for a proper answer right now) – Per Johansson May 30 '13 at 20:34
  • @Nealon: Ah, so using an external tool to perform code generation for you is acceptable. Then, @PerJohansson's suggestion of `gperf` combined with a hash table can be done, you just need a script to generate the appropriate code for you. – jxh May 30 '13 at 20:36
  • @user315052 yes, we are using nant to build everything and generate files for xmls and such. – Nealon May 30 '13 at 20:38

6 Answers6

22

The standard way to implement it is something along the lines of:

typedef enum {value1, value2, value3, (...) } VALUE;

const static struct {
    VALUE      val;
    const char *str;
} conversion [] = {
    {value1, "value1"},
    {value2, "value2"},
    {value3, "value3"},
       (...)
};

VALUE
str2enum (const char *str)
{
     int j;
     for (j = 0;  j < sizeof (conversion) / sizeof (conversion[0]);  ++j)
         if (!strcmp (str, conversion[j].str))
             return conversion[j].val;    
     error_message ("no such string");
}

The converse should be apparent.

wallyk
  • 56,922
  • 16
  • 83
  • 148
  • honestly, I'm a little lost, can you clarify what this does a bit? – Nealon May 30 '13 at 20:18
  • 1
    @Nealon: It sets up a data structure which associates each string value with a corresponding enum value. The conversion function looks for a string match and returns the corresponding enum value. – wallyk May 30 '13 at 20:28
  • so its like making a dictionary in C#? – Nealon May 30 '13 at 20:32
  • 1
    Maybe `const char *str;` instead of `char *str;`. Compiler might detect some wrong uses during this development. – chux - Reinstate Monica May 30 '13 at 20:43
  • @chux: a fair tweak if using a modern compiler, which probably will give a useful warning to the same effect. I've modified it anyway. – wallyk May 30 '13 at 20:50
  • 1
    It's worth noting that this algorithm is O(n) on the number of possible enum values. If you're really just doing the days of the week, this will be fine, but if you have an enum with e.g. 100 possible values, and you're calling this function a large number of times, it might add up. – CmdrMoozy May 30 '13 at 21:20
4

Warning, this is a total hack. You can use dlsym to do a lookup of a variable that is appropriately initialized. For this example to work, you have to compile to allow local symbols to be visible to the dynamic linker. With GCC, the option is -rdynamic.

enum Day {
    SunDay, MonDay, TuesDay, WednesDay, ThursDay, FriDay, SaturDay
};

enum Day Sunday = SunDay,
         Monday = MonDay,
         Tuesday = TuesDay,
         Wednesday = WednesDay,
         Thursday = ThursDay,
         Friday = FriDay,
         Saturday = SaturDay;

int main () {
    const char *daystr = "Thursday";
    void *h = dlopen(0, RTLD_NOW);
    enum Day *day = dlsym(h, daystr);
    if (day) printf("%s = %d\n", daystr, *day);
    else printf("%s not found\n", daystr);
    return 0;
}
jxh
  • 69,070
  • 8
  • 110
  • 193
4

There isn't a direct way, but with C, you improvise. Here's an old trick. Purists may balk at this. But it's a way to manage this kind of stuff somewhat sanely. Uses some preprocessor tricks.

In constants.h put in the following:

CONSTANT(Sunday,  0)
CONSTANT(Monday,  1)
CONSTANT(Tuesday, 2)

In main.c:

#include <stdio.h>

#define CONSTANT(name, value) \
    name = value,

typedef enum {
    #include "constants.h"
} Constants;

#undef CONSTANT

#define CONSTANT(name, value) \
    #name,

char* constants[] = {
    #include "constants.h"
};  

Constants str2enum(char* name) {
    int ii;
    for (ii = 0; ii < sizeof(constants) / sizeof(constants[0]); ++ii) {
        if (!strcmp(name, constants[ii])) {
            return (Constants)ii;
        }   
    }   
    return (Constants)-1;
}   

int main() {
    printf("%s = %d\n", "Monday", str2enum("Monday"));
    printf("%s = %d\n", "Tuesday", str2enum("Tuesday"));
    return 0;
}

You can try other variations of the basic idea.

Ziffusion
  • 8,779
  • 4
  • 29
  • 57
  • 1
    +1. As for a variation, if you use a `struct` to associate the string to the `enum` value, you could `qsort()` the array, and use `bsearch()` to do the lookup. – jxh May 30 '13 at 20:31
  • 1
    Whenever someone mentions "preprocessor tricks", I usually grit my teeth because such associated code is usually challenging to read and very challenging to debug. I understand the appeal of this kind of thing, but have pity for those who have to maintain it. – wallyk May 30 '13 at 20:34
  • 1
    I do like it, but the enumerations are predefined and I'm not allowed to modify them. – Nealon May 30 '13 at 20:34
  • 1
    Yes, the appeal of this approach is in actually being able to define a real enum, and an array of strings, that automatically keep in sync. Otherwise, just use answer by @wallyk. But having this technique in your toolbox can be useful - in spite of what wallyk says above :) It actually makes things more clear and maintainable (once you understand what's going on). And there are other possibilities. Like expanding the include file in the context of a switch statement, to implement dispatchers etc. Kind of like code generation. – Ziffusion May 30 '13 at 20:41
  • @Nealon: I meant for the person who has to work on the code two years after you have left. A standard C programmer is unlikely to have seen this before, so she will be puzzled for some time until understanding it dawns. – wallyk May 30 '13 at 20:52
  • 1
    @wallyk My post went down before I had seen yours, no worries, no judgments were made. – Nealon May 30 '13 at 20:55
1

If you're using straight C, there isnt a "Enum.Parse" equivalent. You'll want to write your own function, comparing the user's string to pre-defined values with strcmp(), and then returning the appropriate enum value.

Another possibility is using an existing "hash map" implementation, or rolling your own - for instance, the one in glib should work for you: https://developer.gnome.org/glib/2.30/glib-Hash-Tables.html

A hash map should be faster than doing a linear search on the possible enum values, if you have a lot of them (for instance, if you were doing something other than the days of the week). A good hash map implementation should be close to O(1) for lookups, instead of O(n) for a linear search.

CmdrMoozy
  • 3,870
  • 3
  • 19
  • 31
  • 1
    I have 500+ plus values across multiple enumerations I'm working with. I'll update my question to reflect that. – Nealon May 30 '13 at 20:09
1

Not really, though if you use a hash function you can setup all of the values of your enum to match a set of hashed strings. You might have to use a more complicated hash if you don't care about case-sensitivity.

This is probably your best solution, since it has lower overhead than strcmp (...). The assignment of an enum value from a string hash does not require repeated string comparisons, etc...

Andon M. Coleman
  • 42,359
  • 2
  • 81
  • 106
  • using a hash would definitely be the least expensive option, and judging from the scale of this, I think that will be the way to go. I'll have to generate separate hash functions for each enumeration. – Nealon May 30 '13 at 20:14
0

That would be a good solution :

enum e_test { a, b, c, END };

enum e_test get_enum_value(char * val) {
    static char const * e_test_str[] = { "a", "b", "c" };
    for (int i = 0; i < END; ++i)
        if (!strcmp(e_test_str[i], val))
            return i;
    return END;
 }
DCMaxxx
  • 2,534
  • 2
  • 25
  • 46