tags:

views:

96

answers:

5

We use one big enum for message passing and there are optional parts of our code base that use specific messages that really only need to be added to the enum if those parts are to be compiled in. Is there a way to define a macro that could do this or something similar? I would like to be able to just add REGISTER_MSG(MESSAGE_NAME); to the beginning of a file in the optional code. I guess the real problem is that macros only replace the code at the location they are written.

+1  A: 

Indeed, you can't have macros modifying code in other compilation units. And the enum should be the same for all the compilation units.

(I wouldn't probably use enum with automatically assigned value for identifying messages in a protocol, BTW.)

AProgrammer
+1 I agree with the automagically. Use of #define or const int. You can use affectation in your enum then.
neuro
I recognize that an enum is not the best idea for a protocol, but I failed to mention that this is message passing inside a giant statemachine in our application, so intercommunication is not an issue as the enum is common to all message recipients as long as it is created at compile time. Given the volume of messages that are created and passed around and the number of types, it is most efficient to use an enum with switch statements even though it is not a great design choice.
beat2049
A: 

You could do something like this with some ugly template hacks:

#define REGISTER_MSG(MESSAGE_NAME) \
  struct tag_##MESSAGE_NAME { }; \
  static const int &MESSAGE_NAME = assign_message_id<tag_##MESSAGE_NAME>::value;

template<typename Tag>
struct assign_message_id {
    static const int value;
};

// should be compiled only once; don't put the definition in a header
int next_message_id() {
    static int id = 0;
    return id++;
}

template<typename Tag>
const int assign_message_id<Tag>::value = next_message_id();

However, since these are not constant, they could not be used in a switch. Additionally, they could change from compile to compile - or even from one run to the next.

Another option would be to write a script to just scan your entire codebase for these REGISTER_MSG tags, and emit a header based on them. Then you'd just need:

#define REGISTER_MSG(MESSAGE_NAME) /* no-op */
bdonlan
wow. I think you should emphasize the **ugly template hack part** :)
neuro
I think that given the handcuffs of all of the existing switch statements in the current implementation it would be a headache to modify the code to use the template hack. The script may not be the best solution from a design perspective, but it would certainly be the easiest to implement.
beat2049
A: 

I would rethink this design, but if you really want to do it this way you can construct the enum header at compile time using Make.

BigEnum.h:
    # First put in everything up to the opening brace,
    cat BigEnumTop > $@ 
    # then extract all of the message names and add them (with comma) to BigEnum.h,
    cat $(OPTIONAL_SOURCES) | grep REGISTER_MSG | sed 's/.*(\(.*\))/\1/' >> $@
    # then put in the non-optional message names, closing brace, and so on.
    cat BigEnumBottom >> $@
Beta
Winner. It's not pretty, but it is easy to implement in our code base and gets the job done.
beat2049
How about portability? What if the code also needs to be compiled on MSVC?
rstevens
A: 

Easy. Put a #include in the enum, and tweak the include paths so that the appropriate file is included.

enum myenum
{
   x,
   y,
#include "enum_extender.h"
}
Paul Biggar
This seems to have some loose ends. It requires that the message name be kept in a file separate from the code and headers, to prevent circular inclusion. Also, won't you need a dummy (empty) enum_extender.h to use when the optional code isn't to be compiled? And how does this scale as the number of possible configurations increases?
Beta
Beta: Yes, you would need a dummy.I dont see what you're saying about the circular inclusion. enum_extender wouldn't include anything.I don't see problems with scale. This isnt something you should repeat often, obviously.
Paul Biggar
I meant that you need enum_extender as a seperate file (which, as you say, doesn't include anything), you can't have a REG live in the same file that uses the message. Scaling: I thought beat2049 meant there might be many independently optional groups of files, 2^n configurations, many enum_extenders with different names, each in a different directory... If there's only one, why not just have two different versions of Enum.h?
Beta
A: 

Variant of the Makefile solution:

# BigEnum.h #includes BigEnum.inc
BigEnum.h: BigEnum.inc
BigEnum.inc:
    # Extract all of the message names for inclusion (with comma) in BigEnum.h
    cat $(OPTIONAL_SOURCES) | grep REGISTER_MSG | sed 's/.*(\(.*\))/\1/' >> $@

The advantages are that support tooling will see a reasonable BigEnum.h with at least some enum values, and the source code repository will contain a single file instead of a seperate header and footer.

You obviously need to have a single definition of BigEnum throughout your code, or else you might violate the ODR. Also consider what happens if your "optional" value happens to be the 256th or 65536th in an enum.

MSalters