2

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];
}
carl.hiass
  • 1,526
  • 1
  • 6
  • 26
  • It's a kinda weird question. Do you want to be able to translate in both directions or just one? Because if all you need to do is turn a enum into a string, then a simple array is fine: `const char * get_token_type(int type) { static const char *types[] = { "String", "Number", "Unknown" }; return types[type]; }` – paddy Mar 24 '21 at 07:03
  • Use an union in combination with a struct and using `__attribute__((__packed__))` at the struct. – paladin Mar 24 '21 at 07:04
  • @paddy yes, just a one-way lookup from the enum value (int) to the string. – carl.hiass Mar 24 '21 at 07:04
  • @paladin I've never heard of that approach, want to show a basic example in an answer? – carl.hiass Mar 24 '21 at 07:04
  • I think they are assuming you're talking about some kind of variant data structure. Again, this highlights the ambiguity of your question. – paddy Mar 24 '21 at 07:05
  • 1
    This asks for [x-macros](https://en.wikipedia.org/wiki/X_Macro). – HolyBlackCat Mar 24 '21 at 07:09
  • @paddy updated to remove the struct – carl.hiass Mar 24 '21 at 07:09
  • 1
    X-macros don't solve what the OP apparently wants to do, which seems to be writing each word once in code. If that truly is important (e.g. there's some _enormous_ set of enums to consider) then auto-generation of code might be the answer. Otherwise just suppress your desires for beauty and do what the rest of us do... Make a table, synchronize it with your enums, leave a comment to ensure future modifications know that values must be added in a second place, and move on. – paddy Mar 24 '21 at 07:12
  • 1
    Does this solve it? https://stackoverflow.com/questions/9907160/how-to-convert-enum-names-to-string-in-c – nielsen Mar 24 '21 at 07:13
  • @paddy makes sense, thank you. When you say 'table' do you mean something like the second appraoch I have above? – carl.hiass Mar 24 '21 at 07:13
  • Yeah, or the approach that I wrote as an inline code example in an earlier comment, which is essentially that... but with less wasted space. – paddy Mar 24 '21 at 07:14
  • @nielsen I suppose, although that makes it even more difficult / harder to follow if anything. – carl.hiass Mar 24 '21 at 07:14
  • @paddy *"X-macros don't solve what the OP apparently wants to do, which seems to be writing each word once"* How so? What about neilsen's link? – HolyBlackCat Mar 24 '21 at 07:18
  • Yeah, I was just responding. That's quite a cool X-macro solution and I take back my comment about them not being useful. However, if you feel that this makes the code more complex, then you 100% have a stupidly simple problem to solve and you're overthinking it. These kinds of solutions are useful in more specialized production environments. – paddy Mar 24 '21 at 07:18
  • If you create a table, I would use a static_assert to verify that the table matches the enums. Otherwise it's easy to forget to update the table when the enums are changing. This wont protect against renaming, but at least out of bounds or wrong index. – Devolus Mar 24 '21 at 07:26
  • @Devolus would you want to post a basic answer showing what you mean by the static assert method? Would it be static_assert(sizeof enum == sizeof enum_strings) ? – carl.hiass Mar 24 '21 at 07:32
  • Does this answer your question? [How to convert enum names to string in c](https://stackoverflow.com/questions/9907160/how-to-convert-enum-names-to-string-in-c) – the busybee Mar 24 '21 at 08:37
  • I posted an example how this could be done. – Devolus Mar 24 '21 at 10:41

3 Answers3

1

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)

Lundin
  • 195,001
  • 40
  • 254
  • 396
1

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;
}
chqrlie
  • 131,814
  • 10
  • 121
  • 189
  • 1
    And here I thought my X macro solution was hard to read :) You should probably clarify that the advantage of this solution is that you allow any enum values. I would have solved that by adding the value as another parameter to the X macro list though. – Lundin Mar 24 '21 at 14:32
  • Modified example of my original code: https://godbolt.org/z/MPPahsh6K. Introducing an extra enum just for the purpose of counting the number of items and enumerating the strings. – Lundin Mar 24 '21 at 14:38
  • @chqlie -- thanks for this answer. Could you just explain all the macro/#defines a bit? Some of them seem a bit tricky for me to understand. – carl.hiass Mar 24 '21 at 16:19
  • @Lundin: good alternative, but then you need to specify all enum values. My approach is less readable but more general. – chqrlie Mar 24 '21 at 17:35
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;
}
Devolus
  • 21,661
  • 13
  • 66
  • 113
  • Why not place the `,` at the end of each line, including the last one of the enumeration? – chqrlie Mar 24 '21 at 10:51
  • That's just my habit because it often makes removing or copy pasting easier – Devolus Mar 24 '21 at 10:58
  • `__COUNTER__` isn't standard C. And well, this solution is about as clunky as X macros in my opinion, so you might as well use X macros. – Lundin Mar 24 '21 at 10:59
  • 1
    Regarding the `,` is is fine to use it at the end of an array initializer list or enum. Didn't use to be like that back in C90 but the language has been fixed. – Lundin Mar 24 '21 at 10:59
  • I think all solutions for this problem are quite clunky. :) I like it, because you get compiletime errors instead of runtime errors. – Devolus Mar 24 '21 at 11:00
  • @Lundin , But I like your solution better, because it doesn't need the struct. – Devolus Mar 24 '21 at 11:02
  • Well... on the other hand, good program design would kind of call for the struct. Depends on how you intend to use the data. If used as key-value pair then it probably should have been a struct. – Lundin Mar 24 '21 at 11:05
  • @Lundin, yes. The advatnage of my approach is, that you can add additional information along with it, which often might be useful, depending on the usecase. – Devolus Mar 24 '21 at 11:07
  • @Devolus what is `__counter__` ? I've never seen that before, do you want to add that to your answer. – carl.hiass Mar 24 '21 at 16:19