¿Qué sucede si usted static_cast valor no válido a la clase enum?

146

Considere este código C ++ 11:

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

Suponga que los datos [0] son ​​en realidad 100. ¿A qué se ajusta el color según el estándar? En particular, si luego lo hago

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

¿El estándar garantiza que se alcanzará el incumplimiento? Si no es así, ¿cuál es la forma correcta, más eficiente y más elegante de verificar un error aquí?

EDITAR:

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

darth happyface
fuente

Respuestas:

131

¿A qué se ajusta el color según 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 puede convertirse explícitamente en 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).

Busquemos 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 data[0] == 100, el valor resultante se especifica (*), y no está involucrado ningún comportamiento indefinido (UB) . De manera más general, a medida que se pasa del tipo subyacente al tipo de enumeración, ningún valor data[0]puede conducir a UB para el static_cast.

Después de CWG 1766 (C ++ 17) Ver defecto de CWG 1766 . El párrafo [expr.static.cast] p10 se ha fortalecido, por lo que ahora puede invocar UB si lanza un valor que está fuera del rango representable de una enumeración al tipo de enumeración. Esto todavía no se aplica al escenario en la pregunta, ya que data[0]es 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 del compilador apliquen a sus modos de compilación C ++ 11 y C ++ 14.

(*) charse requiere que tenga al menos 8 bits de ancho, pero no se requiere que sea unsigned. Se requiere que el valor máximo almacenable sea al menos 127segú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 es indefinido.

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 fuera afirmativa , no habría ninguna diferencia en las garantías que obtiene para operaciones en tipos con signo 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)haría no invoke UB; pero después de GTC 1766, se hace invocación UB.


Ahora, la switchdeclaración:

[interruptor stmt] / 2

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

[conv.prom] / 4

A prvalue de un sin ámbito tipo de enumeración cuyo subyacente tipo se fija (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 de un tipo de enumeración sin ámbito cuyo tipo subyacente es fijo también se puede convertir en un valor del tipo subyacente promocionado.

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

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

A prvalue de un tipo entero distinto de bool, char16_t, char32_t, o wchar_tcuyo número entero de conversión de rango (4,13) es menor que el rango de intse puede convertir en un prvalue de tipo intsi intpuede representar todos los valores del tipo de fuente; de lo contrario, el valor de origen puede convertirse en un valor de tipo unsigned int.

En el caso de un sin ámbito enumeración, que se trataría de ints aquí. Para las enumeraciones de ámbito ( enum classy enum struct), no se aplica ninguna promoción integral. De cualquier manera, la promoción integral tampoco conduce a UB, ya que el valor almacenado está en el rango del tipo subyacente y en el rango de int.

[interruptor stmt] / 5

Cuando switchse ejecuta la instrucción, 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 caseetiqueta coincidente . Si ninguna caseconstante coincide con la condición, y si hay una defaultetiqueta, el control pasa a la declaración etiquetada por la defaultetiqueta.

La defaultetiqueta debe ser golpeada.

Nota: Se podría echar otro vistazo al operador de comparación, pero no se usa explícitamente en la "comparación" mencionada. De hecho, no hay indicios de que introduciría UB para enumeraciones con o sin ámbito en nuestro caso.


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

Si el enumalcance es o no no hace ninguna diferencia aquí. Sin embargo, hace una diferencia si el tipo subyacente es fijo o no. El [decl.enum] / 7 completo 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 más pequeño empadronador y e max es el más grande, los valores de la enumeración son los valores en el intervalo b min a b max , definido como sigue: Sea Ksea 1para la representación de un complemento a dos y 0para una complemento de uno o representación de magnitud de signo. b max es el valor más pequeño mayor o igual que max (| e min | - K, | e max |) e igual a 2M - 1 , dondeMes un entero no negativo. b min es cero si e min no es 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 de ámbito, ya que todas las enumeraciones de ámbito tienen tipos subyacentes fijos.

Afortunadamente, ColorUnfixedel enumerador más pequeño es red = 0x1, entonces 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 Mes 3( 2 2 - 1 ). (Creo que la intención es permitir que el rango se extienda en pasos de 1 bit). Se deduce que b max es 3y bmin es 0.

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

dyp
fuente
3
El tipo subyacente es fijo, por lo que el rango de los valores de enumeración (§7.2 [dcl.enum] p7) es "los valores del tipo subyacente". 100 es ciertamente un valor de char, entonces "El valor no cambia si el valor original está dentro del rango de los valores de enumeración (7.2)". aplica.
Casey
2
Tuve que buscar para ver qué significaba "UB". ('comportamiento indefinido') La pregunta no mencionó la posibilidad de un comportamiento indefinido; así que no se me ocurrió que podrías estar hablando de eso.
karadoc
2
@karadoc He agregado un enlace en la primera aparición del término.
dyp
1
Amo esta respuesta. Para aquellos que descreman demasiado rápido, tenga en cuenta que la última oración "Por lo tanto, 100 estaría fuera del rango ..." solo se aplica si el código se modificó para eliminar la especificación de tipo subyacente (char en este caso). Creo que a eso se refería, de todos modos.
Eric Seppanen
1
@Ruslan CWG 1766 (o la resolución del mismo) no es parte de C ++ 14, pero creo que será parte de C ++ 17. Incluso con las reglas de C ++ 17, no entiendo lo que quieres decir con "invalidar más texto de tu respuesta". Las otras partes de mi respuesta se refieren principalmente a que el "rango de valores de enumeración" es al que se refiere expr.static.cast p10.
dyp