¿Qué sucede si has static_cast value inválido a enum class?

Considere este código C ++ 11:

enum class Color : char { red = 0x1, yellow = 0x2 } // ... char *data = ReadFile(); Color color = static_cast(data[0]); 

Supongamos que los datos [0] son ​​en realidad 100. ¿Qué color se establece de acuerdo con el estándar? En particular, si luego hago

 switch (color) { // ... red and yellow cases omitted default: // handle error break; } 

¿El estándar garantiza que se golpeará el valor predeterminado? Si no, ¿cuál es la forma más adecuada, más eficiente y más elegante de verificar si hay algún error aquí?

EDITAR:

Como beneficio adicional, ¿el estándar ofrece alguna garantía sobre esto pero con simple enumeración?

¿Qué color se establece de acuerdo con el estándar?

Respondiendo con una cita de los estándares C ++ 11 y C ++ 14:

[expr.static.cast] / 10

Un valor de tipo integral o de enumeración se puede convertir explícitamente a un tipo de enumeración. El valor no cambia si el valor original está dentro del rango de los valores de enumeración (7.2). De lo contrario, el valor resultante no está especificado (y podría no estar en ese rango).

Veamos el rango de los valores de enumeración : [dcl.enum] / 7

Para una enumeración cuyo tipo subyacente es fijo, los valores de la enumeración son los valores del tipo subyacente.

Antes de CWG 1766 (C ++ 11, C ++ 14) Por lo tanto, para los data[0] == 100 , el valor resultante se especifica (*), y no está involucrado ningún comportamiento indefinido (UB) . De manera más general, cuando transfiere del tipo subyacente al tipo de enumeración, ningún valor en los data[0] puede conducir a UB para static_cast .

Después de CWG 1766 (C ++ 17) Vea el defecto CWG 1766 . El párrafo [expr.static.cast] p10 se ha fortalecido, por lo que ahora puede invocar UB si arroja un valor que está fuera del rango representable de una enumeración al tipo enum. Esto todavía no se aplica al escenario en la pregunta, ya que los data[0] son del tipo subyacente de la enumeración (ver arriba).

Tenga en cuenta que CWG 1766 se considera un defecto en el estándar, por lo tanto, se acepta que los implementadores de comstackdores se apliquen a sus modos de comstackción C ++ 11 y C ++ 14.

(*) char debe tener al menos 8 bits de ancho, pero no es necesario que esté unsigned . El valor máximo almacenable debe ser de al menos 127 según el Anexo E de la Norma C99.


Comparar con [expr] / 4

Si durante la evaluación de una expresión, el resultado no está matemáticamente definido o no está en el rango de valores representables para su tipo, el comportamiento no está definido.

Antes de CWG 1766, el tipo integral de conversión -> tipo de enumeración puede producir un valor no especificado . La pregunta es: ¿puede un valor no especificado estar fuera de los valores representables para su tipo? Creo que la respuesta es no ; si la respuesta fue afirmativa , no habría ninguna diferencia en las garantías que obtienes para las operaciones en tipos firmados entre “esta operación produce un valor no especificado” y “esta operación tiene un comportamiento indefinido”.

Por lo tanto, antes de CWG 1766, incluso static_cast(10000) no invocaría a UB; pero después de CWG 1766, invoca a UB.


Ahora, la statement de switch :

[stmt.switch] / 2

La condición debe ser de tipo integral, tipo de enumeración o tipo de clase. […] Se realizan promociones integrales.

[conv.prom] / 4

Un prvalue de un tipo de enumeración sin ámbito cuyo tipo subyacente es fijo (7.2) se puede convertir a un prvalue de su tipo subyacente. Además, si la promoción integral se puede aplicar a su tipo subyacente, un valor pr de un tipo de enumeración sin ámbito cuyo tipo subyacente sea fijo también se puede convertir a un valor prvero del tipo subyacente promovido.

Nota: El tipo subyacente de una enumeración con ámbito sin base enum es int . Para las enumeraciones sin ámbito, el tipo subyacente está definido por la implementación, pero no debe ser mayor que int si int puede contener los valores de todos los enumeradores.

Para una enumeración sin ámbito , esto nos lleva a / 1

Un prvalue de un tipo entero distinto de bool , char16_t , char32_t o wchar_t cuyo rango de conversión entera (4.13) es menor que el rango de int se puede convertir a un prvalue de tipo int si int puede representar todos los valores del tipo de fuente ; de lo contrario, el prvalue fuente se puede convertir a un valor prvalue de tipo unsigned int .

En el caso de una enumeración no codificada , estaríamos tratando con int s aquí. Para enumeraciones con ámbito ( enum class y enum struct ), no se aplica promoción integral. De cualquier modo, la promoción integral tampoco conduce a UB, ya que el valor almacenado se encuentra en el rango del tipo subyacente y en el rango de int .

[stmt.switch] / 5

Cuando se ejecuta la instrucción switch , su condición se evalúa y se compara con cada constante de caso. Si una de las constantes de caso es igual al valor de la condición, el control se pasa a la instrucción que sigue a la etiqueta de la case coincidente. Si ninguna constante de case coincide con la condición, y si hay una etiqueta default , el control pasa a la statement etiquetada por la etiqueta default .

La etiqueta default debe ser golpeada.

Nota: Se podría echar otro vistazo al operador de comparación, pero no se usa explícitamente en la referida “comparación”. De hecho, no hay ninguna pista de que introduciría UB para enumeraciones con o sin cobertura en nuestro caso.


Como beneficio adicional, ¿el estándar ofrece alguna garantía sobre esto pero con simple enumeración?

Si la enum tiene o no un scope no hace ninguna diferencia aquí. Sin embargo, hace una diferencia si el tipo subyacente es fijo o no. El [decl.enum] completo / 7 es:

Para una enumeración cuyo tipo subyacente es fijo, los valores de la enumeración son los valores del tipo subyacente. De lo contrario, para una enumeración donde e min es el enumerador más pequeño y e max es el más grande, los valores de la enumeración son los valores en el rango b min a b max , definidos de la siguiente manera: Sea K sea 1 para una representación complementaria de dos y 0 para un complemento de uno o representación de magnitud de signo. b max es el valor más pequeño mayor que o igual a max (| e min | – K , | e max |) e igual a 2 M – 1 , donde M es un entero no negativo. b min es cero si e min es no negativo y – (b max + K ) de lo contrario.

Echemos un vistazo a la siguiente enumeración:

 enum ColorUnfixed /* no fixed underlying type */ { red = 0x1, yellow = 0x2 } 

Tenga en cuenta que no podemos definir esto como una enumeración con ámbito, ya que todas las enumeraciones con ámbito tienen tipos subyacentes corregidos.

Afortunadamente, el enumerador más pequeño de ColorUnfixed es red = 0x1 , por lo que max (| e min | – K , | e max |) es igual a | e max | en cualquier caso, que es yellow = 0x2 . El valor más pequeño mayor o igual a 2 , que es igual a 2 M – 1 para un entero positivo M es 3 ( 2 2 – 1 ). (Creo que la intención es permitir que el rango se amplíe en pasos de 1 bit.) De esto se deduce que b max es 3 y bmin es 0 .

Por lo tanto, 100 estaría fuera del rango de ColorUnfixed , y static_cast produciría un valor no especificado antes de CWG 1766 y un comportamiento indefinido después de CWG 1766.