25

Why does this code trigger a "control reaches end of non-void function" even if all possible values of type_t are handled? What is the best way to take care of this warning? Adding a return -1 after the switch?
(Code tested here)

typedef enum {
    A,
    B
} type_t;

int useType(type_t x) {
    switch (x) {
        case A:
            return 0;
        case B:
            return 1;
    }
}


Related: Detecting if casting an int to an enum results into a non-enumerated value
Community
  • 1
  • 1
Antonio
  • 19,451
  • 13
  • 99
  • 197

2 Answers2

15

In general, enums are not exclusive. Someone could call your function like useType( (type_t)3 ); for example. This is mentioned specifically in C++14 [dcl.enum]/8:

It is possible to define an enumeration that has values not defined by any of its enumerators.

Now, there are a bunch of rules about exactly which other values are possible for which other sorts of enum.

There are two categories of enum. The first is fixed underlying type, e.g. enum type_t : int, or enum class type_t . In those cases all values of the underlying type are valid enumerators.

The second is not fixed underlying type, which includes pre-C++11 enums such as yours. In this case the rule about values can be summarized by saying: compute the smallest number of bits necessary in order to store all values of the enum; then any number expressible in that number of bits is a valid value.


So - in your specific case, a single bit can hold both values A and B, so 3 is not valid value for the enumerator.

But if your enum were A,B,C, then even though 3 is not listed specifically, it is a valid value by the above rule. (So we can see that almost all enums will not be exclusive).

Now we need to look at the rule for what happens if someone does actually try to convert 3 to type_t. The conversion rule is C++14 [expr.static.cast]/10, which says that an unspecified value is produced.

However, CWG issue 1766 recognized that the C++14 text was defective and has replaced it with the following:

A value of integral or enumeration type can be explicitly converted to a complete enumeration type. The value is unchanged if the original value is within the range of the enumeration values (7.2). Otherwise, the behavior is undefined.

Therefore, in your specific case of exactly two enumerators with value 0 and 1, no other value is possible unless the program has already triggered undefined behaviour, so the warning could be considered a false positive.


To remove the warning, add a default: case that does something. I'd also suggest, in the interest of defensive programming, that it's a good idea to have a default case anyway. In practice it may serve to 'contain' the undefined behaviour: if someone does happen to pass an invalid value then you can throw or abort cleanly.


NB: Regarding the warning itself: it's impossible for a compiler to accurately warn if and only if control flow would reach the end of a function , because this would require solving the halting problem.

They tend to err on the side of caution: the compiler will warn if it is not completely sure, meaning that there are false positives.

So the presence of this warning does not necessarily indicate that the executable would actually allow entry into the default path.

M.M
  • 138,810
  • 21
  • 208
  • 365
  • Were they designed that way or was this just a mistake? This could have been UB (well, not now, backwards compatibility...) – Karoly Horvath Nov 09 '15 at 10:44
  • Can an `enum` be made exclusive? – Antonio Nov 09 '15 at 10:44
  • How? :) (I think that would answer the second part of my question, "What is the best way to take care of this warning?") – Antonio Nov 09 '15 at 10:48
  • 1
    I've started a language lawyer [question](http://stackoverflow.com/questions/33607809/can-an-out-of-range-enumator-conversion-produce-a-value-not-representible-in-the) to try and clear up that last quote – M.M Nov 09 '15 at 11:09
  • @M.M But then what is the best/most elegant way to get rid of this false positive? – Antonio Nov 09 '15 at 11:24
  • 3
    As far as I can see, you answered the first of the double question: *Why does this code trigger a "control reaches end of non-void function" even if all possible values of type_t are handled?*, but you left unanswered the second one: *What is the best way to take care of this warning? Adding a return -1 after the switch?* – Johannes Schaub - litb Nov 09 '15 at 19:34
  • @Antonio updated question with latest information. Your enum *is* actually exclusive and the warning is a false positive. I've added a section suggesting a way of avoiding the warning. – M.M Nov 09 '15 at 20:02
  • 21
    Adding a `default` label makes Clang complain: `default label in switch which covers all enumeration values`. Sometimes you just can't win... – Nathan Osman Dec 27 '17 at 06:59
4

To answer the second question ("What is the best way to take care of this warning?"):

In my eyes, typically, the best method is to add a call to __builtin_unreachable() after the switch statement (available both in GCC and Clang - maybe at some point we'll get [[unreachable]]). This way, you explicitly tell the compiler that the code never runs over the switch statement. And if it does, you are happy to accept all dire consequences of undefined behaviour. Note that the most obvious reason to run over would be an enum containing a value that is not listed - which is undefined behaviour anyway as pointed out in the answer by @M.M.

This way, you get rid of the warning on current versions of both GCC and Clang without introducing new warnings. What you lose is the protection by the compiler if you miss a valid situation that does run over the switch statement. This is mitigated to some degree that both GCC and Clang do warn you if miss a switch case completely (e.g. if a value gets added to the enum), but not if one of the cases runs into a break statement.

burnpanck
  • 1,955
  • 1
  • 12
  • 36
  • _And if it does, you are happy to accept all dire consequences of undefined behaviour._ In an age where undefined behavior can literally format your hard drive (https://bugs.llvm.org/show_bug.cgi?id=49599), I think this really understates the possible consequences. Although it might seem safe in conjunction with `-Werror=switch`, in a codebase that undergoes a lot of refactoring, the `[[unreachable]]` can quickly end up far away from the original switch statement. I would therefore advocate for a controlled assertion failure instead, or, if code size is a concern, something like __debugbreak. – Martin Cejp May 09 '21 at 12:15
  • 3
    [According to cppreference](https://en.cppreference.com/w/cpp/utility/unreachable), C++23 will have a `std::unreachable` function. – adentinger Jun 23 '22 at 23:09