dev - c++17
¿Qué sucede si has static_cast value inválido a enum class? (1)
¿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 compiladores se apliquen a sus modos de compilació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<Color>(10000)
no invocaría a UB; pero después de CWG 1766, invoca a UB.
Ahora, la declaración 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
owchar_t
cuyo rango de conversión entera (4.13) es menor que el rango deint
se puede convertir a un prvalue de tipoint
siint
puede representar todos los valores del tipo de fuente ; de lo contrario, el prvalue fuente se puede convertir a un valor prvalue de tipounsigned 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 lacase
coincidente. Si ninguna constante decase
coincide con la condición, y si hay una etiquetadefault
, el control pasa a la declaración etiquetada por la etiquetadefault
.
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 alcance 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
sea1
para una representación complementaria de dos y0
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 , dondeM
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.
Considere este código C ++ 11:
enum class Color : char { red = 0x1, yellow = 0x2 }
// ...
char *data = ReadFile();
Color color = static_cast<Color>(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?