views:

146

answers:

4

This is for the C preprocessor experts:

How can I declare an enum with a list of some identifiers and later during the switch-statement check if an identifier was included in the list?

Example of what I need:

typedef enum { e1, e2, e3, e4, e5, e6 } e;

e x;
switch (x) {
#if DECLARED_IN_ENUM (e1)
  case e1 : ...
#endif
  /* etc. */
}

I thought of using a Boost sequence and expanding it into a comma separated list in the enum, but how can I check later if the sequence contains a certain token?

EDIT: What I was able to do with Boost is:

#define e1 e1
#define e2 e2
#define e3 e3
#define e4 e4
#define e5 e5
#define e6 e6
#define E (e1)(e2)(e3)(e4)(e5)(e6)

typedef enum { BOOST_PP_SEQ_ENUM(E) } e;

e x;
switch (x) {
#if defined (e1)
  case e1 : ...
#endif
  /* etc. */
}

That is not very beautiful, and I would prefer something like:

#define E (e1)(e2)(e3)(e4)(e5)(e6)

typedef enum { BOOST_PP_SEQ_ENUM(E) } e;

e x;
switch (x) {
#if BOOST_PP_SEQ_CONTAINS (e1,E)
  case e1 : ...
#endif
  /* etc. */
}

but how could BOOST_PP_SEQ_CONTAINS be implemented?

+4  A: 

You can't. The C preprocessor doesn't "understand" the C programming language, it just tokenizes it. It doesn't know what "enum" actually means. The compiler handles that.

If you want to test something in the preprocessor, then you'll have to provide preprocessor macros for it to use.

Edit: sorry, missed that you were intending to use Boost.Preprocessor. I don't know whether that can provide the necessary macros, or not, once you've involved something from Boost in the definition of your enum.

Steve Jessop
A: 

Just don't use enum. It serves no useful purpose. Declare all your constants with #define, and use #ifdef.

R..
Even in plain C, there is one great advantage of `enum` over `#define`: constants defined with `enum` are known to the debugger, but constants defined with `#define` usually aren't. Less important but still useful is that the compiler has enough information to yell at you if you forget to include some of the values of an `enum` in a `switch` statement. In C++ you also get the benefit of the constants actually being of their own type.
Zack
I'll give you that, but it's still an implementation issue. If the compiler and debugger implementation were sufficiently smart, they could be aware of `#define` just like `enum`. On the other hand, as far as the actual C language is concerned, `#define` is strictly more powerful than `enum`. There are useful things you can do with `#define` which can't be done with `enum`, but there's nothing (in the bounds of the actual language, not debuggers or implementation-specific things) you can accomplish with `enum` that you couldn't do with `#define`.
R..
I'm coding in c++ and put my enums in classes or namespaces, which is not possible with define.
Thomas
OK, you have a good reason to use `enum` then. I just wish everyone would stop tagging C++ questions as C pretending they're the same language...
R..
There's one thing that you can do with `enum` constants that you can't do with `#define` - have a struct or union tag and/or member with the same name.
caf
I tagged the question as C because some answers (all that were given here) require only C features.
Thomas
One other reason to use enums in my program is to be able to count the number of defined elements, as in `enum {a=0,b,c,number};` (I don't know if this is portable, but it works well for my compilers). How would you do that with define?
Thomas
Yes it's portable, but the fact that you don't know it is makes me wonder why you use it.. The way to do that with `#define` is: by hand. I've seen it done plenty places and it's not any harder to maintain than uses of `#define` without a max/count since you're already watching out that you don't duplicate numbers.
R..
+2  A: 

I don't think BOOST_PP_SEQ_CONTAINS can be implemented. It would require you to be able to compare two sequences of preprocessing tokens, which you can't do.

However, if you rearrange your logic a bit, you can get something closer to what you want. First, we need a couple of helper macros for use with BOOST_PP_SEQ_FOR_EACH:

#include <boost/preprocessor.hpp>

// General purpose macros:
#define EXPAND_ENUM_CASE_2(text1, text2) text1 ## text2
#define EXPAND_ENUM_CASE(r, data, elem) \
    case elem : EXPAND_ENUM_CASE_2(data ## _ ## CASE ## _ , elem)

We can define the list of enumerators and the enumeration just as you have in the original question:

#define WORKDAY_ENUMERATORS (Monday)(Tuesday)(Wednesday)(Thursday)

enum Workday { BOOST_PP_SEQ_ENUM(WORKDAY_ENUMERATORS) }; 

As you can see, I've left Friday off of the list because no one actually does work on Friday. Let's consider as an example a function that returns some text describing the day of the week.

Instead of testing whether an enumerator was included in the list, we define the cases for each of the values using macros:

#define WORKDAY_CASE_Monday    { return "Mondays suck";                     }
#define WORKDAY_CASE_Tuesday   { return "Tuesdays are better than Mondays"; }
#define WORKDAY_CASE_Wednesday { return "Hooray for humpday!";              }
#define WORKDAY_CASE_Thursday  { return "Thursdays are okay";               }
#define WORKDAY_CASE_Friday    { return "No one really works on Friday";    }

We then generate the correct case statements for the list by using the WORKDAY_ENUMERATORS and concatenating the enumerators with the WORKDAY_CASE_ prefix:

const char* get_day_text(Workday d)
{    
    switch (d)
    {
        BOOST_PP_SEQ_FOR_EACH(EXPAND_ENUM_CASE, WORKDAY, WORKDAY_ENUMERATORS)
    }
    return "WTF?!  That's not a workday!";
}

If a day was not included in the WORKDAY_ENUMERATORS list, no case will be generated for it.

Because we should be polite when we use the preprocessor, we then undefine the macros we used:

#undef WORKDAY_CASE_Monday
#undef WORKDAY_CASE_Tuesday
#undef WORKDAY_CASE_Wednesday
#undef WORKDAY_CASE_Thursday
#undef WORKDAY_CASE_Friday

I think this is kind of ugly, but it's one way to get almost the results you are seeking.

James McNellis
Thank you, this is a very good solution for my problem.
Thomas
Do you also know how to get Boost output that starts a new line for every case?
Thomas
@Thomas: No, but I'm not sure why that would matter: newlines are not significant after the evaluation of preprocessing directives.
James McNellis
That could help me when gcc prints out errors (an error like "expected identifier before '('" could be hard to spot when the line is 20000 characters long)
Thomas
A: 

One approach is to have a great big #define or ".h" file which covers all your weekdays (the .h file has the advantage that you don't need to end all likes with backslash) and includes all the relevant information for them in macros. Then #define the generator macro to do something, invoke the big macro (or #include the header), #undef the generator macro and define it to do something else, invoke the big macro again, etc. In this scenario, one variation of the generator macro would generate something like "case ENUM_foo: func_foo(); break;". You could then write all the appropriate code for the appropriate func_* functions and they would be called as appropriate.

supercat
Could you post a short code example? This sounds like James' suggestion to me.
Thomas