0

If I have an enum class like so

enum class Enum {
    A = 1,
    B = 2,
};

I was under the impression that the compiler guarantees that instances of Enum must be either A or B. But I learned that the following is possible

auto instance = Enum{};
cout << (instance == Enum::A) << endl;
cout << (instance == Enum::B) << endl;
cout << static_cast<int>(instance) << endl;

And it prints 0 in all cases. Further I used to think having a switch-case like this is exhaustive

switch (instance) {
    case Enum::A:
        // do something
        break; 
    case Enum::B:
        // do something
        break; 
}

but apparently it's not, how can I handle a value-initialized instance above?

Curious
  • 20,870
  • 8
  • 61
  • 146
  • 1
    For an `enum class` without otherwise specified underlying type, the underlying type is `int` and the enum can have all values that `int` can. The enumerators simply give some of them names. – user17732522 Nov 18 '22 at 15:59
  • 1
    It's possible if your code has an UB earlier. A possible way to trigger UB via enums is `static_cast(0)`. But you likely don't want to do that. – lorro Nov 18 '22 at 16:00
  • Either make it a precondition on the caller that values outside the enumerators are not allowed or add a `default` clause with some error handling. – user17732522 Nov 18 '22 at 16:00
  • https://stackoverflow.com/questions/53897991/enum-class-default-initialization#comment126247751_53898593 – 273K Nov 18 '22 at 16:00
  • I agree that the linked duplicate is not asking the same question, so I reopened. – user17732522 Nov 18 '22 at 16:02
  • You have two questions. The dupe https://stackoverflow.com/questions/53897991/enum-class-default-initialization answers this *Also why is this allowed?* @user17732522 it's to you too – 273K Nov 18 '22 at 16:02
  • 3
    @lorro `static_cast(0)` is well-defined. – user17732522 Nov 18 '22 at 16:03
  • 2
    *how can I handle a value-initialized instance above?* By adding a `default` case. That is the case to use when the value is not one of the enumerations. – NathanOliver Nov 18 '22 at 16:03
  • @273K The "_Also why is this allowed?_" in this question seems to have been asking for a rationale, not for a deduction from the rules. But it is gone now anyway, so it doesn't matter. – user17732522 Nov 18 '22 at 16:05
  • @user17732522 CWG1766 It's UB. – lorro Nov 18 '22 at 16:09
  • 2
    @lorro `0` is in the representable range of the enum – NathanOliver Nov 18 '22 at 16:11
  • 1
    @lorro The range of an enumeration with fixed underlying type is that of the underlying type. The UB would only be relevant if the code used `enum` instead of `enum class` so that the underlying type isn't fixed. But even then `0` is allowed because the range is that of an imagined integer type of minimal width to hold all enumerator values. – user17732522 Nov 18 '22 at 16:12
  • Watch this : it is funny, insightfull and slightly disturbing at the same time : [Lightning Talk: So You Thought C++ Was Weird? Meet Enums - Roth Michaels - CppCon 2021](https://www.youtube.com/watch?v=SGfk5l85cko). In the end the number of bits needed to represent the biggest enum value matter. But values in between will be accepted even if they are not "real" enum values. – Pepijn Kramer Nov 18 '22 at 16:15
  • DON'T give the enums explicit values then the default will be the first one. The reason any value is allowed is when they have set values that's sometimes to OR them together. – QuentinUK Nov 18 '22 at 16:37

1 Answers1

0

the enum with value 0 is not much different from any other non-presented value (like 3), you can just handle it with default

switch (instance) {
    case Enum::A:
        // do something
        break; 
    case Enum::B:
        // do something
        break; 
    default:
        /* do something with non-presented value*/;
}

also note when used as flag, it's pretty common not all value combination have a name.

switch (instance) {
    case Enum(0):
        // all flag unset
        break;
    case Enum::A:
        // do something when A
        break; 
    case Enum::B:
        // do something when B
        break; 
    case Enum::A | Enum::B : // assume operator | exist
        // do something when AB
        break;
    default:
        /* do something with non-presented value*/;
}
apple apple
  • 10,292
  • 2
  • 16
  • 36