Good way to have an enum string value

Viewed 101

I find myself doing the following pattern quite a bit in C:

enum type {String, Number, Unknown};

const char* get_token_type(int type) {
    switch (type) {
        case String: return "String";
        case Number: return "Number";
        default: return "Unknown";
    }
}

Are there any nice alternatives where I can sort of wrap the two up in one?


Here's one more way, it uses a global and not sure how 'clean' it is but at least I can set up everything up at the top in one copy-paste job.

enum type                {String,  Number,   Unknown};
char type_string[][40] = {"String", "Number","Unknown"};

const char* get_token_type(int type) {
    return type_string[type];
}
3 Answers

The ugly and slow switch solution should be replaced by a readable a look-up table:

const char* const type_string[] = {"String", "Number","Unknown"};

typedef enum {String,  Number, Unknown, type_size} type;

...
type something = ...;
if(something > 0 && something<type_size)
  puts( type_string[something] );

A less recommended but possible solution is "X macros". They are hard to read and mostly used when you have no option to change a program that you are maintaining, in order to centralize all data to a single place in the program.

It would look like this:

#include <stdio.h>

#define TYPE_LIST(X) \
  X(String)          \
  X(Number)          \
  X(Unknown)         \

typedef enum 
{
  #define X(x) x,
    TYPE_LIST(X)
  #undef X
  type_size
} type;

const char* const type_string[] = 
{
  #define X(x) [x] = #x,  // array designated initializer using enum constant as index
    TYPE_LIST(X)
  #undef X
};

_Static_assert(sizeof type_string/sizeof *type_string == type_size, 
               "type and type_string mismatch");

int main (void)
{
  printf("%d %d %d\n", String, Number, Unknown);
  printf("%s %s %s\n", type_string[String],
                       type_string[Number], 
                       type_string[Unknown]);

  // or if you really like X macros...
  #define X(x) printf("%d is %s\n", x, #x);
    TYPE_LIST(X)
  #undef X
}

(This only works in standard C, not C90, due to the trailing comma in enum language defect in C90)

Here is a set of macros you can use to access and enumerate the enumeration names and/or values:

#include <stdio.h>
#include <string.h>

#define ENUM_DEF(e, ...)    e __VA_ARGS__,
#define ENUM_VAL(e, ...)    e,
#define ENUM_STR(e, ...)    #e,
#define ENUM_CASE(e, ...)   case e: return #e;
#define ENUM_COUNT(e, ...)  +1

/* this macro contains the names and values of the enumeration members */
#define type_ENUMERATE(V)  V(String,) V(Number, = 2) V(Unknown,)

/* the actual enum type definition */
enum type { type_ENUMERATE(ENUM_DEF) };

/* array type_value contains the values in the enumeration */
enum type type_value[] = { type_ENUMERATE(ENUM_VAL) };

/* array type_value contains the names of the enumeration members */
const char *type_name[] = { type_ENUMERATE(ENUM_STR) };

/* type_count is the number of elements in the enumeration */
#define type_count (ENUM_COUNT(e, ...))

const char *get_token_type(int type) {
    switch (type) {
        /* can only use a switch if all enumeration values are distinct */
        type_ENUMERATE(ENUM_CASE);
    default:
        return "Error";
    }
}

int main() {
    printf("The type names and values are:\n");
    /* format %d might be inappropriate if the enumeration values are too large */
    for (int i = 0; i < type_count; i++) {
        printf(" %s: %d\n", type_name[i], type_value[i]);
    }
    printf("String: %s -> %d\n", get_token_type(String), String);
    printf("Number: %s -> %d\n", get_token_type(Number), Number);
    printf("Unknown: %s -> %d\n", get_token_type(Unknown), Unknown);
    printf("-1: %s -> %d\n", get_token_type(-1), -1);
    return 0;
}

This works only for enums which are continously enumerated. If you have enums like V1 = 2, V2 = 5, ... it will not work, or you would have to manullay count them.

In this case you will get a compiletime error when you add or remove an enum, and you forgot to add/remove the name string. If you rename an enum you will get also an error, because the previous name no longer exits.

#include <stdio.h>
#include <string.h>

#define xstr(s) TOSTRING(s)
#define TOSTRING(s) #s

typedef enum
{
    VALUE1
    ,VALUE2
    ,VALUE3
    ,VALUE_MAX
} Values;


typedef struct namemap
{
    const char *name;
    Values enumValue;
} NameMap;

const NameMap valueNames[] =
{
    { TOSTRING(VALUE1), __COUNTER__ }
    , { TOSTRING(VALUE2), __COUNTER__ }
    , { TOSTRING(VALUE3), __COUNTER__ }
};

static_assert((__COUNTER__) == VALUE_MAX, "Name missing in enum name array");

int main()
{
    printf("%s\n", valueNames[VALUE1].name);
    return 0;
}
Related