17

Is there a way to check in C++11 if an enum is continuous?

It is fully valid to give an enum values which are not. Is there maybe a feature like a type trait in C++14, C++17 or maybe C++20 to check is the enum is continuous? This to be used in a static_assert.

A small example follows:

enum class Types_Discontinuous {
  A = 10,
  B = 1,
  C = 100
};

enum class Types_Continuous {
  A = 0,
  B = 1,
  C = 2
};

static_assert(SOME_TEST<Types_Discontinuous>::value, "Enum should be continuous"); // Fails
static_assert(SOME_TEST<Types_Continuous>::value, "Enum should be continuous");    // Passes
JBRWilkinson
  • 4,821
  • 1
  • 24
  • 36
Bart
  • 1,405
  • 6
  • 32
  • 1
    Means continues, that it has ascending order or it means is starts with zero and then +1 for every value? – RoQuOTriX Feb 06 '20 at 11:23
  • 6
    There's no way to enumerate enumeration labels so it's not possible to do it from inside the program itself. – Some programmer dude Feb 06 '20 at 11:23
  • In this instance I do indeed mean starting from zero with increments of one. – Bart Feb 06 '20 at 11:25
  • @Someprogrammerdude but you could iterate over values and check in a switch case if this value has a matching label? But of course this only works for one enum – RoQuOTriX Feb 06 '20 at 11:25
  • @RoQuOTriX That would mean writing out all elements in the enum. This is the particular point I would like to avoid. – Bart Feb 06 '20 at 11:26
  • 1
    Interesting. I'm thinking along the lines of template programming along the lines of how you can get a compiler to calculate a factorial. You'd kick the thing off with the two bounds A and C, and the template functions check via SFINAE for the presence or otherwise of all the values between them in the `enum`. Sadly I have a day job so can't attempt to write this out, although I will upvote an answer based on this approach. I'm pretty sure someone like @barry or @sehe could do it. – Bathsheba Feb 06 '20 at 11:26
  • @Bart I wrote faster than i thought about it. Thought this would be easy... – RoQuOTriX Feb 06 '20 at 11:28
  • @RoQuOTriX It is indeed not difficult, I am just looking for the most beautiful solution ;-) – Bart Feb 06 '20 at 11:29
  • @Bart the problem itself isn't difficult, but a beautiful solution is :D – RoQuOTriX Feb 06 '20 at 11:41
  • 1
    @RoQuOTriX How would you match a value to a label? And how would you check the order of the labels? And how could it be done at compile-time (which is needed for `static_assert`)? Even if you can't make a "beautiful solution", please write an answer anyway as I'm very curious how it could be done in a generic way. – Some programmer dude Feb 06 '20 at 11:46
  • @Bart Is this just for curiosity, or do you have a real underlying problem that you think something like this will solve? If there's an underlying problem, please ask about it directly instead. – Some programmer dude Feb 06 '20 at 11:47
  • 1
    @Someprogrammerdude what you described is the "beautiful" or good solution. What I meant was the "easy" check solution, which you would have to rewrite for every enum and god bless, I hope no one does that – RoQuOTriX Feb 06 '20 at 11:48
  • @Bart If your only constraint is to use it in a `static_assert`, you could mutate your enumerations to classes that have `static constexpr` attributes and defer to them the responsibility to provide a `static constexpr` isContinue() function. Something like [this](https://godbolt.org/z/45kHkf). But I admit it is not a very elegant workaround. – Fareanor Feb 06 '20 at 11:51
  • @RoQuOTriX Oh okay. Yeah that's definitely not "beautiful" in any kind of way... :) – Some programmer dude Feb 06 '20 at 11:58
  • The most generic way to solve it is probably to write some kind of preprocessor program that can parse source files (or at least the `enum` definitions) and does the check and then outputs the static assertions to some auto-generated source file. This program needs to be run as a kind of pre-build step. – Some programmer dude Feb 06 '20 at 12:01
  • You could use X-macros to define your enum members, and then build on that. – jxh Feb 06 '20 at 21:30

4 Answers4

8

This is not possible in pure C++, because there is no way to enumerate the enum values, or discover the number of the values and minimum and maximum values. But you could try using the help of your compiler to implement something close to what you want. For example, in gcc it is possible to enforce a compilation error if a switch statement does not handle all values of an enum:

enum class my_enum {
    A = 0,
    B = 1,
    C = 2
};

#pragma GCC diagnostic push
#if __GNUC__ < 5
#pragma GCC diagnostic error "-Wswitch"
#else
#pragma GCC diagnostic error "-Wswitch-enum"
#endif

constexpr bool is_my_enum_continuous(my_enum t = my_enum())
{
    // Check that we know all enum values. Effectively works as a static assert.
    switch (t)
    {
    // Intentionally no default case.
    // The compiler will give an error if not all enum values are listed below.
    case my_enum::A:
    case my_enum::B:
    case my_enum::C:
        break;
    }

    // Check that the enum is continuous
    auto [min, max] = std::minmax({my_enum::A, my_enum::B, my_enum::C});
    return static_cast< int >(min) == 0 && static_cast< int >(max) == 2;
}

#pragma GCC diagnostic pop

Obviously, this is specialized for a given enum, but definition of such functions can be automated with preprocessor.

Andrey Semashev
  • 10,046
  • 1
  • 17
  • 27
  • If I understand correctly this would still require writing out all enum values in the switch and list for minmax. Currently I have multiple enums so it is indeed possible but not preferred for my situation. – Bart Feb 06 '20 at 16:24
  • "there is no way to enumerate the enum values" sums up the issue with enum in C... – Puck Jan 12 '23 at 11:09
7

For a number of enums you can probably hack your way through this using the Magic Enum library. For example:

#include "magic_enum.hpp"

template <typename Enum>
constexpr bool is_continuous(Enum = Enum{}) {
    // make sure we're actually testing an enum
    if constexpr (!std::is_enum_v<Enum>)
        return false;
    else {
        // get a sorted list of values in the enum
        const auto values = magic_enum::enum_values<Enum>();
        if (std::size(values) == 0)
            return true;

        // for every value, either it's the same as the last one or it's one larger
        auto prev = values[0];
        for (auto x : values) {
            auto next = static_cast<Enum>(magic_enum::enum_integer(prev) + 1);
            if (x != prev && x != next)
                return false;
            else
                prev = x;
        }
        return true;
    }
}

Note that this is indeed, as the library name implies, "magic" – the library functions on a number of compiler-specific hacks. As such it doesn't really meet your requirement of "pure C++", but is probably as good as we can get until we have reflection facilities in the language.

N. Shead
  • 3,828
  • 1
  • 16
  • 22
2

All enum's are continuous. 0 is always allowed; the highest value allowed is the highest enumerator rounded up to the next 1<<N -1 (all bits one), and all values in between are allowed too. ([dcl.enum] 9.7.1/5). If there are negative enumerators defined, the lowest value allowed is similarly defined by rounding down the lowest enumerator.

The enumerators defined in the enum are constant expressions with a value in range and the correct type, but you can define additional constants outside the enum which have the same properties:

constexpr enum class Types_Discontinuous = static_cast<Types_Discontinuous>(2)

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • 2
    Although you are correct, it is clear from the OP that we want to know this for the defined values. (PS: the down vote ain't mine) – JVApen Feb 06 '20 at 11:42
  • 1
    @JVApen: That's the exact problem. The "defined values" are not a property of the enum type itself. The standard is explicit what the values of the enum are. – MSalters Feb 06 '20 at 11:45
2

I'd love to see an answer on this. I've been needing it as well.

Unfortunately, I don't think this is possible using the existing utilities. If you want to implement a type trait on this, you need support from your compiler, so writing a template for it doesn't sound feasible.

I've already extended the enumeration with a specific tag to indicate it is contiguous and immediately gives you the size: enum class constructor c++ , how to pass specific value?

Alternatively, you can write your own trait:

 template<T> struct IsContiguous : std::false_type {};

This needs to be specialized whenever you define an contiguous enum where you want to use this. Unfortunately, this requires some maintenance and attention if the enum gets changed.

JVApen
  • 11,008
  • 5
  • 31
  • 67