Search code examples
c++runtimeunordered-mapcompile-timestatic-initialization

Creating a value made out of two bytes at compile-time instead of runtime


I have an enumerator which can be used as a uint8_t and I want to use a combination of two values of this enumerator to create a number of type uint16_t where the leftmost and the rightmost bytes are determined by these 2 enumeration values.

I then need to use this resulting value as the key element type of a unordered_map<uint16_t, something> where I store several something values.

So I came up with this code which resolves two enumeration values into a key at run-time. The comments just indicate that the real problem involves more than the elements showed in the example below.

enum Value : uint8_t {
    V1, V2, V3, V4 // , ... , VN
};

static inline uint16_t compose(Value a, Value b) {
    return ((uint16_t) a << 8) | b;
}

static const unordered_map<uint16_t, something> elements = {
    { compose(Value::V3, Value::V1), something1 },
    { compose(Value::V4, Value::V4), something2 },
    { compose(Value::V1, Value::V2), something3 },
    // { ... }, { compose(Value::VA, Value::VB), somethingN }
};

If I'm not mistaken, compose will be called several times when the program starts and tries to initialise the variables and the constants before running main.

Now the big question:

Is there a better way to do this in order to compose the number at compile-time instead of runtime?

I could potentially hardcode the numbers instead of running compose, but that would be a very ugly and possibly unsafe solution.

Imagine if I had to switch the position of two or more "values" of the enumerator; that would require a complete revision of the hardcoded numbers so that's not an option at all.

It also would be very nice to maintain the names of the two enumeration values that are composing a uint16_t so when I need to edit the something corresponding to V1 and V4 I can find it easily.

Unfortunately I don't think that the preprocessor can do something like this and I doubt that the compiler is able to optimise compose even though I used the inline keyword.

Please help me.


Solution

  • In c++20, you can make compose a consteval function. Then it's guaranteed to be evaluated at compile time:

    consteval std::uint16_t compose(Value a, Value b) {
        return ((std::uint16_t) a << 8) | b;
    }
    

    Here's a demo.

    Note that you won't be able to call this function at run-time if you need to.