Is there a possibility to convert enumerator names to string in C?
8 Answers
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.
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];
}
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"
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
.
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.
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);
)