4

I have a set of values in an enum. As an example I chose WEEK_DAY (my actual case has many (50+) values, and they are not-continuous (1000,1001,2000,...)):

typedef enum{
  SUNDAY,
  MONDAY,
  FRIDAY = 6
}WEEK_DAY;

I now want to create a function that, given a WEEK_DAY value, would return its name. I done this by using:

#define STRING_REPLACE(x) #x

char *value_2_name(WEEK_DAY day)
{
       switch(day)
       {
       case SUNDAY:
                       return STRING_REPLACE(SUNDAY);
       case MONDAY:
                       return STRING_REPLACE(MONDAY);
       case FRIDAY:
                       return STRING_REPLACE(FRIDAY);
       }
}

And calling it with printf("%s", value_2_name(6)); / printf("%s", value_2_name(FRIDAY)); would print out "FRIDAY" as expected.

Is there a way to squeeze this function into a one line?

i.e. to somehow make the substitution between parameter WEEK_DAY day and its enum WEEK_DAY counterpart, and then use the STRING_REPLACE?

What I'm looking for is something like: STRING_REPLACE(day_2_WEEK_DAY_enum)

Some enums have forced values so it's not possible to use answers from How to convert enum names to string in c

CIsForCookies
  • 12,097
  • 11
  • 59
  • 124
  • 7
    Unfortunately C doesn't have any [introspection](https://en.wikipedia.org/wiki/Type_introspection) features, so it's not really possible to create a "one line" variant. – Some programmer dude Oct 23 '18 at 11:11
  • 4
    what you can try is to define a lookup table with macros, now the call is 1 line. there are a lot of examples of this on SO: possible dupe: https://stackoverflow.com/questions/9907160/how-to-convert-enum-names-to-string-in-c – Jean-François Fabre Oct 23 '18 at 11:12
  • So my solution would be the best way to achieve what I want? I thought of using `#define` instead of `enum` but I don't see how that would change anything. Using a looktable was another solution I thought of, but the actual number of enums is big, and they are not continuous which would make me use a hashtable I guess, and that does seems easier (and therefore better for my purposes) – CIsForCookies Oct 23 '18 at 11:13
  • the "non continuous" aspect forbids using the excellent solution that I linked to. It's then _not_ a duplicate of this. – Jean-François Fabre Oct 23 '18 at 11:19
  • 1
    Possible duplicate of [How to convert enum names to string in c](https://stackoverflow.com/questions/9907160/how-to-convert-enum-names-to-string-in-c) – KamilCuk Oct 23 '18 at 11:26
  • `Is there a way to squeeze this function into a one line?` Just remove newlines. – KamilCuk Oct 23 '18 at 11:28
  • dupe doesn't work. forced enums @KamilCuk – Jean-François Fabre Oct 23 '18 at 11:28
  • 1
    Looking at your "actual case", there's no way around a run-time look-up. The best you can do is to keep the integer values sorted, in which case the switch can be replaced by a binary search, for a mini-optimization. Keep one table with values and one with strings. Or alternatively a table of structs with both in one item. – Lundin Oct 23 '18 at 14:13

3 Answers3

3

Not really one line, but you can use a standard-compliant C compiler (C99+) and an X macro for this:

#define STRINGIFY(x) #x
#define WEEKDAYS \
    X(SUNDAY,) \
    X(MONDAY,) \
    X(FRIDAY, = 6)

typedef enum {
    #define X(name, init) name init,
    WEEKDAYS
    #undef X
} Weekday;

char *weekday_lookup[] = {
    #define X(name, init) [name] = STRINGIFY(name),
    WEEKDAYS
    #undef X
};

This will mechanistically produce the following code:

typedef enum {

    SUNDAY , MONDAY , FRIDAY = 6,

} Weekday;

char *weekday_lookup[] = {

    [SUNDAY] = "SUNDAY", [MONDAY] = "MONDAY", [FRIDAY] = "FRIDAY",

};

Another way, if a lookup table would become too large or not compilable (too large index or negative values), or if C89 is required, is to build a table of value, name pairs that will be iterated over.


If you don't like writing backlashes or long #defines, you can use an include file for the X-database:

weekday_def.inc:

X(SUNDAY,)
X(MONDAY,)
X(FRIDAY, = 6)

Actual use:

typedef enum {
    #define X(name, init) name init,
    #include "weekday_def.inc"
    #undef X
} Weekday;

char *weekday_lookup[] = {
    #define X(name, init) [name] = STRINGIFY(name),
    #include "weekday_def.inc"
    #undef X
};
  • Just a nit-pick: it might be better to write the X macro as `X(FRIDAY, 6)` and then `#define X(name, init) name = init,` as this gives a more generic x macro list, if you wish to use the initializer for some other purpose elsewhere. – Lundin Oct 23 '18 at 13:58
  • @Lundin that was deliberate - to avoid naming the first 2 - to show that it is possible. After the `enum` type is complete, it is possible to use the name to resolve the value. – Antti Haapala -- Слава Україні Oct 23 '18 at 14:03
  • Hmm well I guess in the OP's "real case" they say that enums might have values that are not adjacent, in which case the designated initializers can't be used. You'd end up with an enormous array. – Lundin Oct 23 '18 at 14:05
2

Your solution is pretty good already as it is simplistic. I guess the problem is when the enums get pretty big. I don't think there is a way to do this in one line except call a function. As 'some programmer dude' said, C doesn't have introspection features. So you've got to make it up yourself. I made an enum structure to do this. It will work with spacing in the enum-- however you may realize how complicated and ridiculous it gets, just to perform this function.

enum.h

#ifndef __ENUM_H__
#define __ENUM_H__

#define null 0x00

#define ENUM_MAX_VALUES 4
#define ENUM_MAX_SCHEMA 4
#define ENUM_MAX_ENUM 4
#define ENUM_MAX_STRING_LEN 16

/**
* enum_key_value_t is essentially a key/value pair
* the key is the integer, the value is the string
*/
typedef struct
{
    /** integer enum value */
    int key;
    /** string enum value */
    char value[ENUM_MAX_STRING_LEN];
}enum_key_value_t;
/**
* An enum schema contains all possible string/int pairs
*/
typedef struct
{
    /** all possible values of the enumerator object */
    enum_key_value_t values[ENUM_MAX_VALUES];
    /** the number of values used out of MAX_ENUM_VALUES */
    int num_values;
}enum_schema_t;

typedef struct
{
    /** current value of the enumerator object */
    enum_key_value_t *value;
    enum_schema_t *schema;
}enum_t;

enum_schema_t *new_EnumSchema(void);

void EnumSchema_AddValue(enum_schema_t *e, int key, const char *value);
enum_key_value_t *Enum_SetValue(enum_t *e, int key);
const char *Enum_GetValue(enum_t *e);
enum_t *new_Enum(enum_schema_t *schema, int initial_value);

#endif

enum.c

#include "enum.h"
#include <string.h>
#include <stdio.h>
/** used in place of null strings etc. */
const char g_UNDEFINED[] = "<<UNDEFINED>>";
/** All enumerator objects */
static enum_schema_t g_EnumSchemas[ENUM_MAX_SCHEMA];
static enum_t g_Enums[ENUM_MAX_ENUM];

/** Current number of enumerator objects */
static int g_num_EnumSchemas = 0;
static int g_num_Enums = 0;

static enum_key_value_t *Enum_FindValue(enum_schema_t *e, int key);

/**
* new_Enum
*
* create a new enumerator
*
* @return pointer to the new enumerator
*/
enum_schema_t *new_EnumSchema(void)
{
    if (g_num_EnumSchemas < ENUM_MAX_SCHEMA)
    {
        enum_schema_t *ret = &g_EnumSchemas[g_num_EnumSchemas++];

        ret->num_values = 0;

        return ret;
    }

    return null;
}

/**
* new_Enum
*
* create a new enumerator
*
* @return pointer to the new enumerator
*/
enum_t *new_Enum(enum_schema_t *schema, int initial_value)
{
    if (g_num_Enums < ENUM_MAX_ENUM)
    {
        enum_t *ret = &g_Enums[g_num_Enums++];

        ret->schema = schema;
        ret->value = Enum_FindValue(schema, initial_value);

        return ret;
    }

    return null;
}
/**
* Enum_AddValue
*
* adds a value/key key to a enumerator object
*
* @param e pointer to the enumerator object
* @param key the enumerated byte key
* @param value the value to show for this key
*/
void EnumSchema_AddValue(enum_schema_t *e, int key, const char *value)
{
    if (e->num_values < ENUM_MAX_VALUES)
    {
        int i;
        enum_key_value_t *val = &e->values[e->num_values++];

        val->key = key;

        strncpy(val->value, value, ENUM_MAX_STRING_LEN - 1);

        val->value[ENUM_MAX_STRING_LEN - 1] = 0;
    }
}
/**
* Enum_SetValue
*
* changes the enumerated key
*
* @param e pointer to the enumerator object
* @param key the new enumerated byte key
* @return pointer to the enum_key_value_t object that contains the key
*/
enum_key_value_t *Enum_SetValue(enum_t *e, int key)
{
    enum_key_value_t *val = Enum_FindValue(e->schema, key);

    if (val != null)
    {
        e->value = val;

        return val;
    }

    return null;
}
/**
* Enum_GetValue
*
* gets the enumerated value key for enumerated key
*
* @param e pointer to the enumerator object
* @return value key
*/
const char *Enum_GetValue(enum_t *e)
{
    if (e->value != null)
        return e->value->value;

    return g_UNDEFINED;
}

/*************************************
* STATIC FUNCTIONS (Local functions)
*************************************/

/**
* Enum_FindValue
*
* finds the enumerated key
*
* @param e pointer to the enumerator object
* @param key the enumerated byte key
* @return pointer to enum_key_value_t object that contains the key
*/
static enum_key_value_t *Enum_FindValue(enum_schema_t *e, int key)
{
    int i;

    for (i = 0; i < e->num_values; i++)
    {
        enum_key_value_t *val = &e->values[i];

        if (val->key == key)
            return val;
    }

    return null;
}

main.c

#include <stdio.h>

#include "enum.h"

typedef enum
{
    SUNDAY,
    MONDAY,
    FRIDAY = 6
}WEEK_DAY;

enum_schema_t *week_day_init()
{
    enum_schema_t *enum_weekday = new_EnumSchema();

    // add possible values
    EnumSchema_AddValue(enum_weekday, SUNDAY, "SUNDAY");
    EnumSchema_AddValue(enum_weekday, MONDAY, "MONDAY");
    EnumSchema_AddValue(enum_weekday, FRIDAY, "FRIDAY");

    return enum_weekday;
}

void main()
{
    enum_schema_t *week_day_enum_t = week_day_init();

    enum_t *weekday1 = new_Enum(week_day_enum_t, SUNDAY);

    // the 'one-liner'
    printf("weekday1 is currently '%s'\n",Enum_GetValue(weekday1));

    Enum_SetValue(weekday1, FRIDAY);

    printf("weekday1 is now '%s'\n", Enum_GetValue(weekday1));

}

output

weekday1 is currently 'SUNDAY'
weekday1 is now 'FRIDAY'
pm101
  • 1,309
  • 10
  • 30
  • 1
    I think you can improve `EnumSchema_AddValue(enum_weekday, SUNDAY, "SUNDAY")` with a macro to stringify the enum: `#define ENUMADD(enum_weekday,value) EnumSchema_AddValue(enum_weekday, value, #value)` – Jean-François Fabre Oct 23 '18 at 11:41
  • 2
    the other issue is: a `switch` statement can have a `O(1)` complexity with jump tables, etc... your solution has a `O(n)` complexity, performing linear search in a loop – Jean-François Fabre Oct 23 '18 at 11:43
1

Ugly, un-maintainable and not efficient, but does exactly what you asked for:

#define ENUMS()  \
ENTRY(SUNDAY, 0) \
ENTRY(MONDAY, 1) \
ENTRY(FRIDAY, 6)

typedef enum{
#define ENTRY(_enum, _val) _enum = _val,
ENUMS()
#undef ENTRY
}WEEK_DAY;

#define MAX_STR_LEN 7
char days_str[][MAX_STR_LEN]={
#define ENTRY(_enum, _val) #_enum,
ENUMS()
#undef ENTRY
};

char* value_2_name(WEEK_DAY day)
{
    return days_str[day - ((1U - (((unsigned int)(day - sizeof(days_str)/MAX_STR_LEN))>>31))
                                    *
                                  (day - (sizeof(days_str)/MAX_STR_LEN) ) )
                        - (1U - (((unsigned int)(day - sizeof(days_str)/MAX_STR_LEN))>>31))] ; 
}

It supports non continuous enum values as depicted, it uses minimal string arrays- the char* array in this example is of size 21 bytes, contains only 3 strings, no "holes" (the reason for the calculation of the array index) but should not be used by humans.

izac89
  • 3,790
  • 7
  • 30
  • 46