Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Hierarchical Enums in C++

I'm working on a message parser/generator subsystem. I'm creating an auto-generator that uses a database that contains all of the information about this protocol, including enum lists, to generate the code. One thing I came across is the need for hierarchical enumerations.

updated

(I was trying to simplify things by not describing the full problem, but the comments below make it obvious that I erred by simplifying too much.)

The Database being used will store things as simplified strings (customer decision), but the protocol only speaks "byte triplets" (aka Hierarchical Enum). The full problem could be described as such:

Given a set of unique strings that each correspond with a unique triplet, 1) find the triplet for any given string, and 2) find the string for any given triplet. Make sure to account for "Undefined" and "No Statement" enumerations (which do not have strings associated with them). [As one poster noted, yes it is insane.]

(Caveat: I've been doing C++ for well over a decade, but I've been doing Java this last year -- my C++ is probably "corrupted".)

So, to use an admittedly contrived example, given:

// There is only one category
// POP= "P", COUNTRY= "K", CLASSICAL= "C"
enum Category {POP, COUNTRY, CLASSICAL};

// There is one Type enum for each Category.
// ROCK= "R", BIG_BAND = "B", COUNTRY_POP= "C" 
enum PopType {ROCK, BIG_BAND, COUNTRY_POP};
enum CountryType {CLASSICAL_COUNTRY, MODERN_COUNTRY, BLUEGRASS, COUNTRY_AND_WESTERN};
// ...

// There is one Subtype for each Type
// EIGHTIES= "E", HEAVY_METAL= "H", SOFT_ROCK= "S"
enum RockSubType { EIGHTIES, HEAVY_METAL, SOFT_ROCK};
// ...

When I get 0, 0, 0 (Pop, Rock, Eighties), I need to translate that to "PRE". Conversely, if I see "PC" in the Database, that needs to be sent out the wire as 0, 2 (Pop, Country, NULL).

I'm blatantly ignoring "Undefined" and No Statement" at this point. Generating a triplet from a string seems straight forward (use an unordered map, string to triple). Generating a string from a triplet (that may contain a NULL in the last entry) ... not so much. Most of the "enum tricks" that I know won't work: for instance, Types repeat values -- each Type enum starts at zero -- so I can't index an array based on the Enum value to grab the string.

What's got me is the relationship. At first glance it appears to be a fairly straight forward "is-a" relationship, but that doesn't work because this case is bidirectional. The leaf -> root navigation is very straight forward, and would be appropriate for a class hierarchy; unfortunately, going the other way is not so straight forward.

I cannot "hand roll" this -- I have to generate the code -- so that probably eliminates any XML based solutions. It also has to be "reasonably fast". The "Java Solution" involves using protected static variables, initialized on construction, and abstract base classes; however, I do not believe this would work in C++ (order of initialization, etc.). Plus, aesthetically, I feel this should be ... more "const". Other code I've seen that tackles this problem uses unions, explicitly listing all of the enum types in the union.

The only other thing I can come up with is using Template Specialization and explicit specialization, but I'm at a loss. I did a web search on this, but I found nothing that would tell me if it would even work. Still, if it can be done with a union, can't it be done with Template Specialization?

Is it possible to do something like this using templates, specialization, explicit specialization? Is there another, more obvious, solution (i.e. a design pattern that I've forgotten) that I'm missing?

Oh, before I forget -- the solution must be portable. More specifically, it must work on Windows (Visual Studio 2010) and Redhat Enterprise 6/Centos 6 (GCC 4.4.4 IIRC).

And, lest I forget, this protocol is huge. The theoretical max on this is about 133,000 entries; once I include "Undefined" and "No Statement" I'll probably have that many entries.

Thanks.

like image 309
John Price Avatar asked Nov 14 '11 16:11

John Price


2 Answers

Effectively, you are in a bit of a pinch here.

My proposal would imply first using 3 enums:

  • Category
  • Type
  • SubType

With no distinction (at first) between the various types or subtypes (we just throw them all in the same basket).

Then, I would simply use a structure:

struct MusicType {
  Category category;
  Type type;
  SubType subtype;
};

And define a simple set of valid types:

struct MusicTypeLess {
  bool operator()(MusicType const& left, MusicType const& right) const {
    if (left.category < right.category) { return true; }
    if (left.category > right.category) { return false; }

    if (left.type < right.type) { return true; }
    if (left.type > right.type) { return false; }

    return left.subtype < right.subtype;
  }
};

MusicType MusicTypes[] = {
  { Category::Pop, Type::Rock, SubType::EightiesRock },
  ...
};

// Sort it on initialization or define in sorted during generation

Then you can define simple queries:

typedef std::pair<MusicType const*, MusicType const*> MusicTypeRange;

MusicTypeRange listAll() {
  return MusicTypeRange(MusicTypes, MusicTypes + size(MusicTypes));
}

namespace {
  struct MusicTypeCategorySearch {
    bool operator()(MusicType const& left, MusicType const& right) const {
      return left.category < right.category;
    }
  };
}

MusicTypeRange searchByCategory(Category cat) {
  MusicType const search = { cat, /* doesn't matter */ };
  return std::equal_range(MusicTypes,
                          MusicTypes + size(MusicTypes),
                          search,
                          MusicTypeCategorySearch());
}

namespace {
  struct MusicTypeTypeSearch {
    bool operator()(MusicType const& left, MusicType const& right) const {
      if (left.category < right.category) { return true; }
      if (left.category > right.category) { return false; }

      return left.type < right.type;
    }
  };
}

MusicTypeRange searchByType(Category cat, Type type) {
  MusicType const search = { cat, type, /* doesn't matter */ };
  return std::equal_range(MusicTypes,
                          MusicTypes + size(MusicTypes),
                          search,
                          MusicTypeTypeSearch ());
}

// little supplement :)
bool exists(MusicType const& mt) {
  return std::binary_search(MusicTypes, MusicTypes + size(MusicTypes), mt);
}

Because the array is sorted, the operations are fast (log N), so it should go smoothly.

like image 74
Matthieu M. Avatar answered Oct 21 '22 08:10

Matthieu M.


I think the Music class should contain the sub genres...(has-a) also called aggregation.

like image 1
Steve Wellens Avatar answered Oct 21 '22 09:10

Steve Wellens