Bidirectional mapping between enum values and types
Recently, while working with a glue code integrating low level C APIs in C++ I stumbled upon a problem where I needed to map enum values to types (and vice-versa).
Problem definition
Imagine you’ve got a factory function:
|
|
Okay, so the create
function must be a template but how to determine the
return type? Well, mapping from enum Types
to any of the types is needed.
It’s easy to create one thanks to C++’s constant value template specialisation.
|
|
With the above mapping, the implementation of create
function is quite trivial.
|
|
Cool, job done. But, is it possible to create reverse mapping? Mapping from any of the aforementioned types back to its respective enum? Well, that can be done in an equally trivial manner by introduction of another specialised type.
|
|
The above can be implemented using consteval
function as well but, for the
time being, I’m gonna stick with a more traditional syntax.
|
|
This will definitely work but it doesn’t feel optimal as there are two mappings that have to be independently maintained. It would be perfect to have only one mapping capable of bidirectional resolution between types and enumerations.
Limitations
To implement two-way mapping between types and enums, I’ll have rely on some assumptions and inevitably will need to introduce some limitations.
Adjustment to the enum type
I will need two extra values in the enum
for it to be supported by the
discussed code. First
and Last
so, the resulting enum will be:
|
|
These are commonly introduced within enums so this might not be a problem but can be a deal breaker in some case so, it’s worth to keep that in mind.
Enums must be contiguous
Second assumption is that all values within an enum
must form a contiguous
range. Enums containing non-contiquous ranges, with explicitly defined values
won’t be supported e.g.:
|
|
The reason for this will become obvious later on.
Enums must map to distinct types
This limitation is fairly obvious as well. It’s not really a problem to map a set of enumerations to exactly the same type but the implication is that it won’t be possible to do the reverse opposite. Therefore, enum values must map to distinct types for the reverse mapping to be possible.
Mapping enums to types
Let’s start with a declaration of the mapping type:
|
|
Let’s first take care of the simplest case - which is mapping enums
to types.
The following specialisation should take care of that nicely.
|
|
With the following, it’s possible to create mappings the following way:
|
|
This can be simplified slightly with a using
statement similarly as commonly
done in STL:
|
|
That’s nice, half of the problem is solved. How to implement the reverse mapping though?
Mapping types to enums
In essence, finding an enum value for a given type is the same as performing an index operation on a type list. Which means that the problem boils down to declaring a typelist containing all types. I’ve discussed typelists thoroughly in my previous post. It’s required to have familiarity with concepts discussed there in order to proceed. From now on, I’ll assume you did that and we are basically on the same page (quite literally :)).
Initial type list
Let’s first specialise the EnumToType
mapping for the first enum value.
|
|
This declares a type list with a single type void
in it. With that in place,
the specialisation for other enum values has to be altered to contain
tmap
as well. But what should it be? Each new tmap
should effectively
append a new type to already existing type list (the one declared for prior
enum value). So, the first prerequisite is to be able to determine prior enum
value, given the one at hand. For that purpose I’m gonna define this simple
type:
|
|
This could be simplified with std::to_underlying
but I want to stick with
c++17 compatibility. It’s a simple function which just decrements the given
enum value and casts it back to the given enum type. Additionally it clamps
the value to First
so you can never go below the smallest enumeration
available.
I already have the initial declaration of the type list for First
enumeration, now all subsequent ones should just append new type to this map.
Here’s the updated EnumToType
:
|
|
Having the above, the last thing required is the mapping for Last
which is a
generalised case:
|
|
The complete type list can be access through Last
enumeration value so, to
index a type we have to do the following:
|
|
For the sake of completeness, I’m gonna declare a helper macro for extracting a type associated with an enum:
|
|
That concludes the implementation.
Usage example
Having the following enums and types:
|
|
A bidirectional mapping can be declared the following way:
|
|
To extract a type associated with an enum, we can use ENUM_TO_TYPE
so, the
definition of the factory function used in my first example might look like so:
|
|
Enum lookup by type is possible as well:
|
|
Conclusion
The code discussed here in its entirety is available in my gitlab repo.