12,886,892 members (37,321 online)
alternative version

#### Stats

19.5K views
13 bookmarked
Posted 18 Nov 2010

# First Class C++ Enums

, 18 Nov 2010 CPOL
 Rate this:
First Class C++ Enums

## Introduction

An enumeration is a set of constant integer values, and this feature is supported in various programming languages. Before diving headlong into the concept of enumerations, I would like to briefly look at how integers are often used in programming, since integers are the building blocks of enumerations. We usually think of numbers as representations of a quantity. Other numbers have significance on their own merits. Examples:

Number Meaning
1492 The year in which Columbus sailed to America. There is also some evidence that in that same year, COBOL was invented. It did not lead to a Y1.5K problem in the year 1500 because computers were not yet in use.
5150 The name of a Van Halen album, named after Eddie Van Halen's home recording studio. The studio was named after a California law related to mental health.
32 The Fahrenheit temperature at which fresh water freezes. Incidentally, Daniel Fahrenheit chose the freezing temperature of brine as the zero temperature, making this temperature scale the natural choice to be used alongside our super intuitive American system of weights and measures.
2012 The year the Mayan Calendar ends. During that year, archaeologists hope to save the world by proving there's a missing digit where the date appears on the calendar, but it ironically turns out to be a leading zero. Bummer...

These numeric values all have significance outside of mathematics. In programming, numeric values are often used to identify something -- a row in a database table, an error condition, and so on. Again, these numbers don't have a highly mathematical purpose for existence, though they might be bit coded or defined in ranges that indicate severity of errors. Integers can be compared unambiguously, unlike text, and their small size (usually 32 or 64 bits) makes them easy to pass around by value. All developers know this intuitively, but we rarely give thought to how we use numbers to indicate a state or result. Numbers representing a state in code have little apparent meaning to the reader, so we use enumerations to put friendly identifiers in code. These text identifiers are effectively replaced with constant integers when you compile, though `enum`s do have their own data type. As an example, we'll define an enumeration of playing cards, then write a function to select a card at random. First, the `enum`:

```enum PlayingCard {
Joker = 0,
AceOfClubs = 1,
Num2OfClubs = 2,
Num3OfClubs = 3,
Num4OfClubs = 4,
Num5OfClubs = 5,
Num6OfClubs = 6,
Num7OfClubs = 7,
Num8OfClubs = 8,
Num9OfClubs = 9,
Num10OfClubs = 10,
JackOfClubs = 11,
QueenOfClubs = 12,
KingOfClubs = 13,
AceOfDiamonds = 14,
Num2OfDiamonds = 15,
Num3OfDiamonds = 16,
Num4OfDiamonds = 17,
Num5OfDiamonds = 18,
Num6OfDiamonds = 19,
Num7OfDiamonds = 20,
Num8OfDiamonds = 21,
Num9OfDiamonds = 22,
Num10OfDiamonds = 23,
JackOfDiamonds = 24,
QueenOfDiamonds = 25,
KingOfDiamonds = 26,
AceOfHearts = 27,
Num2OfHearts = 28,
Num3OfHearts = 29,
Num4OfHearts = 30,
Num5OfHearts = 31,
Num6OfHearts = 32,
Num7OfHearts = 33,
Num8OfHearts = 34,
Num9OfHearts = 35,
Num10OfHearts = 36,
JackOfHearts = 37,
QueenOfHearts = 38,
KingOfHearts = 39,

Modulus
};```

As you know, an `enum` gives each face card a meaningful identifier that maps to an integer value. Here's a short snippet utilizing the enumeration, selecting a random card from the deck, then printing it out. We expect to see meaningful output in the standard output console.

```static PlayingCard randomCard();

int _tmain(int argc, _TCHAR* argv[])
{
srand( (unsigned int) Joker );
PlayingCard card = randomCard();
std::cout << "Random card: " << card << std::endl;
#ifdef _DEBUG
std::cout << "Press <Enter> to continue: ";
getchar();
#endif
return 0;
}

static PlayingCard randomCard()
{
return PlayingCard( (PlayingCard) (rand() % Modulus) );
}```

And the console output is...

`Random card: 25`

What? 25? It appears that `enum`s are second class citizens in C++. The friendly names are only visible inside of code. It's like when you go to renew your driver's license -- you have a name, but you take a number. You don't know anything, for example, about number 97 until that number is called and the person stands up. Numbers used for identification need some sort of context to convey meaning.

Let's summarize the challenge we face in C++. We programmers assign meaning to integers, representing the internal state of our software. We use enumerations (or preprocessor definitions, or static constants) to give our code more readability. When something goes awry in our code, we need to emit output for the purpose of troubleshooting. Unfortunately, when dumping error values to the debug monitor or console, the raw numbers have no contextual meaning, forcing us to look at code even when the problem is due to external factors (for example, a database or network is down). Often, non-programmers are perfectly able to resolve these sorts of problems, but they must interrupt a developer or scroll through documentation to figure out what a certain error number means. We need enumerations that know how to emit their values as meaningful text. What we need is enumerations that are first class C++ citizens.

## Background

Again, we want `enum`s that emit meaningful text output. Further, we want them to support certain math operators; many enumerations are meant to be bitwise, for example. We want them to have a restricted set of possible values. Enumerated types should support reflection, including iteration of the constants. These types should be stubborn about mixing with other types, causing the compiler to complain if we accidentally try to set their values to that of another data type. Finally, if an enumerated value represents an error or exception state, why not make it throwable like an exception?

The solution in C++ can be hand-coded, but each enumeration requires a large amount of code. It's clear we need a tool or strategy to automate the solution. A strategy meeting all the requirements above requires multiple processing loops through the values in an enumeration. In other words, I'm saying that `enum { a, b, c }` generates output `{ t``<sub>1</sub>``(a), t``<sub>1</sub>``(b), t``<sub>1</sub>``(c) }, t``<sub>2</sub>``(a), t``<sub>2</sub>``(b), t``<sub>2</sub>``(c) } ... {t``<sub>n</sub>``(a), t``<sub>n</sub>``(b), t``<sub>n</sub>``(c)}`, where each `t` represents a single transformation of an enumerated constant. C++ templates don't offer an elegant solution. They allow multiple transformations on parameters, but the number of template parameters is fixed. The preprocessor can be used to "loop" at compile time (see the Boost preprocessor library), but manipulating the preprocessor requires strange syntax and can even confuse intellisense. Nevertheless, being able to hand code a single prototype is proof of concept, and a general solution can be achieved through code generation.

Code generation (aka metacoding) sounds radical, but it's not. You use code generation all the times without thinking about it. The wizards in your development IDEs generate code in various languages. IDL (Interface Definition Language) compilers generate code. Various designer tools in Visual Studio .NET projects generate code.

There are many ways to perform the code generation we need, but in all cases, you use some type of parser that reads your enumeration definitions, emitting C++ header files as output. Parsing is tedious and error-prone work, so it's best to pass that work off to existing tools rather than writing something custom. You can use XML, but XML is really verbose, sucking all the elegance out of a solution. You can use Google's Protocol Buffers library. It would push the limits of the PB language syntax, but it's doable.

Ultimately, I chose the geekiest solution available. I used the Google's V8 JavaScript engine so that I could use JSON (JavaScript Object Notation) to designate `enum `definitions. JSON's beauty is that it is both declarative and dynamic. On the declarative side, it lets you define a data type somewhat resembling a C structure, and it can also look like an `enum `definition. In addition, JSON types can be manipulated programmatically, and their data structure somewhat resembles that of a tree of nested hashtables. Being dynamic, JSON allows a programmer to actually write a program to generate an `enum `definition. You can write code to generate our card deck in a loop rather than declaratively. This allows you to easily reformat all of the constant names with a simple code tweak, and it eliminates inconsistencies in the naming standard caused by typos. The JavaScript code to generate the card deck enumeration is in the sample application.

## How It Works

A disclaimer: This is not a V8 tutorial. True, the world lacks samples of embedding V8, and you can use the code as a reference if you choose to do so. The experts on V8 all work for a company called Google, and I'm not among their ranks. My code gets V8 running, but it may not be a textbook implementation.

The C++ generator is composed of this V8 JavaScript hosting environment and a very small, custom JavaScript API. The user writes a small block of JSON, then tells `cg` (the code generator global variable in the API) to take the information and dump C++ output to a file. The jsmeta (abbreviation for JavaScript MetaCoder) application exposes `cg` to the scripting environment, gives it a couple useful methods, and does all the hard work in the bowels of its own C++ code. Inside of jsmeta, the JSON object gets expanded into a much larger chunk of C++ code to support the features we desire.

Much of the heavy lifting in the resulting, generated C++ code is assumed to be implemented in header files that you can `#include`. This is by design. First, it reduces the overall size of your code. Second, it allows developers to customize what goes on underneath the generated C++ classes. The `cg `object lets you `#include` any file you want into your generated code. Here's an example:

`cg.write('#include "enumerations/api.h"');`

The file enumerations/api.h is provided in the source code. The templates and base classes used by the generated C++ classes are defined in this header file. This header file implements operators `++` and `--` as iterators, as opposed to add/subtract fix operators. In other words, `++` changes the value to the next enumerated constant rather than adding one. This addresses the value restriction requirement, where values must remain inside the set of constants in the enumeration. As stated before, you can create your own API and `#include` it instead. In so doing, you have the freedom to define operators to have different behaviors.

Speaking of operators, you will notice that the code in api.h makes heavy use of operator overloading, especially the '`()`' functor operator. Operator overloading uses funny syntax, and overload declarations are a bit hard to read. There is a really good reason for using functors in this scenario: we don't want our base classes to have name collisions with enumerated values defined in the subclasses. Operators are nameless functions in C++, allowing us to avoid reserving names that might be needed for an enum.

## Using the Code

If you want to build the jsmeta tool, you will need to download or build the V8 library files, v8.lib and v8_g.lib. It is most likely you will need to build them from the Google source code. Those files should be copied to the v8 folder in the jsmeta project.

The attached program is ready to install as a tool in MSVC. The procedure is quite simple, following these steps:

1. Put the rules file and EXE file (from the attached zip) in a folder, preferably one that will be in the EXE search path for MSVC. Figure 1 shows how to add a folder to the EXE search path in MSVC.
2. Add the EXE as a build tool. This requires the following steps.
3. Right click on your project in Solution Explorer and select Custom Build Tools.
4. Press the Find Existing button and select jsmeta.rules. The UI is shown in figure 2.
5. Make sure JavaScript C++ MetaCoder is turned on.

Figure 1: Setting up PATH to find the jsmeta.exe tool. Use the Tools | Options from the MSVC menu.

Figure 2: Installing the custom build rule for JS files.

The following snippet is a sample of the JavaScript / JSON code defining enumerations. Note that two of the enumerations are defined dynamically with code.

```//test.js

//Add a #include at the top of the HPP output file.

cg.write('#include "enumerations/api.h"');

//Before defining namespaces and enums, here's a demo of JSON field behavior!

var jasonExample = {
a: 1,
a: 2        //Use the same field name, or from a hashtable perspective, the same KEY

};
//Dump the output to see what happens to the field 'a'.

cg.write("//jsonExample.a = ", jasonExample.a );

/*  IMPORTANT NOTE:
In the test.js.hpp file generated by jsmeta, you'll see that jsonExample.a = 2.
This relates to the use of the terms Namespace, Enum, and BitEnum below.
If you define two Enum fields sequentially in a JSON block,
the second field will be used, and the information in the first
will be lost.  Therefore, jsmeta allows you to disambiguate these fields
by planting _# on the end of the Enum, BitEnum, or NameSpace,
where # represents any number.
*/

//Define a namespace with enumerations.  Namespaces may be nested.

var ns =
{ Namespace: { enumsamples: {
Enum: { SequentialEnum: {
FirstConstant: 0,
SecondConstant: {
Value: 2,
Throwable: true
},
NegativeValue: -41,
}},
//The _2 on the end is disregarded by jsmeta.

//This just tells JavaScript to consider this

//another JSON field.  Naming this field Enum would overwrite the SampleEnum

//definition above.

Enum_2: { AnotherSequentialEnum: {
a : null,   //Let the program auto-number this one.

b : null,
c : null,
d : 0,
}},
BitEnum: { SampleBitwiseEnum: {
Test9: 9,
Test1: {
Value: 1,
Throwable: true
},
Test2: 2,
Test8: 8,
TestAuto: null, //Should be 16

FirstMatch: 8,
NextMatch: 8,
}},
//Illustrates a nested namespace

Namespace: { printing: {
//Define an enumeration mapping paper sizes

//to their area in square tenths/mm.

//This is just a placeholder for the enum we'll generate below.

Enum_2: { PaperSizes : {
Invalid: 0,
}}
}},
Enum_3 : { CardDeck: {
Joker : 0
}}
}}};

//Advanced usage:  Programmatically populate a 'PaperSizes' enumeration.  The idea

//here is that every paper size maps to a single integer value, its area in square

//tenths of millimeters.  If we can declare the members as shown below, it's easier

//to inspect each definition visually than if we pre-calculated the areas first. So

//they are declared as follows, then the sheetsizes JSON object will be transformed

//into the PaperSizes definition started above.

var sheetsizes = [
{name: 'Letter',    width: 8.5, height: 11.0,   metric: false},
{name: 'A4',        width: 210, height: 297,    metric: true}
];

//Iterate each sheet size definition.

for( var n=0; n<sheetsizes.length; n++ ){
var ss = sheetsizes[n];
var w = ss.width;
var h = ss.height;
//Convert to tenths of millimeters.  This ensures all w and h values will

//be whole numbers.

if( ss.metric )
{
w *= 10.0;
h *= 10.0;
}
else {
w *= 254.0;
h *= 254.0;
}
//Add the calculated enum value to the PaperSizes enumeration.

ns.Namespace.enumsamples.Namespace.printing.Enum_2.PaperSizes[ss.name] =
Math.round(w * h);
}

//Example of populating a long array by looping.

//Let's map each card in a deck from 1 to 52, recalling

//that Joker = 0 in CardDeck declaration above.

var suits = Array("Clubs", "Diamonds", "Hearts", "Spades");
var names = {
1 : "Ace",
11 : "Jack",
12 : "Queen",
13 : "King",
};
//For each suit

for(var n=0; n<4; n++)
{
//For each face value in the suit

for( var faceval=1; faceval <= 13; ++faceval )
{
//See if we have a name mapped to the card's numeric value...

var name = names[faceval];
if( typeof(name) != 'string' ){
name = "Num" + faceval; //Identifiers can NOT start

}
//Burn the name of the constant.

var cardname = name + "Of" + suits[n];
//Auto-number the constant.

ns.Namespace.enumsamples.Enum_3.CardDeck[cardname] = null;
}
}
//The next value gives us the total number of cards.

ns.Namespace.enumsamples.Enum_3.CardDeck['TotalCards'] = null;

//This rips the namespaces and enumerations in the JSON variable ns,

//writing output to test.js.hpp.

cg.write(ns);```

You are now ready to build. Some things to note about the JSON object shown in the code block above:

• Namespaces are optional.
• Namespaces can be nested inside of namespaces.
• Please read the code comment above titled `IMPORTANT NOTE`. This explains the strange syntax such as `Enum_2`.

The script above generates the following C++ code. The generated file is scriptfile.js.hpp, where scriptfile is the name portion of the JavaScript file.

```///test.js.hpp

#include "enumerations/api.h"

//jsonExample.a = 2

namespace enumsamples {
template<class TOuter> class SequentialEnum
: public enumerations::Enumeration,
public enumerations::MathOps<SequentialEnum<TOuter>>
{
friend class enumerations::MathOps<SequentialEnum<TOuter>>;
friend class enumerations::TEnumOps<SequentialEnum>;
public:
typedef enumerations::int_t int_t;
typedef enumerations::Enumeration::intval_t intval_t;
SequentialEnum() {}
enum enum_t {
FirstConstant = 0,
SecondConstant = 2,
NegativeValue = -41,
};
SequentialEnum(const SequentialEnum& other)
: Enumeration(other)
{
}

//Reflection implementation forSequentialEnum

class reflector_t : public enumerations::EnumReflector<
SequentialEnum<TOuter>, 3, DEFAULT_ENUM> {
friend class enumerations::BaseEnumReflector
<SequentialEnum<TOuter>, 3>;
friend class SequentialEnum;
static PENTRY entries(){
static ENTRY defs[] = {
{"{", 0, 0, 8},
{"FirstConstant", (int_t)0, /*next match offset*/0, 0},
{"SecondConstant", (int_t)2, /*next match offset*/0, 1},
{"NegativeValue", (int_t)-41, /*next match offset*/0, 0},
{"}", 0, 0, 16},
};
return defs + 1;
}
public:
static METADATA md = {"SequentialEnum", entries(), 3};
return &md;
}
static enum_t first(){
return SequentialEnum<TOuter>::FirstConstant;
}
static enum_t last(){
return SequentialEnum<TOuter>::NegativeValue;
}
static PENTRY find(int_t val){
PENTRY defs = entries();
HASHNODE nodes[] = {
{{defs + 0/* 0 */,},1},
{{defs + 2/* -41 */,},1},
{{defs + 1/* 2 */,},1},
{{},0},
{{},0},
{{},0},
{{},0},
};
HASHNODE& node = nodes[positiveModulus(val, 7)];
for(size_t i=0; i<node.size; i++){
if( val == node.ents[i]->value )
return node.ents[i];
}
return defs + 3;
}
typedef struct _hashnode {
PENTRY ents[1];
size_t size;
} HASHNODE, *PHASHNODE;
};//end class reflector_t

operator enum_t() const { return
(enum_t)reflector_t::entries()[___offset___].value; }
operator std::string() const { return reflector_t::name() + "::" +
std::string(reflector_t::entries()[___offset___].name); }
SequentialEnum(enum_t newval) {
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
}
SequentialEnum& operator = (enum_t newval){
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
return *this;
}
typedef enumerations::ExceptionT<SequentialEnum<TOuter>> Exception;
void tryThrowException(const std::string& msg=std::string() ) const {
PENTRY pe = reflector_t::entries()+ ___offset___;
if( pe->flags & 1 )
throw Exception(*this, msg);
}
State operator() () const {
if( ___offset___ < 0 ) return BeforeEntries;
return ( ___offset___ >= 3 ) ? AfterEntries : Valid;
}
protected:
enumerations::Enumeration& operator = (const intval_t newVal){
PENTRY pe = reflector_t::find(newVal.nval);
___offset___ = pe - reflector_t::entries();
return *static_cast<Enumeration*>(this);
}
};
template<class TOuter> class AnotherSequentialEnum
: public enumerations::Enumeration,
public enumerations::MathOps<AnotherSequentialEnum<TOuter>>
{
friend class enumerations::MathOps<AnotherSequentialEnum<TOuter>>;
friend class enumerations::TEnumOps<AnotherSequentialEnum>;
public:
typedef enumerations::int_t int_t;
typedef enumerations::Enumeration::intval_t intval_t;
AnotherSequentialEnum() {}
enum enum_t {
a = 0,
b = 1,
c = 2,
d = 0,
};
AnotherSequentialEnum(const AnotherSequentialEnum& other)
: Enumeration(other)
{
}

//Reflection implementation forAnotherSequentialEnum

class reflector_t : public enumerations::EnumReflector<
AnotherSequentialEnum<TOuter>, 4, DEFAULT_ENUM> {
friend class enumerations::BaseEnumReflector
<AnotherSequentialEnum<TOuter>, 4>;
friend class AnotherSequentialEnum;
static PENTRY entries(){
static ENTRY defs[] = {
{"{", 0, 0, 8},
{"a", (int_t)0, /*next match offset*/3, 0},
{"b", (int_t)1, /*next match offset*/0, 0},
{"c", (int_t)2, /*next match offset*/0, 0},
{"d", (int_t)0, /*next match offset*/0, 0},
{"}", 0, 0, 16},
};
return defs + 1;
}
public:
{"AnotherSequentialEnum", entries(), 4};
return &md;
}
static enum_t first(){
return AnotherSequentialEnum<TOuter>::a;
}
static enum_t last(){
return AnotherSequentialEnum<TOuter>::d;
}
static PENTRY find(int_t val){
PENTRY defs = entries();
HASHNODE nodes[] = {
{{defs + 0/* 0 */,defs + 3/* 0 */,},2},
{{defs + 1/* 1 */,},1},
{{defs + 2/* 2 */,},1},
{{},0},
{{},0},
{{},0},
{{},0},
};
HASHNODE& node = nodes[positiveModulus(val, 7)];
for(size_t i=0; i<node.size; i++){
if( val == node.ents[i]->value )
return node.ents[i];
}
return defs + 4;
}
typedef struct _hashnode {
PENTRY ents[2];
size_t size;
} HASHNODE, *PHASHNODE;
};//end class reflector_t

operator enum_t() const { return (enum_t)reflector_t::entries()
[___offset___].value; }
operator std::string() const { return reflector_t::name() + "::" +
std::string(reflector_t::entries()[___offset___].name); }
AnotherSequentialEnum(enum_t newval) {
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
}
AnotherSequentialEnum& operator = (enum_t newval){
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
return *this;
}
typedef enumerations::ExceptionT<AnotherSequentialEnum<TOuter>> Exception;
void tryThrowException(const std::string& msg=std::string() ) const {
PENTRY pe = reflector_t::entries()+ ___offset___;
if( pe->flags & 1 )
throw Exception(*this, msg);
}
State operator() () const {
if( ___offset___ < 0 ) return BeforeEntries;
return ( ___offset___ >= 4 ) ? AfterEntries : Valid;
}
protected:
enumerations::Enumeration& operator = (const intval_t newVal){
PENTRY pe = reflector_t::find(newVal.nval);
___offset___ = pe - reflector_t::entries();
return *static_cast<Enumeration*>(this);
}
};
template<class TOuter> class SampleBitwiseEnum
: public enumerations::Enumeration,
public enumerations::BitwiseOps<SampleBitwiseEnum<TOuter>>
{
friend class enumerations::BitwiseOps<SampleBitwiseEnum<TOuter>>;
friend class enumerations::TEnumOps<SampleBitwiseEnum>;
public:
typedef enumerations::int_t int_t;
typedef enumerations::Enumeration::intval_t intval_t;
SampleBitwiseEnum() {}
enum enum_t {
Bitsum_0x0 = 0,
Test1 = 1,
Test2 = 2,
Bitsum_0x3 = 3,
Test8 = 8,
Test9 = 9,
Bitsum_0xa = 10,
Bitsum_0xb = 11,
TestAuto = 16,
Bitsum_0x11 = 17,
Bitsum_0x12 = 18,
Bitsum_0x13 = 19,
Bitsum_0x18 = 24,
Bitsum_0x19 = 25,
Bitsum_0x1a = 26,
Bitsum_0x1b = 27,
FirstMatch = 8,
NextMatch = 8,
};
SampleBitwiseEnum(const SampleBitwiseEnum& other)
: Enumeration(other)
{
}

//Reflection implementation forSampleBitwiseEnum

class reflector_t : public enumerations::EnumReflector<
SampleBitwiseEnum<TOuter>, 18, BITWISE_ENUM> {
friend class enumerations::BaseEnumReflector
<SampleBitwiseEnum<TOuter>, 18>;
friend class SampleBitwiseEnum;
static PENTRY entries(){
static ENTRY defs[] = {
{"{", 0, 0, 8},
{"Bitsum_0x0", (int_t)0, /*next match offset*/0, 4},
{"Test1", (int_t)1, /*next match offset*/0, 1},
{"Test2", (int_t)2, /*next match offset*/0, 0},
{"Bitsum_0x3", (int_t)3, /*next match offset*/0, 5},
{"Test8", (int_t)8, /*next match offset*/12, 0},
{"Test9", (int_t)9, /*next match offset*/0, 0},
{"Bitsum_0xa", (int_t)10, /*next match offset*/0, 4},
{"Bitsum_0xb", (int_t)11, /*next match offset*/0, 5},
{"TestAuto", (int_t)16, /*next match offset*/0, 0},
{"Bitsum_0x11", (int_t)17, /*next match offset*/0, 5},
{"Bitsum_0x12", (int_t)18, /*next match offset*/0, 4},
{"Bitsum_0x13", (int_t)19, /*next match offset*/0, 5},
{"Bitsum_0x18", (int_t)24, /*next match offset*/0, 4},
{"Bitsum_0x19", (int_t)25, /*next match offset*/0, 5},
{"Bitsum_0x1a", (int_t)26, /*next match offset*/0, 4},
{"Bitsum_0x1b", (int_t)27, /*next match offset*/0, 5},
{"FirstMatch", (int_t)8, /*next match offset*/1, 0},
{"NextMatch", (int_t)8, /*next match offset*/0, 0},
{"}", 0, 0, 16},
};
return defs + 1;
}
public:
static METADATA md = {"SampleBitwiseEnum", entries(), 18};
return &md;
}
static enum_t first(){
return SampleBitwiseEnum<TOuter>::Bitsum_0x0;
}
static enum_t last(){
return SampleBitwiseEnum<TOuter>::NextMatch;
}
static PENTRY find(int_t val){
PENTRY defs = entries();
HASHNODE nodes[] = {
{{defs + 0/* 0 */,defs + 14/* 26 */,},2},
{{defs + 1/* 1 */,defs + 15/* 27 */,},2},
{{defs + 2/* 2 */,},1},
{{defs + 3/* 3 */,defs + 8/* 16 */,},2},
{{defs + 9/* 17 */,},1},
{{defs + 10/* 18 */,},1},
{{defs + 11/* 19 */,},1},
{{},0},
{{defs + 4/* 8 */,defs + 16/* 8 */,
defs + 17/* 8 */,},3},
{{defs + 5/* 9 */,},1},
{{defs + 6/* 10 */,},1},
{{defs + 7/* 11 */,defs + 12/* 24 */,},2},
{{defs + 13/* 25 */,},1},
};
HASHNODE& node = nodes[positiveModulus(val, 13)];
for(size_t i=0; i<node.size; i++){
if( val == node.ents[i]->value )
return node.ents[i];
}
return defs + 18;
}
typedef struct _hashnode {
PENTRY ents[3];
size_t size;
} HASHNODE, *PHASHNODE;
};//end class reflector_t

operator enum_t() const { return (enum_t)
reflector_t::entries()[___offset___].value; }
operator std::string() const { return reflector_t::name() + "::" +
std::string(reflector_t::entries()[___offset___].name); }
SampleBitwiseEnum(enum_t newval) {
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
}
SampleBitwiseEnum& operator = (enum_t newval){
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
return *this;
}
typedef enumerations::ExceptionT<SampleBitwiseEnum<TOuter>> Exception;
void tryThrowException(const std::string& msg=std::string() ) const {
PENTRY pe = reflector_t::entries()+ ___offset___;
if( pe->flags & 1 )
throw Exception(*this, msg);
}
State operator() () const {
if( ___offset___ < 0 ) return BeforeEntries;
return ( ___offset___ >= 18 ) ? AfterEntries : Valid;
}
protected:
enumerations::Enumeration& operator = (const intval_t newVal){
PENTRY pe = reflector_t::find(newVal.nval);
___offset___ = pe - reflector_t::entries();
return *static_cast<Enumeration*>(this);
}
};
namespace printing {
template<class TOuter> class PaperSizes
: public enumerations::Enumeration,
public enumerations::MathOps<PaperSizes<TOuter>>
{
friend class enumerations::MathOps<PaperSizes<TOuter>>;
friend class enumerations::TEnumOps<PaperSizes>;
public:
typedef enumerations::int_t int_t;
typedef enumerations::Enumeration::intval_t intval_t;
PaperSizes() {}
enum enum_t {
Invalid = 0,
Letter = 6032246,
A4 = 6237000,
};
PaperSizes(const PaperSizes& other)
: Enumeration(other)
{
}

//Reflection implementation forPaperSizes

class reflector_t : public enumerations::EnumReflector<
PaperSizes<TOuter>, 3, DEFAULT_ENUM> {
friend class enumerations::BaseEnumReflector
<PaperSizes<TOuter>, 3>;
friend class PaperSizes;
static PENTRY entries(){
static ENTRY defs[] = {
{"{", 0, 0, 8},
{"Invalid", (int_t)0, /*next match offset*/0, 0},
{"Letter", (int_t)6032246, /*next match offset*/0, 0},
{"A4", (int_t)6237000, /*next match offset*/0, 0},
{"}", 0, 0, 16},
};
return defs + 1;
}
public:
static METADATA md = {"PaperSizes", entries(), 3};
return &md;
}
static enum_t first(){
return PaperSizes<TOuter>::Invalid;
}
static enum_t last(){
return PaperSizes<TOuter>::A4;
}
static PENTRY find(int_t val){
PENTRY defs = entries();
HASHNODE nodes[] = {
{{defs + 0/* 0 */,defs + 2/* 6237000 */,},2},
{{},0},
{{},0},
{{defs + 1/* 6032246 */,},1},
{{},0},
{{},0},
{{},0},
};
HASHNODE& node = nodes[positiveModulus(val, 7)];
for(size_t i=0; i<node.size; i++){
if( val == node.ents[i]->value )
return node.ents[i];
}
return defs + 3;
}
typedef struct _hashnode {
PENTRY ents[2];
size_t size;
} HASHNODE, *PHASHNODE;
};//end class reflector_t

operator enum_t() const { return (enum_t)
reflector_t::entries()[___offset___].value; }
operator std::string() const { return reflector_t::name() + "::" +
std::string(reflector_t::entries()[___offset___].name); }
PaperSizes(enum_t newval) {
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
}
PaperSizes& operator = (enum_t newval){
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
return *this;
}
typedef enumerations::ExceptionT<PaperSizes<TOuter>> Exception;
void tryThrowException(const std::string&
msg=std::string() ) const {
PENTRY pe = reflector_t::entries()+ ___offset___;
if( pe->flags & 1 )
throw Exception(*this, msg);
}
State operator() () const {
if( ___offset___ < 0 ) return BeforeEntries;
return ( ___offset___ >= 3 ) ? AfterEntries : Valid;
}
protected:
enumerations::Enumeration& operator = (const intval_t newVal){
PENTRY pe = reflector_t::find(newVal.nval);
___offset___ = pe - reflector_t::entries();
return *static_cast<Enumeration*>(this);
}
};
}; // end namespace printing

template<class TOuter> class CardDeck
: public enumerations::Enumeration,
public enumerations::MathOps<CardDeck<TOuter>>
{
friend class enumerations::MathOps<CardDeck<TOuter>>;
friend class enumerations::TEnumOps<CardDeck>;
public:
typedef enumerations::int_t int_t;
typedef enumerations::Enumeration::intval_t intval_t;
CardDeck() {}
enum enum_t {
Joker = 0,
AceOfClubs = 1,
Num2OfClubs = 2,
Num3OfClubs = 3,
Num4OfClubs = 4,
Num5OfClubs = 5,
Num6OfClubs = 6,
Num7OfClubs = 7,
Num8OfClubs = 8,
Num9OfClubs = 9,
Num10OfClubs = 10,
JackOfClubs = 11,
QueenOfClubs = 12,
KingOfClubs = 13,
AceOfDiamonds = 14,
Num2OfDiamonds = 15,
Num3OfDiamonds = 16,
Num4OfDiamonds = 17,
Num5OfDiamonds = 18,
Num6OfDiamonds = 19,
Num7OfDiamonds = 20,
Num8OfDiamonds = 21,
Num9OfDiamonds = 22,
Num10OfDiamonds = 23,
JackOfDiamonds = 24,
QueenOfDiamonds = 25,
KingOfDiamonds = 26,
AceOfHearts = 27,
Num2OfHearts = 28,
Num3OfHearts = 29,
Num4OfHearts = 30,
Num5OfHearts = 31,
Num6OfHearts = 32,
Num7OfHearts = 33,
Num8OfHearts = 34,
Num9OfHearts = 35,
Num10OfHearts = 36,
JackOfHearts = 37,
QueenOfHearts = 38,
KingOfHearts = 39,
TotalCards = 53,
};
CardDeck(const CardDeck& other)
: Enumeration(other)
{
}

//Reflection implementation forCardDeck

class reflector_t : public enumerations::EnumReflector<CardDeck<TOuter>,
54, DEFAULT_ENUM> {
friend class enumerations::BaseEnumReflector<CardDeck<TOuter>, 54>;
friend class CardDeck;
static PENTRY entries(){
static ENTRY defs[] = {
{"{", 0, 0, 8},
{"Joker", (int_t)0, /*next match offset*/0, 0},
{"AceOfClubs", (int_t)1, /*next match offset*/0, 0},
{"Num2OfClubs", (int_t)2, /*next match offset*/0, 0},
{"Num3OfClubs", (int_t)3, /*next match offset*/0, 0},
{"Num4OfClubs", (int_t)4, /*next match offset*/0, 0},
{"Num5OfClubs", (int_t)5, /*next match offset*/0, 0},
{"Num6OfClubs", (int_t)6, /*next match offset*/0, 0},
{"Num7OfClubs", (int_t)7, /*next match offset*/0, 0},
{"Num8OfClubs", (int_t)8, /*next match offset*/0, 0},
{"Num9OfClubs", (int_t)9, /*next match offset*/0, 0},
{"Num10OfClubs", (int_t)10, /*next match offset*/0, 0},
{"JackOfClubs", (int_t)11, /*next match offset*/0, 0},
{"QueenOfClubs", (int_t)12, /*next match offset*/0, 0},
{"KingOfClubs", (int_t)13, /*next match offset*/0, 0},
{"AceOfDiamonds", (int_t)14, /*next match offset*/0, 0},
{"Num2OfDiamonds", (int_t)15, /*next match offset*/0, 0},
{"Num3OfDiamonds", (int_t)16, /*next match offset*/0, 0},
{"Num4OfDiamonds", (int_t)17, /*next match offset*/0, 0},
{"Num5OfDiamonds", (int_t)18, /*next match offset*/0, 0},
{"Num6OfDiamonds", (int_t)19, /*next match offset*/0, 0},
{"Num7OfDiamonds", (int_t)20, /*next match offset*/0, 0},
{"Num8OfDiamonds", (int_t)21, /*next match offset*/0, 0},
{"Num9OfDiamonds", (int_t)22, /*next match offset*/0, 0},
{"Num10OfDiamonds", (int_t)23, /*next match offset*/0, 0},
{"JackOfDiamonds", (int_t)24, /*next match offset*/0, 0},
{"QueenOfDiamonds", (int_t)25, /*next match offset*/0, 0},
{"KingOfDiamonds", (int_t)26, /*next match offset*/0, 0},
{"AceOfHearts", (int_t)27, /*next match offset*/0, 0},
{"Num2OfHearts", (int_t)28, /*next match offset*/0, 0},
{"Num3OfHearts", (int_t)29, /*next match offset*/0, 0},
{"Num4OfHearts", (int_t)30, /*next match offset*/0, 0},
{"Num5OfHearts", (int_t)31, /*next match offset*/0, 0},
{"Num6OfHearts", (int_t)32, /*next match offset*/0, 0},
{"Num7OfHearts", (int_t)33, /*next match offset*/0, 0},
{"Num8OfHearts", (int_t)34, /*next match offset*/0, 0},
{"Num9OfHearts", (int_t)35, /*next match offset*/0, 0},
{"Num10OfHearts", (int_t)36, /*next match offset*/0, 0},
{"JackOfHearts", (int_t)37, /*next match offset*/0, 0},
{"QueenOfHearts", (int_t)38, /*next match offset*/0, 0},
{"KingOfHearts", (int_t)39, /*next match offset*/0, 0},
{"AceOfSpades", (int_t)40, /*next match offset*/0, 0},
{"Num2OfSpades", (int_t)41, /*next match offset*/0, 0},
{"Num3OfSpades", (int_t)42, /*next match offset*/0, 0},
{"Num4OfSpades", (int_t)43, /*next match offset*/0, 0},
{"Num5OfSpades", (int_t)44, /*next match offset*/0, 0},
{"Num6OfSpades", (int_t)45, /*next match offset*/0, 0},
{"Num7OfSpades", (int_t)46, /*next match offset*/0, 0},
{"Num8OfSpades", (int_t)47, /*next match offset*/0, 0},
{"Num9OfSpades", (int_t)48, /*next match offset*/0, 0},
{"Num10OfSpades", (int_t)49, /*next match offset*/0, 0},
{"JackOfSpades", (int_t)50, /*next match offset*/0, 0},
{"QueenOfSpades", (int_t)51, /*next match offset*/0, 0},
{"KingOfSpades", (int_t)52, /*next match offset*/0, 0},
{"TotalCards", (int_t)53, /*next match offset*/0, 0},
{"}", 0, 0, 16},
};
return defs + 1;
}
public:
static METADATA md = {"CardDeck", entries(), 54};
return &md;
}
static enum_t first(){
return CardDeck<TOuter>::Joker;
}
static enum_t last(){
return CardDeck<TOuter>::TotalCards;
}
static PENTRY find(int_t val){
PENTRY defs = entries();
HASHNODE nodes[] = {
{{defs + 0/* 0 */,defs + 53/* 53 */,},2},
{{defs + 1/* 1 */,},1},
{{defs + 2/* 2 */,},1},
{{defs + 3/* 3 */,},1},
{{defs + 4/* 4 */,},1},
{{defs + 5/* 5 */,},1},
{{defs + 6/* 6 */,},1},
{{defs + 7/* 7 */,},1},
{{defs + 8/* 8 */,},1},
{{defs + 9/* 9 */,},1},
{{defs + 10/* 10 */,},1},
{{defs + 11/* 11 */,},1},
{{defs + 12/* 12 */,},1},
{{defs + 13/* 13 */,},1},
{{defs + 14/* 14 */,},1},
{{defs + 15/* 15 */,},1},
{{defs + 16/* 16 */,},1},
{{defs + 17/* 17 */,},1},
{{defs + 18/* 18 */,},1},
{{defs + 19/* 19 */,},1},
{{defs + 20/* 20 */,},1},
{{defs + 21/* 21 */,},1},
{{defs + 22/* 22 */,},1},
{{defs + 23/* 23 */,},1},
{{defs + 24/* 24 */,},1},
{{defs + 25/* 25 */,},1},
{{defs + 26/* 26 */,},1},
{{defs + 27/* 27 */,},1},
{{defs + 28/* 28 */,},1},
{{defs + 29/* 29 */,},1},
{{defs + 30/* 30 */,},1},
{{defs + 31/* 31 */,},1},
{{defs + 32/* 32 */,},1},
{{defs + 33/* 33 */,},1},
{{defs + 34/* 34 */,},1},
{{defs + 35/* 35 */,},1},
{{defs + 36/* 36 */,},1},
{{defs + 37/* 37 */,},1},
{{defs + 38/* 38 */,},1},
{{defs + 39/* 39 */,},1},
{{defs + 40/* 40 */,},1},
{{defs + 41/* 41 */,},1},
{{defs + 42/* 42 */,},1},
{{defs + 43/* 43 */,},1},
{{defs + 44/* 44 */,},1},
{{defs + 45/* 45 */,},1},
{{defs + 46/* 46 */,},1},
{{defs + 47/* 47 */,},1},
{{defs + 48/* 48 */,},1},
{{defs + 49/* 49 */,},1},
{{defs + 50/* 50 */,},1},
{{defs + 51/* 51 */,},1},
{{defs + 52/* 52 */,},1},
};
HASHNODE& node = nodes[positiveModulus(val, 53)];
for(size_t i=0; i<node.size; i++){
if( val == node.ents[i]->value )
return node.ents[i];
}
return defs + 54;
}
typedef struct _hashnode {
PENTRY ents[2];
size_t size;
} HASHNODE, *PHASHNODE;
};//end class reflector_t

operator enum_t() const { return
(enum_t)reflector_t::entries()[___offset___].value; }
operator std::string() const { return reflector_t::name() + "::" +
std::string(reflector_t::entries()[___offset___].name); }
CardDeck(enum_t newval) {
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
}
CardDeck& operator = (enum_t newval){
PENTRY pe = reflector_t::find(newval);
___offset___ = (int)(pe - reflector_t::entries() );
return *this;
}
typedef enumerations::ExceptionT<CardDeck<TOuter>> Exception;
void tryThrowException(const std::string& msg=std::string() ) const {
PENTRY pe = reflector_t::entries()+ ___offset___;
if( pe->flags & 1 )
throw Exception(*this, msg);
}
State operator() () const {
if( ___offset___ < 0 ) return BeforeEntries;
return ( ___offset___ >= 54 ) ? AfterEntries : Valid;
}
protected:
enumerations::Enumeration& operator = (const intval_t newVal){
PENTRY pe = reflector_t::find(newVal.nval);
___offset___ = pe - reflector_t::entries();
return *static_cast<Enumeration*>(this);
}
};
}; // end namespace enumsamples```

There's quite a lot to digest here! Here are the main points:

• A class instance is roughly equivalent to a single value from its contained enumeration. The current value of that instance is retrieved through the `enum_t` cast operator.
• The class is mutable. The iterators (`++` and `--`) and copy (`=`) operator change the current value of an instance.
• The only member variable comes from the `Enumeration` base class. Therefore, the `sizeof() `operator returns something tiny, and you can return these class objects from functions.
• The classes implement both the copy operator and copy constructor.
• The addition and subtraction fix operators are inherited from the base `Enumeration` class (`++` and `--`). Both operators are iterative; they change the current value to the next or previous constant in the enumeration. No addition or subtraction takes place.
• The `tryThrowException()` function will throw an `enumerations::Exception` when the enumerated constant is a JSON block with an attribute `Throwable: true`.
• The base class allows dumping any `enumerations::Enumeration` object to `std::ostream` class instances, like `std::cout`. The output will display the value as a `string `rather than a numeric constant. This is extremely valuable for the purpose of troubleshooting.

The following code demonstrates how to plug in the template classes generated by the jsmeta tool:

```class Foo
{
public:
typedef enumsamples::SequentialEnum<Foo> SequentialEnum;
typedef enumsamples::SampleBitwiseEnum<Foo> SampleBitwiseEnum;

typedef enumsamples::printing::PaperSizes<Foo> PaperSizes;
};

//You can define "global" enums outside a class.  Just use <int> as the parameter type.

typedef enumsamples::CardDeck<int> PlayingCard;```

It's a great time to point something out: the generated `enum `classes are all templates. This allows type isolation based on the template parameter. If you `typedef SampleEnum<T> SampleEnum` twice with different template parameters for `T`, the resulting types are incompatible. This is necessary since we will generally use the outer class as a template parameter, and an `enum `defined inside one class should usually be incompatible with an `enum `inside another class, even if both enumerations came from the same template. The opposite use case is also possible. If you just use `int` as a random template parameter, you could actually `typedef` the same enumeration multiple times inside multiple classes, and the resulting types would all be compatible. So templating these classes makes possible two different, mutually exclusive use cases. See testharness.cpp for an illustration of this concept.

## Points of Interest

As I was inserting code snippets, it struck me that I could generate a large amount of C++ code from a relatively small amount of script code. All of this can be done without adding keywords or other proprietary features not supported by the C++ standard. I'm not saying it's a bad thing, but it requires a substantial amount of build functionality to support the proprietary language extensions of QT or Managed C++. Supporting first-class enumerated values requires a pretty small amount of build functionality, and it does not depend on any deviation from the C++ standard.

Another thing I noticed about this effort is that there are actually two levels of code generation at play in this solution. Most solutions for code generation, like IDLs (Interface Definition Languages), are completely declarative. Using JSON, we are actually able to dynamically populate the declaration block, giving us two software layers in which the contents of the enumeration classes are built. Layer #1 is the jsmeta tool, and layer #2 is the optional ability to dynamically populate the JSON objects.

There are two sides to every coin, and generating code makes your binary larger. This can be overkill when you have limited memory available. Note that the names/metadata for the enumerations are `static `variables, always taking up a chunk of data space in your binary. One very interesting scenario is when software is used to control machinery. In this use case, every line of code requires test coverage, and the results must be logged. Code generation must be used cautiously in situations where the safety of a machinist depends on reliable software. First class `enum`s aren't always appropriate for every use case of C++!

Let's wrap up where we started, with a deck of cards. Enumerations were dealt a bad hand in C++, since the constant names in `enum`s get discarded when code is compiled. Using C++ in the standard way, `enum `constants compile into numbers that simply forget their mapped names. Fortunately, C++ has powerful language features, like operator overloading, that allow us to generate classes to act like feature-rich enumerations. In the card deck enumeration from the sample code, we can now print out the named constant for each card instead of just its integer mapping. Using this functionality, it now becomes easy to see when we have an ace and a ten in hand!

## History

• Version 1.0, October 2010

## Share

 Software Developer (Senior) CDK Global, LLC United States
Director of Software Engineering for a startup software/hardware solution provider based in Silicon Valley. Currently in search of a new position with another company.

## You may also be interested in...

 Pro

 First Prev Next
 Vote -1 Da_Hero25-Feb-13 2:40 Da_Hero 25-Feb-13 2:40
 My vote of 1 Member 414658124-Nov-10 9:53 Member 4146581 24-Nov-10 9:53
 Re: My vote of 1 [modified] Chris Grimes9-Dec-10 16:49 Chris Grimes 9-Dec-10 16:49
 My vote of 1 shenron22-Nov-10 22:32 shenron 22-Nov-10 22:32
 Re: My vote of 1 Chris Grimes9-Dec-10 17:51 Chris Grimes 9-Dec-10 17:51
 My vote of 5 Hatem Mostafa22-Nov-10 0:33 Hatem Mostafa 22-Nov-10 0:33
 Useful information Hatem Mostafa22-Nov-10 0:32 Hatem Mostafa 22-Nov-10 0:32
 Last Visit: 31-Dec-99 18:00     Last Update: 25-Apr-17 6:53 Refresh 1