How to convert enum names to string in c

CStringEnums

C Problem Overview


Is there a possibility to convert enumerator names to string in C?

C Solutions


Solution 1 - C

One way, making the preprocessor do the work. It also ensures your enums and strings are in sync.

#define FOREACH_FRUIT(FRUIT) \
        FRUIT(apple)   \
        FRUIT(orange)  \
        FRUIT(grape)   \
        FRUIT(banana)  \

#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,

enum FRUIT_ENUM {
    FOREACH_FRUIT(GENERATE_ENUM)
};

static const char *FRUIT_STRING[] = {
    FOREACH_FRUIT(GENERATE_STRING)
};

After the preprocessor gets done, you'll have:

enum FRUIT_ENUM {
    apple, orange, grape, banana,
};

static const char *FRUIT_STRING[] = {
    "apple", "orange", "grape", "banana",
};

Then you could do something like:

printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);

If the use case is literally just printing the enum name, add the following macros:

#define str(x) #x
#define xstr(x) str(x)

Then do:

printf("enum apple as a string: %s\n", xstr(apple));

In this case, it may seem like the two-level macro is superfluous, however, due to how stringification works in C, it is necessary in some cases. For example, let's say we want to use a #define with an enum:

#define foo apple

int main() {
    printf("%s\n", str(foo));
    printf("%s\n", xstr(foo));
}

The output would be:

foo
apple

This is because str will stringify the input foo rather than expand it to be apple. By using xstr the macro expansion is done first, then that result is stringified.

See Stringification for more information.

Solution 2 - C

In a situation where you have this:

enum fruit {
    apple, 
    orange, 
    grape,
    banana,
    // etc.
};

I like to put this in the header file where the enum is defined:

static inline char *stringFromFruit(enum fruit f)
{
    static const char *strings[] = { "apple", "orange", "grape", "banana", /* continue for rest of values */ };

    return strings[f];
}

Solution 3 - C

I found a C preprocessor trick that is doing the same job without declaring a dedicated array string (Source: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en).

Sequential enums

Following the invention of Stefan Ram, sequential enums (without explicitely stating the index, e.g. enum {foo=-1, foo1 = 1}) can be realized like this genius trick:

#include <stdio.h>

#define NAMES C(RED)C(GREEN)C(BLUE)
#define C(x) x,
enum color { NAMES TOP };
#undef C

#define C(x) #x,    
const char * const color_name[] = { NAMES };

This gives the following result:

int main( void )  { 
    printf( "The color is %s.\n", color_name[ RED ]);  
    printf( "There are %d colors.\n", TOP ); 
}

> The color is RED.
> There are 3 colors.

Non-Sequential enums

Since I wanted to map error codes definitions to are array string, so that I can append the raw error definition to the error code (e.g. "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."), I extended the code in that way that you can easily determine the required index for the respective enum values:

#define LOOPN(n,a) LOOP##n(a)
#define LOOPF ,
#define LOOP2(a) a LOOPF a LOOPF
#define LOOP3(a) a LOOPF a LOOPF a LOOPF
#define LOOP4(a) a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP5(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP6(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP7(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP8(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP9(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF


#define LC_ERRORS_NAMES \
	Cn(LC_RESPONSE_PLUGIN_OK, -10) \
	Cw(8) \
	Cn(LC_RESPONSE_GENERIC_ERROR, -1) \
	Cn(LC_FT_OK, 0) \
	Ci(LC_FT_INVALID_HANDLE) \
	Ci(LC_FT_DEVICE_NOT_FOUND) \
	Ci(LC_FT_DEVICE_NOT_OPENED) \
	Ci(LC_FT_IO_ERROR) \
	Ci(LC_FT_INSUFFICIENT_RESOURCES) \
	Ci(LC_FT_INVALID_PARAMETER) \
	Ci(LC_FT_INVALID_BAUD_RATE) \
	Ci(LC_FT_DEVICE_NOT_OPENED_FOR_ERASE) \
	Ci(LC_FT_DEVICE_NOT_OPENED_FOR_WRITE) \
	Ci(LC_FT_FAILED_TO_WRITE_DEVICE) \
	Ci(LC_FT_EEPROM_READ_FAILED) \
	Ci(LC_FT_EEPROM_WRITE_FAILED) \
	Ci(LC_FT_EEPROM_ERASE_FAILED) \
	Ci(LC_FT_EEPROM_NOT_PRESENT) \
	Ci(LC_FT_EEPROM_NOT_PROGRAMMED) \
	Ci(LC_FT_INVALID_ARGS) \
	Ci(LC_FT_NOT_SUPPORTED) \
	Ci(LC_FT_OTHER_ERROR) \
	Ci(LC_FT_DEVICE_LIST_NOT_READY)


#define Cn(x,y) x=y,
#define Ci(x) x,
#define Cw(x)
enum LC_errors { LC_ERRORS_NAMES TOP };
#undef Cn
#undef Ci
#undef Cw
#define Cn(x,y) #x,
#define Ci(x) #x,
#define Cw(x) LOOPN(x,"")
static const char* __LC_errors__strings[] = { LC_ERRORS_NAMES };
static const char** LC_errors__strings = &__LC_errors__strings[10];

In this example, the C preprocessor will generate the following code:

enum LC_errors { LC_RESPONSE_PLUGIN_OK=-10,  LC_RESPONSE_GENERIC_ERROR=-1, LC_FT_OK=0, LC_FT_INVALID_HANDLE, LC_FT_DEVICE_NOT_FOUND, LC_FT_DEVICE_NOT_OPENED, LC_FT_IO_ERROR, LC_FT_INSUFFICIENT_RESOURCES, LC_FT_INVALID_PARAMETER, LC_FT_INVALID_BAUD_RATE, LC_FT_DEVICE_NOT_OPENED_FOR_ERASE, LC_FT_DEVICE_NOT_OPENED_FOR_WRITE, LC_FT_FAILED_TO_WRITE_DEVICE, LC_FT_EEPROM_READ_FAILED, LC_FT_EEPROM_WRITE_FAILED, LC_FT_EEPROM_ERASE_FAILED, LC_FT_EEPROM_NOT_PRESENT, LC_FT_EEPROM_NOT_PROGRAMMED, LC_FT_INVALID_ARGS, LC_FT_NOT_SUPPORTED, LC_FT_OTHER_ERROR, LC_FT_DEVICE_LIST_NOT_READY, TOP };

static const char* __LC_errors__strings[] = { "LC_RESPONSE_PLUGIN_OK", "" , "" , "" , "" , "" , "" , "" , "" "LC_RESPONSE_GENERIC_ERROR", "LC_FT_OK", "LC_FT_INVALID_HANDLE", "LC_FT_DEVICE_NOT_FOUND", "LC_FT_DEVICE_NOT_OPENED", "LC_FT_IO_ERROR", "LC_FT_INSUFFICIENT_RESOURCES", "LC_FT_INVALID_PARAMETER", "LC_FT_INVALID_BAUD_RATE", "LC_FT_DEVICE_NOT_OPENED_FOR_ERASE", "LC_FT_DEVICE_NOT_OPENED_FOR_WRITE", "LC_FT_FAILED_TO_WRITE_DEVICE", "LC_FT_EEPROM_READ_FAILED", "LC_FT_EEPROM_WRITE_FAILED", "LC_FT_EEPROM_ERASE_FAILED", "LC_FT_EEPROM_NOT_PRESENT", "LC_FT_EEPROM_NOT_PROGRAMMED", "LC_FT_INVALID_ARGS", "LC_FT_NOT_SUPPORTED", "LC_FT_OTHER_ERROR", "LC_FT_DEVICE_LIST_NOT_READY", };

This results to the following implementation capabilities:

> LC_errors__strings[-1] > ==> LC_errors__strings[LC_RESPONSE_GENERIC_ERROR] > ==> "LC_RESPONSE_GENERIC_ERROR"

Solution 4 - C

You don't need to rely on the preprocessor to ensure your enums and strings are in sync. To me using macros tend to make the code harder to read.

Using Enum And An Array Of Strings

enum fruit                                                                   
{
    APPLE = 0, 
    ORANGE, 
    GRAPE,
    BANANA,
    /* etc. */
    FRUIT_MAX                                                                                                                
};   

const char * const fruit_str[] =
{
    [BANANA] = "banana",
    [ORANGE] = "orange",
    [GRAPE]  = "grape",
    [APPLE]  = "apple",
    /* etc. */  
};

Note: the strings in the fruit_str array don't have to be declared in the same order as the enum items.

How To Use It

printf("enum apple as a string: %s\n", fruit_str[APPLE]);

Adding A Compile Time Check

If you are afraid to forget one string, you can add the following check:

#define ASSERT_ENUM_TO_STR(sarray, max) \                                       
  typedef char assert_sizeof_##max[(sizeof(sarray)/sizeof(sarray[0]) == (max)) ? 1 : -1]

ASSERT_ENUM_TO_STR(fruit_str, FRUIT_MAX);

An error would be reported at compile time if the amount of enum items does not match the amount of strings in the array.

Solution 5 - C

There is no simple way to achieves this directly. But P99 has macros that allow you to create such type of function automatically:

 P99_DECLARE_ENUM(color, red, green, blue);

in a header file, and

 P99_DEFINE_ENUM(color);

in one compilation unit (.c file) should then do the trick, in that example the function then would be called color_getname.

Solution 6 - C

An simpler alternative to Hokyo's "Non-Sequential enums" answer, based on using designators to instantiate the string array:

#define NAMES C(RED, 10)C(GREEN, 20)C(BLUE, 30)
#define C(k, v) k = v,
enum color { NAMES };
#undef C

#define C(k, v) [v] = #k,    
const char * const color_name[] = { NAMES };

Solution 7 - C

A function like that without validating the enum is a trifle dangerous. I suggest using a switch statement. Another advantage is that this can be used for enums that have defined values, for example for flags where the values are 1,2,4,8,16 etc.

Also put all your enum strings together in one array:-

static const char * allEnums[] = {
    "Undefined",
    "apple",
    "orange"
    /* etc */
};

define the indices in a header file:-

#define ID_undefined       0
#define ID_fruit_apple     1
#define ID_fruit_orange    2
/* etc */

Doing this makes it easier to produce different versions, for example if you want to make international versions of your program with other languages.

Using a macro, also in the header file:-

#define CASE(type,val) case val: index = ID_##type##_##val; break;

Make a function with a switch statement, this should return a const char * because the strings static consts:-

const char * FruitString(enum fruit e){

	unsigned int index;

    switch(e){
	    CASE(fruit, apple)
	    CASE(fruit, orange)
	    CASE(fruit, banana)
        /* etc */
        default: index = ID_undefined;
    }
    return allEnums[index];
}

If programming with Windows then the ID_ values can be resource values.

(If using C++ then all the functions can have the same name.

string EnumToString(fruit e);

)

Solution 8 - C

I usually do this:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   

Solution 9 - C

I settled on making a function whose body is updated by copying the enum over and using a regex in Vim. I use a switch-case because my enum isn't compact so we have maximum flexibility. I keep the regex as a comment in the code so it's just a matter of copy-pasting it.

My enum (shortened, the real one is much bigger):

enum opcode
{
	op_1word_ops = 1024,
	op_end,

	op_2word_ops = 2048,
	op_ret_v,
	op_jmp,

	op_3word_ops = 3072,
	op_load_v,
	op_load_i,

	op_5word_ops = 5120,
	op_func2_vvv,
};

The function before copying the enum over:

const char *get_op_name(enum opcode op)
{
	// To update copy the enum and apply this regex:
	// s/\t\([^, ]*\).*$/\t\tcase \1:    \treturn "\1";
	switch (op)
	{
	}

	return "Unknown op";
}

I paste the contents of the enum inside the switch brackets:

const char *get_op_name(enum opcode op)
{
	// To update copy the enum and apply this regex:
	// s/\t\([^, ]*\).*$/\t\tcase \1:    \treturn "\1";
	switch (op)
	{
	op_1word_ops = 1024,
	op_end,

	op_2word_ops = 2048,
	op_ret_v,
	op_jmp,

	op_3word_ops = 3072,
	op_load_v,
	op_load_i,

	op_5word_ops = 5120,
	op_func2_vvv,
	}

	return "Unknown op";
}

Then with Shift-V in Vim I select the lines, press : then paste (Ctrl-V on Windows) the regex s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1"; and press Enter:

const char *get_op_name(enum opcode op)
{
	// To update copy the enum and apply this regex:
	// s/\t\([^, ]*\).*$/\t\tcase \1:    \treturn "\1";
	switch (op)
	{
		case op_1word_ops:    	return "op_1word_ops";
		case op_end:    	return "op_end";

		case op_2word_ops:    	return "op_2word_ops";
		case op_ret_v:    	return "op_ret_v";
		case op_jmp:    	return "op_jmp";

		case op_3word_ops:    	return "op_3word_ops";
		case op_load_v:    	return "op_load_v";
		case op_load_i:    	return "op_load_i";

		case op_5word_ops:    	return "op_5word_ops";
		case op_func2_vvv:    	return "op_func2_vvv";
	}

	return "Unknown op";
}

The regex skips the first \t character, then puts every character that follows that is neither , nor into \1 and matches the rest of the line to delete everything. Then with \1 being the enum label it remakes the lines in the case <label>: return "<label>"; format. Note that it looks poorly aligned in this post only because StackOverflow uses 4-space tabulation whereas in Vim I use 8-space tabulation, so you might want to edit the regex for style.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMishaView Question on Stackoverflow
Solution 1 - CTerrence MView Answer on Stackoverflow
Solution 2 - CRichard J. Ross IIIView Answer on Stackoverflow
Solution 3 - CMaschinaView Answer on Stackoverflow
Solution 4 - CjyvetView Answer on Stackoverflow
Solution 5 - CJens GustedtView Answer on Stackoverflow
Solution 6 - CLarsView Answer on Stackoverflow
Solution 7 - CQuentinUKView Answer on Stackoverflow
Solution 8 - CgigilibalaView Answer on Stackoverflow
Solution 9 - CMichel RouzicView Answer on Stackoverflow