Creating a string list and an enum list from a C++ macro
Solution 1
Here a solution I learned a few days ago. The simplified version that attends your question is:
#define ENUM_MACRO(name, v1, v2, v3, v4, v5, v6, v7)\
enum name { v1, v2, v3, v4, v5, v6, v7};\
const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7};
ENUM_MACRO(Week, Sun, Mon, Tue, Wed, Thu, Fri, Sat);
But you can have an improved version, with a function call, like this:
#define ENUM_MACRO(name, v1, v2, v3, v4, v5, v6, v7)\
enum name { v1, v2, v3, v4, v5, v6, v7};\
const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7};\
const char *name##ToString(value) { return name##Strings[value]; }
ENUM_MACRO(Week, Sun, Mon, Tue, Wed, Thu, Fri, Sat);
This will grow to be:
enum Week { Sun, Mon, Tue, Wed, Thu, Fri, Sat};
const char *WeekStrings[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
const char *WeekToString(value) { return WeekStrings[value]; };
You can even use an offset for the first element, like this one:
#define ENUM_MACRO(name, offset, v1, v2, v3, v4, v5, v6, v7)\
enum name { v1 = offset, v2, v3, v4, v5, v6, v7};\
const char *name##Strings[] = { #v1, #v2, #v3, #v4, #v5, #v6, #v7};\
const char *name##ToString(value) { return name##Strings[value - offset ]; }
ENUM_MACRO(Week, 1, Sun, Mon, Tue, Wed, Thu, Fri, Sat);
I hope this helps.
Take care, Beco
Reference:
Print the month question, by Kush, answer by Danny Varod
Solution 2
You can do it with a bit of macro magic:
#define FRUITS \
etype(Unknown), \
etype(Apple), \
etype(Orange), \
etype(Banana), \
etype(Apricot), \
etype(Mango)
#define etype(x) F_##x
typedef enum { FRUITS } Fruit;
#undef etype
#define etype(x) #x
static const char *strFruit[] = { FRUITS };
Here is a test program:
#include <iostream>
#include <exception>
#include <vector>
#define FRUITS \
etype(Unknown), \
etype(Apple), \
etype(Orange), \
etype(Banana), \
etype(Apricot), \
etype(Mango)
#define etype(x) F_##x
typedef enum { FRUITS } Fruit;
#undef etype
#define etype(x) #x
static const char *strFruit[] = { FRUITS };
const char *enum2str (Fruit f)
{
return strFruit[static_cast<int>(f)];
}
Fruit str2enum (const char *f)
{
const int n = sizeof(strFruit) / sizeof(strFruit[0]);
for (int i = 0; i < n; ++i)
{
if (strcmp(strFruit[i], f) == 0)
return (Fruit) i;
}
return F_Unknown;
}
int main (int argc, char *argv[])
{
std::cout << "I like " << enum2str(F_Mango) << std::endl;
std::cout << "I do not like " << enum2str(F_Banana) << std::endl;
std::vector<char *> v;
v.push_back("Apple");
v.push_back("Mango");
v.push_back("Tomato");
for (int i = 0; i < v.size(); ++i)
{
const Fruit f = str2enum(v[i]);
if (f == F_Unknown)
std::cout << "Is " << v[i] << " a fruit?" << std::endl;
else
std::cout << v[i] << " is a fruit" << std::endl;
}
return 0;
}
It outputs:
I like Mango
I do not like Banana
Apple is a fruit
Mango is a fruit
Is Tomato a fruit?
Solution 3
Here is my solution:
#define FRUITS(fruit) \
fruit(Apple) \
fruit(Orange) \
fruit(Banana)
#define CREATE_ENUM(name) \
F_##name,
#define CREATE_STRINGS(name) \
#name,
The trick is that 'fruit' is an argument of the macro 'FRUITS' and will be replaced by what ever you pass to. For example:
FRUITS(CREATE_ENUM)
will expand to this:
F_Apple, F_Orange, F_Banana,
Lets create the enum and the string array:
enum fruit {
FRUITS(CREATE_ENUM)
};
const char* fruit_names[] = {
FRUITS(CREATE_STRINGS)
};
Solution 4
Here is a solution with Boost.Preprocessor:
#include <boost/preprocessor.hpp>
#define DEFINE_ENUM_DECL_VAL(r, name, val) BOOST_PP_CAT(name, BOOST_PP_CAT(_, val))
#define DEFINE_ENUM_VAL_STR(r, name, val) BOOST_PP_STRINGIZE(val)
#define DEFINE_ENUM(name, val_seq) \
enum name { \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(DEFINE_ENUM_DECL_VAL, name, val_seq)) \
}; \
static const char* BOOST_PP_CAT(name, _strings[] = ) { \
BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(DEFINE_ENUM_VAL_STR, name, val_seq)) \
};
DEFINE_ENUM(E, (AAA)(BBB)(CCC))
(AAA)(BBB)(CCC)
is a Boost.Preprocessor sequence of tree elements AAA, BBB and CCC; the macro append the enum name to it's modalities:
enum E { E_AAA, E_BBB, E_CCC };
static const char* E_strings[] = { "AAA", "BBB", "CCC" };
Solution 5
One way to do this is with X-Macros, which are basically a way to define a macro which is then used for generating more complex structures than a simple macro easily allows. Here is an example of doing exactly what you are asking.
Related videos on Youtube
Tiago
Updated on July 09, 2022Comments
-
Tiago almost 2 years
In order to make my code shorter and easier to change I want to replace something like
enum{ E_AAA, E_BBB, E_CCC }; static const char *strings{"AAA", "BBB", "CCC" };
With a macro, like INIT(AAA, BBB, CCC); but when I try doing a macro with variable arguments, and stringification I get an error as the arguments are not declared.
Any idea on how to do this?
-
Xeo about 13 yearsHow exactly does your macro look like?
-
Begemoth about 13 yearsTake a look at Boost.Preprocessor, it is ugly (due to limitations of the cpp) but it povides means to write macros that operate on sequences.
-
Lightness Races in Orbit about 13 yearsSounds like you forgot quotes when outputting the
strings
definition. Also, can you pick a language please: C or C++? -
Dipan Mehta almost 8 yearsThis is very useful question and helped me. Thanks for asking.
-
Tom over 2 yearsBasic c++ enum class implementation here stackoverflow.com/questions/207976/…
-
-
digEmAll about 13 years+1 I was just writing this, but you found an already existing example :)
-
Xeo about 13 yearsTrailing commas are totally fine in enums.
-
Lindydancer about 13 yearsI had the check the standards, in C99 they're OK (which was news to me -- thanks). However, they are not accepted by neither C89 nor C++ (1998).
-
Xeo about 13 yearsYou're right, Comeau Online also rejects trailing comma in C++03 strict mode. :) Just ignore my previous comment then.
-
Lindydancer about 13 yearsLooks like you just found a problem with the Comeau compiler, C++03 should accept a trailing comma, according to the draft.
-
DrBeco about 13 yearsPlease, moderator, remove the CW flag. Thank you.
-
Mike Seymour about 13 years@Lindydancer: C++03 should not accept a trailing comma, according to the published standard.
-
Chris Pitman about 13 yearsThis is an X-Macro, using include files with X-Macros is just an extension allowing
MY_LIST
to be moved to a separate file. -
Lindydancer about 13 yearsThe X-Macro requires that you include a file at the use point (were
strings
is defined, in the example above). The List macros I presented above does not require this. One advantage is that you can place the definition in its natural location and not in a dedicated header file. -
tr3w almost 12 yearsI like this, but I found a similar but cleaner way here: stackoverflow.com/a/238157/599142
-
DrBeco over 9 yearsAs we can see the OP is not active in the site. Is it possible (and desirable!) to a moderator accept my answer as the "accepted answer" (if one think it has value for that, of course)? Thanks for any comment on the procedure.
-
Tim Post over 9 yearsIt's not possible for anyone but the question author to accept an answer. There's been discussions about an automatic default for cases like this, but 'accepted' was never intended to indicate 'best', just the answer that the question author could relate to the most. Only the question author knows that for sure, so we've been very reluctant to introduce that sort of change.
-
Dipan Mehta almost 8 yearsThis is lovely! I really needed this but never thought somebody would have solved it.
-
UKMonkey over 6 yearsBad example for X-Macros, since the procressor can already stringify arguments; meaning that the whole X(red, "red") can be replaced with X(red) with a better X.
-
Paul Floyd over 6 yearsNice, but the macro is not reusable like the CREATE_ENUM and CREATE_STRINGS macros in one of the other answers.
-
DrBeco over 6 yearsThanks Paul (nice name for a band). That was commented in 2011/2014, so the context was different. Anyhow, I still like my answer. Macros for "general things" are never used at full potential. You spend a lot of time to create a general macro only to recreate another "even better" when programming another software. Simplicity is better. Take care.
-
emmenlau almost 4 yearsThis code example is very helpful and was a good start. But please note it has a few shortcomings that may need to be addressed. First, in its current form, the macro can not be used in namespaces or classes. Second, it does not explicitly remove white-spaces from enum strings. This is not strictly required but may be unexpected. Both points are possible to address with some effort, giving a nice and very flexible solution. Thanks @gg99!