In declarative C ++ programming

Often I have to face the problem of matching the parameter space of one API in the parameter space of another. Often I see that this is solved using nested nested nested ... switch statements.

And I was wondering if there would be a library or a technique that allows you to "declare" a mapping instead of a "program."

A trivial example would be to combine the values โ€‹โ€‹of two enumerations into one:

namespace sourceAPI { struct A { typedef e { A1, A2, A3 } }; struct B { typedef e { B1, B2 } }; } namespace targetAPI { struct AB { typedef e { A1B1, A1B2, A2B1, A2B2, A3B1, A3B2 } }; } 

In which the display is often performed, for example,

 switch( a ){ case( A::A1 ): switch( b ) { case( B::B1 ): return A1B1; case( B::B2 ): return A1B2; .... } 

And this display still needs a "reverse" switch.

But I would like something "dense" like

 declare( source( A::A1, B::B1 ), target( AB::A1B1 ) ); declare( source( A::A1, B::B2 ), target( AB::A1B2 ) ); .... 

Has anyone seen such a technique or structure or library?

+4
source share
5 answers

You can use Boost.Bimap , which provides bi-directional matching between two types.

This has a bit of overhead at runtime (usually about the same amount of overhead you would get with a std::map pair for this purpose, which is not much).

This allows you to define mappings as tightly as your example; usually you just add pairs to the map, one pair at a time.

+3
source

In many cases, you can accomplish this with simple lookup tables. Since the listed types can be assigned to integer values, you can use them as an index in an array of enumerated types of another type, therefore, quickly and easily convert. It has a pleasant side effect of being as fast as humanly possible. Depending on your use, the lookup tables can be quite large (but if you do a switch statement with one case for each enumeration, this will be even larger). In addition, if you need bidirectional conversion, you will need to do 2 lookup tables, one for each direction.

Also, keep in mind that many compilers can optimize these types to the minimum data type needed to store each value. There are ways around this (often this is a compiler flag, or you can simply declare a dummy value of something like 0xffffffff to force a 32-bit enumeration), but it's worth noting.

If you have non-integer values, you can use maps. STL includes several flavors, and, as mentioned above, boost has a nice, bi-directional.

+1
source

Using a table driven approach is accurate - this is equivalent.

There are two problems you need to worry about: changes to the enum layout (which change the routine) or changes to the contents of the enum (add / remove).

Whatever solution you choose, you want to mitigate the problems caused by these two problems.

In my own work, I prefer to use a template like this:

 TargetAPI ToTargetAPI(SourceAPI source) { // ... } 

If a situation may arise, I turn to another ToXXXX method.

For enumerations, my ToXXXX methods are either a single switch, or a table search (or, in some cases, expression conversion), and I check the input ranges using the code that generates (whether it limits the check or the default instruction on the switch).

For me, the mechanism of transferring from one type to another is less important than designing, which prevents errors from occurring when changing the API if they do not work and do not work quickly. Think of it this way: will you lose more time entering the full switch statement (nested or not) with error checking or tracing an error from an enumeration that is out of range and not noted?

+1
source

For this particular type of task, you can often cheat a bit and just use non-overlapping bit patterns for two enumerations. For instance:

 enum A::ea; enum B::eb; // ... set values of a and b. AB::e result = (b << 2) | a; 

In this particular case, since A has exactly three members, the result is even an adjacent range of values. When the number of members (just one enumeration) is only one less than two, the result will not be touching, though.

I am sure that your question is really intended to be more general than just finding a way to deal with this particular problem. In fact, I suspect that this example is purely hypothetical. Unfortunately, it's hard to guess what other types of problems you might be interested in. Of course, in C ++ there are several examples of declarative programming. For a few obvious examples, pretty much everything that Boost Spirit or Boost Xpressive uses ends with at least some declarative programming. For better or worse, however, both of them are devoted to similar problems that are slightly different from those that you seem concerned about.

0
source

A std :: map with boost :: tuple as a key.
If you don't mind using make_tuple in your ad, you already have variable key elements in your ad for free. You need to make_tuple to perform the actual conversion.

Edit:
Things get more complicated when ranges or wildcards are needed.

0
source

All Articles