Given trivial typedef
-free ANSI C code, how does one implement an X-macro to define everything in one place?
enum Foo {
CAN, BAR, UNKNOWN
};
const char *Foo_to_str(enum Foo foo) {
switch (foo) {
case CAN:
return "Can";
case BAR:
return "Bar is here";
case UNKNOWN:
/* `default:` like to keep this commented
out so compiler warns that case is missed */
return "UNKNOWN";
}
}
enum Foo Foo_from_str(const char *s) {
if (strcmp(s, "Can")) return CAN;
else if (strcmp(s, "Bar is here")) return BAR;
else return UNKNOWN;
}
I started to mess around with arrays but not sure if I would be better served by a {{FOO, "Foo"}}
style array, also I don't need the actual array so not sure if that's a good approach anyway:
#define FOOX \
X(FOO, "Foo") \
X(BAR, "Bar is here")
enum Foo{
#define X(_, N) N,
FOOX
#undef X
};
char const *const FooA[] = {
#define X(N, _) #N,
FooE
#undef X
};
const char* Foo_to_str(enum FooE foo) {
return FooA[foo];
}
(would be great to separate interface and implementation in a header and source; but that might be a little ambitious considering macro usage)
#define X(N, _) #N
Here you are making an array of the name parameter converted to a string, rather than returning the provided string. That is "BAR"
rather than "Bar is here"
. Other than that the code looks mostly ok.
I would recommend to use this style:
#define FOO_LIST(X) \
/*name, str */ \
X(FOO, "Foo") \
X(BAR, "Bar is here")
Passing the X
as macro parameter removes the need to define some mysterious X
all over the place and later #undef
it. Instead you can create situation-specific macros with unique names. Another advantage with this style is that if you have multiple X macro list data sets with the same parameters, you could reuse those situation-specific macros across multiple lists.
A complete example would be something like this:
#include <string.h>
#include <stdio.h>
#define FOO_LIST(X) \
/*name, str */ \
X(FOO, "Foo") \
X(BAR, "Bar is here")
enum Foo {
#define FOO_ENUM(name, str) name,
FOO_LIST(FOO_ENUM)
FOO_N, // number of items in the enum
FOO_ERROR = -1 // some error code
};
const char* const FooA[] = {
#define FOO_STR(name, str) str,
FOO_LIST(FOO_STR)
};
const char* Foo_to_str(enum Foo f) {
return FooA[f];
}
enum Foo Foo_from_str(const char* s) {
/*
This is a naive search algorithm calling strcmp repeatedly in a
nested list of ?: operators, ending with a -1 if nothing was found.
*/
#define FOO_STRCMP(name, str) (strcmp(str,s)==0) ? name :
return FOO_LIST(FOO_STRCMP) FOO_ERROR;
}
int main()
{
puts(Foo_to_str(FOO));
puts(Foo_to_str(BAR));
printf("%d\n", Foo_from_str("Foo"));
printf("%d\n", Foo_from_str("Bar is here"));
printf("%d\n", Foo_from_str("Bad input"));
}