0

Enum classes are supposed to be strong enums in the sense that they don't implicitly convert to and from int. For instance:

enum class EC { a, b };

However, when switching over such a "strong enum":

int sw(EC ec) {
  switch (ec) {
    case EC::a: return 0;
    case EC::b: return 1;
  }
}

gcc -Wreturn-type-wants me to add a default clause to the switch even though all legal enum values are covered:

warning: control reaches end of non-void function [-Wreturn-type]

In an old (non-class) enum, this makes sense, because any int could have been accidentally converted to EC. But I had (apparently wrongly) assumed that assigning an invalid enum member to an enum class was UB.

How can I use truly strong enum classes where the compiler realises that functions like sw cover all possible paths? Of course I could just add a default: branch that I know will never be triggered, but I want to make sure that adding more members to EC in the future will trigger a warning in the switch.

bitmask
  • 32,434
  • 14
  • 99
  • 159
  • In my code, I put a _should never happen_ `throw std::logic_error("failure");` rather than a _should never happen_ return with a default or spoof value. – Eljay Apr 25 '20 at 15:07

5 Answers5

1

You can return a dummy variable, to remove the "control reaches end of non-void function". This way, the warning is removed, and any additions to the enum-class will still trigger a warning in the switch statement:

int sw(EC ec) {
  switch (ec) {
    case EC::a: return 0;
    case EC::b: return 1;
  }

  return 0; //dummy variable
}
Azam Bham
  • 1,389
  • 5
  • 6
  • In GCC you can just use `__builtin_unreachable();`. – Evg Apr 25 '20 at 15:00
  • @Evg True, but it is not standard behaviour. – Azam Bham Apr 25 '20 at 15:02
  • `throw` or non-return function such as `abort`/`exit` might be more appropriate that dummy return. – Jarod42 Apr 25 '20 at 18:34
  • @Jarod42 It *may* be more appropriate depending on the context. If the function isn't critical, or if the return value is checked, or the use of exceptions are not acceptable etc, it *may* be more suitable to return a dummy variable. Without any further information, I can't make any strong recommendations. – Azam Bham Apr 25 '20 at 18:43
1

"control reaches end of non-void function" is quite different from common "enumeration value 'c' not handled in switch [-Wswitch]" warning. I think compiler is being a bit too cautious here, but this warning may turn out to be handy because it will prevent potential future UB caused by modification of enum and ignoring of -Wswitch warning.

Rewriting this snippet like this would make code future proof:

online compiler

enum class EC { a, b /*,c */ };

int sw(EC ec) {
    int result{};
  switch (ec) { // warning: enumeration value 'c' not handled in switch [-Wswitch]
    case EC::a: result = 0; break;
    case EC::b: result = 1; break;
  }
  return result; // control flow will always leave function properly
}
user7860670
  • 35,849
  • 4
  • 58
  • 84
  • The point about `-Wswitch` and `-Wreturn-type` is valid, I missed that. However, this potentially requires moving and assigning result (which might not be a good idea for non-int types). – bitmask Apr 25 '20 at 15:57
0

A enum variable - both old-school one and enum class can hold values that are not one of the members of the enumeration. As long as the integer value fits within the underlying type (with a few more restrictions), it is valid to store it in the enumeration type.

Jesper Juhl
  • 30,449
  • 3
  • 47
  • 70
  • That rule about "as long as the integer value fits within the underlying type" only applies when underlaying type is explicitly defined. In OP's example the only valid values for `EC` are 0 and 1 even though it would have an `int` as underlaying type. – user7860670 Apr 25 '20 at 15:04
  • 1
    @user7860670 I don't think you are right about that. I'll see if I can find the relevant text in the standard. – Jesper Juhl Apr 25 '20 at 15:14
  • see 10.2 Enumeration declarations [dcl.enum] 8 For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, for an enumeration where e min is the smallest enumerator and e max is the largest, the values of the enumeration are the values in the range b min to b max , defined as follows: Let K be 1 for a two’s complement representation and 0 for a ones’ complement or sign-magnitude representation. b max is the smallest value greater than or equal to max ( |e min | − K,|e max | ) and equal to 2 M − 1, where M is a non-negative – user7860670 Apr 25 '20 at 15:15
  • @user7860670 : http://eel.is/c++draft/enum#dcl.enum "For an enumeration whose underlying type is fixed, the values of the enumeration are the values of the underlying type. Otherwise, the values of the enumeration are the values representable by a hypothetical integer type with minimal width M such that all enumerators can be represented. The width of the smallest bit-field large enough to hold all the values of the enumeration type is M. It is possible to define an enumeration that has values not defined by any of its enumerators." -since there are only 2 enumerators of value 0&1 you've right – Jesper Juhl Apr 25 '20 at 15:20
  • @user7860670 but for a larger enumeration with bigger M, it's easy to have values outside the listed enumerators. – Jesper Juhl Apr 25 '20 at 15:21
0

This worked for me:

enum class EC {a, b, c};
int sw (EC ec)
{
    int rc;
    switch (ec)
    {
        case EC::a:
        rc = 0;
        break;
    case EC::b:
        rc = 1;
        break;
    }
    return rc;
}
mzimmers
  • 857
  • 7
  • 17
0

In GCC/Clang/ICC you can silence this warning with __builtin_unreachable():

int sw(EC ec) {
    switch (ec) {
        case EC::a: return 0;
        case EC::b: return 1;
    }

    assert(false);
    __builtin_unreachable();
}

In MSVC __assume(0) can be used.

Evg
  • 25,259
  • 5
  • 41
  • 83