"control reaches end of non-void function" with fully handled case switch over an enum type
In general, enum
s 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.
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.