Click here to Skip to main content
15,879,474 members
Articles / Programming Languages / C++

Making C++ enumerations first class citizens

Rate me:
Please Sign up or sign in to vote.
2.57/5 (5 votes)
27 Jan 2019CPOL6 min read 8.1K   4   11
Overcoming C++'s weak enumerations and making them powerful tools

Introduction

This is the first in a set of articles I'll be posting, which explore some of the technologies I've created over a long period of time as part of a large projects. It is about a million lines of code, all written by me. About half is a general purpose system called CIDLib and on top of that is built an automation platform called CQC. This article is about a smallish but extremely important feature of the general purpose code, which is the ability to take C++ enumerations up a number of steps in power. 

The techniques demonstrated here are not very complicated as such things go, so it's not that hard to do and the benefits are significant.

IDL Generation

Most of the systems I create are highly networked, and a key component of all of them is my ORB (Object Request Broker.) I'll get into that more in a future article, but most ORBs have some sort of IDL (Interface Description Language) which is used to define client/server interfaces for subsequent code generation. My ORB has such an IDL and associated compiler, but it also can generate other things like constants and enumerations. Over the years I've continued to refine the enumeration support , and it is now quite powerful.

Here is what a typical standard (non-bitmapped) enumeration definition might look like, though this only demonstrates a fairly limited set of the available capabilities.

<CIDIDL:Enum CIDIDL:Name="EDataPnts" CIDIDL:XlatMap="BaseName"
             CIDIDL:SafeStyle="Yes" CIDIDL:BinStream="Yes"
             CIDIDL:TextStreamMap="Text" CIDIDL:IncDec="Inc">
    <CIDIDL:DocText>
        These are the data points that the clients report to the
        server. The base name matches the text in the msg, for easy
        translation.
    </CIDIDL:DocText>
    <CIDIDL:EnumVal CIDIDL:Name="ExtHumidity"   CIDIDL:Text="External Humidty"/>
    <CIDIDL:EnumVal CIDIDL:Name="ExtTemp"       CIDIDL:Text="External Temp"/>
    <CIDIDL:EnumVal CIDIDL:Name="IntHumidity"   CIDIDL:Text="Internal Humidity"/>
    <CIDIDL:EnumVal CIDIDL:Name="IntTemp"       CIDIDL:Text="Internal Temp"/>
    <CIDIDL:EnumVal CIDIDL:Name="Lux"           CIDIDL:Text="Luminosity"/>
</CIDIDL:Enum>

The key capabilities that the IDL generated enumerations have are:

  •  Validity checking
  •  Binary streaming
  •  Formatted text output streaming
  •  Up to four different mechanisms for converting between the enumeration and different text formats
  •  Increment/decrement operators
  •  Conversion between the enum and up to to two different numerical mappings
  •  If bitmapped, it provides bitwise operations

In the above example only a few of these capabilities are demonstrated, which are:

  •  XlatMap="BaseName" - This allows you to translate back and forth between the value names and the enumeration. Here it says to use the base names of the values (ExtHumidity, Lux, etc...)
  •  TextStreamMap="Text" - This makes the enumeration available for << operations on my text streams, in this case it will use the associated text of the values, which here is set inline via the Text="" attribute on each value. Text can be set others ways as well, such as via loadable (translatable) text.
  •  IncDec="Inc" - This is enabling an increment operator on this enumeration, no decrement in this case.
  •  BinStream="Yes" - Supports streaming to/from my binary streams.

 

  •  Note that the SafeStyle attribute makes it generate 'enum class' type enums. This could be removed now. It was part of a massive, but necessarily incremental, translation of this large code base to the newer style enumerations.

IDL Output

The output from the IDL compiler includes of course both header and implementation file content. The header content is generated partly into a namespace (part of my overall application/library architecture which I won't get into here) and partly some global stuff. The namespace based content would look like this:

enum class EDataPnts
{
    ExtHumidity
    , ExtTemp
    , IntHumidity
    , IntTemp
    , Lux
    , Count
    , Min = ExtHumidity
    , Max = Lux
};
EDataPnts eXlatEDataPnts
(
   const TString& strToXlat, const tCIDLib::TBoolean bThrowIfNot = kCIDLib::False
);
const TString& strXlatEDataPnts
(
   const EDataPnts eToXlat, const tCIDLib::TBoolean bThrowIfNot = kCIDLib::False
);
tCIDLib::TBoolean bIsValidEnum(const EDataPnts eVal);

So it generates the enum itself, the text to enum translation method, the enum to text translation, and the validity checker method. Note the magic values it generates as well, which are count and min/max values. The global header stuff it spits out in this case would be:

TBinOutStream& operator<<(TBinOutStream& strmTar, const tVideoDemoC::EDataPnts eToStream);
TBinInStream& operator>>(TBinInStream& strmSrc, tVideoDemoC::EDataPnts& eToFill);
tCIDLib::TVoid TBinInStream_ReadArray
(
   TBinInStream& strmSrc, tVideoDemoC::EDataPnts* const aeList, const tCIDLib::TCard4 c4Count
);
tCIDLib::TVoid TBinOutStream_WriteArray
(
   TBinOutStream& strmTar, const tVideoDemoC::EDataPnts* const aeList, const tCIDLib::TCard4 c4Count
);
tVideoDemoC::EDataPnts operator++(tVideoDemoC::EDataPnts& eVal, int);
TTextOutStream& operator<<(TTextOutStream& strmTar, const tVideoDemoC::EDataPnts eToStream);

So here it provides binary streaming operators, the text output streaming operator, the increment operator, and also array based binary streaming operators if binary streaming is enabled.

The implementation is a good bit more voluminous so I won't reproduce it all. But the first it generates a map that includes all of the numeric and text mapping options for the enumeration, which looks like this (wrapped for easier reading here):

static TEnumMap::TEnumValItem aeitemValues_EDataPnts[5] =
{
    {
       tCIDLib::TInt4(tVideoDemoC::EDataPnts::ExtHumidity), 0, 0
       ,  { L"", L"", L"", L"ExtHumidity", L"EDataPnts::ExtHumidity", L"External Humidty" } 
    }
  , {
       tCIDLib::TInt4(tVideoDemoC::EDataPnts::ExtTemp), 0, 0
       ,  { L"", L"", L"", L"ExtTemp", L"EDataPnts::ExtTemp", L"External Temp" } 
    }
  , {
       tCIDLib::TInt4(tVideoDemoC::EDataPnts::IntHumidity), 0, 0
       ,  { L"", L"", L"", L"IntHumidity", L"EDataPnts::IntHumidity", L"Internal Humidity" } 
    }
  , {
       tCIDLib::TInt4(tVideoDemoC::EDataPnts::IntTemp), 0, 0
       ,  { L"", L"", L"", L"IntTemp", L"EDataPnts::IntTemp", L"Internal Temp" } 
    }
  , {
       tCIDLib::TInt4(tVideoDemoC::EDataPnts::Lux), 0, 0
       ,  { L"", L"", L"", L"Lux", L"EDataPnts::Lux", L"Luminosity" } 
       }
};

static TEnumMap emapEDataPnts
(
     L"EDataPnts"
     , 5
     , kCIDLib::False
     , aeitemValues_EDataPnts
     , nullptr
     , tCIDLib::TCard4(tVideoDemoC::EDataPnts::Count)
);
The TEnumMap class is a helper class that keeps a lot of the grunt work support for these smart enums out of line, so it's not generated over and over again. It could have been templated or generated, but I chose not to. An awful lot of enumerations are created in this code base and this avoids a lot of final code size. Since this is generated code, the subsequently generated methods can easily (and accurately) handle casting to/from the actual enum type as required. The map class primarily provides lookup methods to handle all of the back and forth mapping operations, based on the provided values map entries.
 
Here is an example of the streaming output operator:
 
TTextOutStream& operator<<(TTextOutStream& strmTar, const tVideoDemoC::EDataPnts eVal)
{
    strmTar << emapEDataPnts.strMapEnumVal
    (
         tCIDLib::TCard4(eVal), TEnumMap::ETextVals::Text, kCIDLib::False
    );
    return strmTar;
}
It calls the strMapEnumVal() method on the generated map object. It passes the value to map (cast to an unsigned value) and which of the possible text mapping values to map. The last parameter allows it to either throw if the value is invalid, or to output a standard "???" string to indicates the value was unknown, in this case the latter.
 
Example Code

Here are some examples of how this functionality can be used. It really makes a huge difference. Note that my indexed collection classes (vectors and arrays) allow for enumerations as indices. So I can use type safe (normally unconvertable) enums as indices without jumping through any conversion hoops:

TOutConsole conOut;
TVector<TString, tDemo::EDataPnts, tDemo::EDataPnts::Count> colTheValues;

// Load up each slot with its own text
tDemo::EDataPnts eCurPnt = tDemo::EDataPnts::Min;
for (; eCurPnt <= tDemo::EDataPnts::Max; eCurPnt++)
    colTheValues.objAdd(tDemo::strXlatEDataPnts(eCurPnt));

//
//  We can go back and validate the values we added to the list
//  by translating back the other direction.
//
tDemo::EDataPnts eCurPnt = tDemo::EDataPnts::Min;
for (; eCurPnt <= tDemo::EDataPnts::Max; eCurPnt++)
{
    if (tDemo::eXlatEDataPnts(colTheValues[eCurPnt]) != eCurPnt)
    {
        conOut << L"Round trip translation failed on point: " << eCurPnt
               << kCIDLib::NewLn;
        break;
    }
}

// Dump the text values to the output console. We'll use a lambda this time
colTheValues.ForEach
(
    [&conOut](const TString& strVal) { conOut << strVal; return kCIDLib::True; }
);

 

Summary

As mentioned, this is a fairly small example of what the IDL compiler can do for enumerations. We didn't demonstrate a lot of the features of standard enums, and didn't touch on bitmapped enums at all. But you hopefully get the picture. This article would have gotten quite long if I tried to demonstrate everything.

Nothing being done here is particular complicated. It's just parsing of the IDL content and spitting out the generated code. Of course this generation step needs to be integrated into the build process, which it very tightly is in my case since I have my own project system and build tools and so forth. But most build environments would provide the means to kick off a program based on the relative time stamps of a source IDL file vs the output files it is going to create. In my case it's just a bit easier since the build tools know about the IDL files and know what output files will be generated, what namespace and export keywords to use for the project, whther the project is a library or application, and so forth, so it's all very automatic.

However you go about implementing it, once you get used to this level of functionality in enumerations, which is really stronger than even more modern languages like C# in many ways, you can get spoiled. I feel like I'm really hobbled when I have to deal with straight C++ or C# and don't have these types of tools available to me.

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Founder Charmed Quark Systems
United States United States
Dean Roddey is the author of CQC (the Charmed Quark Controller), a powerful, software-based automation platform, and the large open source project (CIDLib) on which it is based.

www.charmedquark.com
https://github.com/DeanRoddey/CIDLib

Comments and Discussions

 
GeneralMy vote of 5 Pin
koothkeeper29-Jan-19 7:08
professionalkoothkeeper29-Jan-19 7:08 
See my comment below.
PraiseVisionary! Pin
koothkeeper29-Jan-19 7:07
professionalkoothkeeper29-Jan-19 7:07 
GeneralRe: Visionary! Pin
Dean Roddey29-Jan-19 9:47
Dean Roddey29-Jan-19 9:47 
QuestionFirst class citizens??? Pin
Paulo Zemek28-Jan-19 11:43
mvaPaulo Zemek28-Jan-19 11:43 
AnswerRe: First class citizens??? Pin
Dean Roddey28-Jan-19 12:29
Dean Roddey28-Jan-19 12:29 
GeneralRe: First class citizens??? Pin
Paulo Zemek28-Jan-19 12:54
mvaPaulo Zemek28-Jan-19 12:54 
GeneralRe: First class citizens??? Pin
Dean Roddey28-Jan-19 13:38
Dean Roddey28-Jan-19 13:38 
QuestionForest - Trees Pin
Rick York28-Jan-19 6:43
mveRick York28-Jan-19 6:43 
AnswerRe: Forest - Trees Pin
Dean Roddey28-Jan-19 8:34
Dean Roddey28-Jan-19 8:34 
GeneralRe: Forest - Trees Pin
Rick York28-Jan-19 9:32
mveRick York28-Jan-19 9:32 
GeneralRe: Forest - Trees Pin
Dean Roddey28-Jan-19 9:50
Dean Roddey28-Jan-19 9:50 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.