Creating a string list and an enum list from a C++ macro

31,355

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.

Share:
31,355

Related videos on Youtube

Tiago
Author by

Tiago

Updated on July 09, 2022

Comments

  • Tiago
    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
      Xeo about 13 years
      How exactly does your macro look like?
    • Begemoth
      Begemoth about 13 years
      Take 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
      Lightness Races in Orbit about 13 years
      Sounds like you forgot quotes when outputting the strings definition. Also, can you pick a language please: C or C++?
    • Dipan Mehta
      Dipan Mehta almost 8 years
      This is very useful question and helped me. Thanks for asking.
    • Tom
      Tom over 2 years
      Basic c++ enum class implementation here stackoverflow.com/questions/207976/…
  • digEmAll
    digEmAll about 13 years
    +1 I was just writing this, but you found an already existing example :)
  • Xeo
    Xeo about 13 years
    Trailing commas are totally fine in enums.
  • Lindydancer
    Lindydancer about 13 years
    I 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
    Xeo about 13 years
    You're right, Comeau Online also rejects trailing comma in C++03 strict mode. :) Just ignore my previous comment then.
  • Lindydancer
    Lindydancer about 13 years
    Looks like you just found a problem with the Comeau compiler, C++03 should accept a trailing comma, according to the draft.
  • DrBeco
    DrBeco about 13 years
    Please, moderator, remove the CW flag. Thank you.
  • Mike Seymour
    Mike Seymour about 13 years
    @Lindydancer: C++03 should not accept a trailing comma, according to the published standard.
  • Chris Pitman
    Chris Pitman about 13 years
    This 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
    Lindydancer about 13 years
    The 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
    tr3w almost 12 years
    I like this, but I found a similar but cleaner way here: stackoverflow.com/a/238157/599142
  • DrBeco
    DrBeco over 9 years
    As 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
    Tim Post over 9 years
    It'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
    Dipan Mehta almost 8 years
    This is lovely! I really needed this but never thought somebody would have solved it.
  • UKMonkey
    UKMonkey over 6 years
    Bad 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
    Paul Floyd over 6 years
    Nice, but the macro is not reusable like the CREATE_ENUM and CREATE_STRINGS macros in one of the other answers.
  • DrBeco
    DrBeco over 6 years
    Thanks 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
    emmenlau almost 4 years
    This 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!