Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

A proper way of associating enums with strings

Tags:

c

enums

c-strings

Let's say I have a number of strings I use often throughout my program (to store state and things like that). String operations can be expensive, so whenever addressing them I'd like to use an enumeration. I've seen a couple solutions so far:

typedef enum {
    STRING_HELLO = 0,
    STRING_WORLD
} string_enum_type;

// Must be in sync with string_enum_type
const char *string_enumerations[] = {
    "Hello",
    "World"
}

The other one I encounter quite often:

typedef enum {
    STRING_HELLO,
    STRING_WORLD
} string_enum_type;

const char *string_enumerations[] = {
    [STRING_HELLO] = "Hello",
    [STRING_WORLD] = "World"
}

What are cons/pros of these two methods? Is there a better one?

like image 902
PoVa Avatar asked Feb 26 '18 10:02

PoVa


People also ask

How do I assign an enum to a string?

There are two ways to convert an Enum to String in Java, first by using the name() method of Enum which is an implicit method and available to all Enum, and second by using toString() method.

How do you use enums correctly?

You should always use enums when a variable (especially a method parameter) can only take one out of a small set of possible values. Examples would be things like type constants (contract status: “permanent”, “temp”, “apprentice”), or flags (“execute now”, “defer execution”).

How do you store strings in enum?

Steps to followFirst create a sample set of enum variables. Then, create a custom attribute class for just custom String Value. Create an extension method for just enum. Use the extension method to get just enum custom string value.

Can enums be strings?

No they cannot. They are limited to numeric values of the underlying enum type.


2 Answers

The only advantage with the former is that it's backwards-compatible with ancient C standards.

Apart from that, the latter alternative is superior, as it ensures data integrity even if the enum is modified or items change places. However, it should be completed with a check to ensure that the number of items in the enum corresponds with the number of items in the look-up table:

typedef enum {
    STRING_HELLO,
    STRING_WORLD,
    STRING_N  // counter
} string_enum_type;

const char *string_enumerations[] = {
    [STRING_HELLO] = "Hello",
    [STRING_WORLD] = "World"
};

_Static_assert(sizeof string_enumerations/sizeof *string_enumerations == STRING_N,
               "string_enum_type does not match string_enumerations");

The above is the best method for a simple "enum - lookup table" coupling. Another option would be to use structs, but that's more suitable for more complex data types.


And finally, more as a side-note, the 3rd version would be to use "X macros". This is not recommended unless you have specialized requirements regarding code repetition and maintenance. I'll include it here for completeness, but I don't recommend it in the general case:

#define STRING_LIST          \
 /* index         str    */  \
  X(STRING_HELLO, "Hello")   \
  X(STRING_WORLD, "World")


typedef enum {
  #define X(index, str) index,
    STRING_LIST
  #undef X
  STRING_N // counter
} string_enum_type;


const char *string_enumerations[] = {
  #define X(index, str) [index] = str,
    STRING_LIST
  #undef X
};

_Static_assert(sizeof string_enumerations/sizeof *string_enumerations == STRING_N,
               "string_enum_type does not match string_enumerations");
like image 59
Lundin Avatar answered Oct 01 '22 01:10

Lundin


Another possibility might be to use a function, instead of an array:

const char *enumtostring(string_enum_type e) {
    switch(e) {
        case STRING_HELLO: return "hello";
        case STRING_WORLD: return "world";
    }
}

gcc, at least, will warn if you add an enum value but forget to add the matching switch case.

(I suppose you could try making this sort of function inline, as well.)


Addendum: The gcc warning I mentioned applies only if the switch statement does not have a default case. So if you want to print something for out-of-bounds values that somehow creep through, you could do that, not with a default case, but with something like this:

const char *enumtostring(string_enum_type e) {
    switch(e) {
        case STRING_HELLO: return "hello";
        case STRING_WORLD: return "world";
    }
    return "(unrecognized string_enum_type value)";
}

It's also nice to include the out-of-bounds value:

    static char tmpbuf[50];
    snprintf(tmpbuf, sizeof(tmpbuf), "(unrecognized string_enum_type value %d)", e);
    return tmpbuf;

(This last fragment has a couple of additional limitations, but this addendum is getting long already, so I won't belabor the point with them just now.)

like image 27
Steve Summit Avatar answered Oct 01 '22 00:10

Steve Summit