image0

We have several pieces of code at Tetrane that associate data to enumeration values, only relevant for ad-hoc evaluations. A simple example is to match a string to the different enum values, for example for representing CPU registers:

enum Registers {
    EAX = 0,
    EBX,
    // ...
};

const std::pair<Registers, std::string> descriptions[] = {
    { Registers::EAX, "EAX" },
    { Registers::EBX, "EBX" },
    // ...
};

Now, because Registers can be "extended" with more values in the future, the problem of detecting that descriptions lacks data when new enum values are added arises. We want to detect this at compile-time, but descriptions is only a sample: we have several arrays like that spread in sources.

It is not possible to count the number of items in Registers. A common technique is to have a last enum value that stands as the count, given that all items have consecutive integral items:

enum Registers {
    EAX = 0, EBX, //...
    Count
}

That way, we can fail at compile-time if the number of items in descriptions differs from Registers::Count:

enum Registers {
    EAX = 0, EBX, ECX, // one more
    Count
}

// Provides the number of items in an array at compile-time.
template <typename Type, std::size_t Count>
constexpr std::size_t countOf(Type (&)[Count]) {
    return Count;
}

static_assert(
    countOf(descriptions) == Registers::Count,
    "descriptions and Registers don't have the same number of values");

Now, that's cool that we can detect this upon compilation. However, it would be even better to dump the number of elements in both descriptions and Registers! But that's not possible in a static_assert as it only accepts string literals for the description. If the assertion above failed, we would get:

error: static assertion failed: descriptions and Registers don't have the same number of values

We can rely on a trick by triggering a warning in GCC which does print the values when the comparison fails. The idea is to provoke an overflow that is detected by GCC; we enable this using SFINAE:

template <int N> struct TriggerOverflowWarning {
    static constexpr char value() {  return N + 256; }
};

template <int N, int M, typename Enable = void>
struct CheckEqualityWithWarning: std::true_type {};

template <int N, int M>
struct CheckEqualityWithWarning<N, M, typename std::enable_if<N != M>::type> {
    static constexpr bool value = (TriggerOverflowWarning<N>::value()
        == TriggerOverflowWarning<M>::value());
};

static_assert(
    CheckEqualityWithWarning<countOf(descriptions), Registers::Count>::value,
    "descriptions misses values from Registers");

Here is the output:

In instantiation of ‘static constexpr char TriggerOverflowWarning::value() \
    [with int N = 3]’:
  required from ‘constexpr const bool CheckEqualityWithWarning<2, 3>::value’
  required from here
warning: overflow in implicit constant conversion [-Woverflow]
In instantiation of ‘static constexpr char TriggerOverflowWarning::value() \
    [with int N = 2]’:
  required from ‘constexpr const bool CheckEqualityWithWarning<2, 3>::value’
  required from here
warning: overflow in implicit constant conversion [-Woverflow]
In function ‘int main()’:
error: static assertion failed: descriptions misses values from Registers

The output is several lines long, and it is arguably not very elegant, but it is undoubtedly faster to diagnose this error since it provides more precise information regarding what's wrong. Otherwise you would have to (manually) count the number of values in both the enum and the array, to detect how many values descriptions is missing (or perharps it contains too many pairs!). And I am too lazy spending time doing this kind of tasks: it's boring and error-prone. After all, programming is all about automation.


Comments

comments powered by Disqus