April 30th, 2008

Enums, Macros, Unicode and Token-Pasting

Enums, Macros, Unicode and Token-Pasting

Hi, I am Rocky a developer on the Visual C++ IDE team.  I would like to discuss the C++ programming technique of creating macro generated enums.  I recently used this for distinguishing various C++ types such as class, variable, and function.  Having the list of types in one file makes it easy to add types and allows for many different types of uses.  The examples I have below mirror uses in code that I have worked with.  I have also seen this technique used in many places in various other source bases, but I have not seen it discussed much in text books – so I thought I would highlight this technique.

Consider this enum:

enum Animal { dog, cat, fish, bird };

Now dog can be used in place of 0.  You can get compiler enforced type safety that macros do not provide.  The VS debugger will also show the friendly value of the enum instead of integers.  However, functions that print out enum values need better formatting.  This code can help:

wchar_t* AnimalDiscription[] = { L“dog”, L“cat”, L“fish”, L“bird” };

With this array, debugging code can now print the friendly value of the enum by using the enum to index into the string array.

With macro generated enums both the enum and the friendly names can be maintained as one entity.  Consider the file animal.inc:

MYENUM(dog)

MYENUM(cat)

MYENUM(fish)

MYENUM(bird)

And the following C++ code:

enum Animal {

#define MYENUM(e) _##e,

#include “animal.inc”

#undef MYENUM

};

 

wchar_t* AnimalDescription[] = {

#define MYENUM(e) L“_” L#e,

#include “animal.inc”

#undef MYENUM

};

 

Now editing animal.inc will update both the enum and the friendly text of the enum.  In this case I added an underscore in front of the animal names I used before to get the macro to work correctly.  The token-pasting operator ## cannot be used as the first token.  The stringizing operator # creates a string from the operator.  By adding an L right before the stringizing opera tor the resulting string is a wide string.

These macro generated enums can be “debugged” by using the compiler switch /EP or /P.  This will cause the compiler to output the preprocessor file:

enum Animal {

_dog,

_cat,

_fish,

_bird,

};

 

wchar_t* AnimalDescription[] = {

L“_” L“dog”,

L“_” L“cat”,

L“_” L“fish”,

L“_” L“bird”,

};

C++ allows for a comma after the last entry of the enum and the array initializer.

This macro string replacement technique can be further expanded to produce code.  Here is an example of using string replacement to create function prototypes:

#define MYENUM(e) void Order_##e();

#include “animal.inc”

#undef MYENUM

This expands to:

void Order_dog();

void Order_cat();

void Order_fish();

void Order_bird();

You may wish to do some action based on the kind of animal.  If you switch on the kind of animal, here is an example of creating case labels and function calls:

#define MYENUM(e) case _##e:

                   Order_##e();

                   break;

#include “animal.inc”

#undef MYENUM

 

This expands to:

case _dog: Order_dog(); break;

case _cat: Order_cat(); break;

case _fish: Order_fish(); break;

case _bird: Order_bird(); break;

 

In this example the function definitions would need to be added for each of the Order_dog(), Order_cat(), etc..  If you were to add a new animal to animal.inc, you would not need to remember that you would also need to add a new Order_ function definition for this new animal.  The linker would give you an error reminding you!

Macro string replacement is a powerful tool that can be leveraged to allow for internal data to be stored in one spot.  Keeping this data in one spot reduces the chances for errors, missed cases or missed matched cases.

Category
C++

0 comments

Discussion are closed.