'When is the compiler allowed to optimize away a validity check of an enum or enum class type value in C++?
While searching for an answer to the question above, I came across the answer of Luke Kowald to the question Check if a value is defined in an C enum?. It states that one can check if a value is a valid for an enum, by checking if it is equal to one of the possible values in a switch.
typedef enum {
MODE_A,
MODE_B,
MODE_C
}MODE;
int modeValid(int mode)
{
int valid = 0;
switch(mode) {
case MODE_A:
case MODE_B:
case MODE_C:
valid = 1;
};
return valid;
}
Suppose, the value int mode
has been converted to type MODE
prior to checking if it is valid:
int modeValid(MODE mode)
{
int valid = 0;
switch(mode) {
case MODE_A:
case MODE_B:
case MODE_C:
valid = 1;
};
return valid;
}
Would this still be guaranteed to work or could the compiler optimize the check into always true as the enum should never have a value other than the ones checked for?
How would this behave in case of enum classes?
Solution 1:[1]
Would this still be guaranteed to work
Yes.
No rule disallows calling the function like this:
modeValid(static_cast<MODE>(42));
And because that won't match any of the case labels, the behaviour must be that 0 is returned.
could the compiler optimize the check into always true as the enum should never have a value other than the ones checked for?
The as-if rule applies. If the compiler can prove that observed behaviour is as specified, then yes. This can be the case for example when the function call is expanded inline with a compile time constant argument.
How would this behave in case of enum classes?
Same.
Standard rules (from latest C++ draft):
The values of an enum are the values of the underlying type:
[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 underlying type is an integral type:
[dcl.enum]
Each enumeration defines a type that is different from all other types. Each enumeration also has an underlying type. The underlying type can be explicitly specified using an enum-base. For a scoped enumeration type, the underlying type is int if it is not explicitly specified. In both of these cases, the underlying type is said to be fixed. ...
Solution 2:[2]
While enum has named values, it can also hold all other values of the underlying type. Same applies to enum class
. This is perfectly legal:
enum Color { red = 0xFF0000, green = 0x00FF00, blue = 0x0000FF };
int main() {
Color buttonColor = static_cast<Color>(red | green); // yellow
}
Solution 3:[3]
C++ 2018 draft N4659 10.2 (“Enumeration declarations,” [dcl.enum]) 8 tells us what the values of an enumeration type are. I will quote it below, but it essentially says:
- If an enumeration has an underlying type that is fixed, the values of the enumeration are the values of the underlying type. So there can be (integer) values in that type that are not values of the enumerators (the named values).
- If the underlying type is not fixed, the values of the enumeration are essentially those of a bit-field with enough bits to represent the named values. Again, this may result in there being more (integer) values in the enumeration type than there are named enumerators.
The paragraph explicitly states “It is possible to define an enumeration that has values not defined by any of its enumerators.”
Therefore, unless the named enumerators fill the range of the enumeration type, it is possible for your mode
object of type MODE
to have a value that is not one of the named enumerator values of the MODE
type. Converting a value to the MODE
type will not ensure the result has the value of one of the named enumerators.
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 emin is the smallest enumerator and emax is the largest, the values of the enumeration are the values in the range bmin to bmax, defined as follows: Let K be 1 for a two’s complement representation and 0 for a ones’ complement or sign-magnitude representation. bmax is the smallest value greater than or equal to max(|emin| ? K, |emax|) and equal to 2M ? 1, where M is a non-negative integer. bmin is zero if emin is non-negative and ?(bmax + K) otherwise. The size of the smallest bit-field large enough to hold all the values of the enumeration type is max(M, 1) if bmin is zero and M + 1 otherwise. It is possible to define an enumeration that has values not defined by any of its enumerators. If the enumerator-list is empty, the values of the enumeration are as if the enumeration had a single enumerator with value 0.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | Ayxan Haqverdili |
Solution 3 |