¿Las enumeraciones de C ++ están firmadas o sin firmar?
107
¿Las enumeraciones de C ++ están firmadas o sin firmar? Y, por extensión, ¿es seguro validar una entrada comprobando que sea <= su valor máximo y omitir> = su valor mínimo (asumiendo que comenzó en 0 y se incrementó en 1)?
Cuando usamos un tipo enum en un contexto que requiere su signo, en realidad estamos hablando de convertir implícitamente el tipo enum en un tipo integral. El estándar C ++ 03 dice que esto se realiza mediante Promoción Integral, nada relacionado con el tipo subyacente de la enumeración. Entonces, no entiendo por qué todas las respuestas aquí mencionan que el tipo subyacente no está definido por el estándar. Describí el comportamiento esperado aquí: stackoverflow.com/questions/24802322/…
JavaMan
Respuestas:
60
No debe confiar en ninguna representación específica. Lea el siguiente enlace . Además, el estándar dice que está definido por la implementación qué tipo integral se usa como tipo subyacente para una enumeración, excepto que no debe ser mayor que int, a menos que algún valor no pueda caber en int o unsigned int.
En resumen: no puede confiar en que una enumeración esté firmada o sin firmar.
La respuesta de Michael Burr (que cita el estándar) en realidad implica que puede confiar en que esté firmado si define un valor de enumeración como negativo debido a que el tipo puede "representar todos los valores de enumerador definidos en la enumeración".
Samuel Harmer
101
Vayamos a la fuente. Esto es lo que dice el documento del estándar C ++ 03 (ISO / IEC 14882: 2003) en 7.2-5 (Declaraciones de enumeración):
El tipo subyacente de una enumeración es un tipo integral que puede representar todos los valores del enumerador definidos en la enumeración. Está definido por la implementación qué tipo integral se usa como tipo subyacente para una enumeración, excepto que el tipo subyacente no debe ser mayor que int a menos que el valor de un enumerador no pueda caber en un int o unsigned int.
En resumen, su compilador puede elegir (obviamente, si tiene números negativos para algunos de sus valores de enumeración, se firmará).
¿Cómo podemos evitar las conjeturas del compilador y decirle que use un tipo sin signo subyacente cuando todos los valores de enumeración son números enteros pequeños y positivos? (Estamos detectando un hallazgo de UBsan porque el compilador está seleccionando un int, y los int sufren un desbordamiento. El valor es unsigned y positivo, y nuestro uso depende de unsigned wrap para proporcionar una disminución o "zancada negativa").
jww
@jww: eso dependerá de qué compilador esté usando exactamente, supongo. Dado que el estándar no dicta el tipo subyacente, y deja esto a la implementación, entonces necesita mirar la documentación de su herramienta y ver si esta opción es posible. Si desea garantizar un cierto comportamiento en su código, ¿por qué no lanzar el miembro enum que usa en la expresión?
ysap
22
No debe depender de que estén firmados o sin firmar. Si desea que estén firmados o sin firmar explícitamente, puede utilizar lo siguiente:
enum X :signedint{...};// signed enumenum Y :unsignedint{...};// unsigned enum
No debe confiar en que esté firmado o sin firmar. De acuerdo con el estándar, está definido por la implementación qué tipo integral se usa como tipo subyacente para una enumeración. En la mayoría de las implementaciones, sin embargo, es un entero con signo.
enum X :signedint{...};// signed enumenum Y :unsignedint{...};// unsigned enum
Incluso ahora, sin embargo, se puede lograr una validación simple usando la enumeración como una variable o tipo de parámetro como este:
enumFruit{Apple,Banana};enumFruit fruitVariable =Banana;// Okay, Banana is a member of the Fruit enum
fruitVariable =1;// Error, 1 is not a member of enum Fruit// even though it has the same value as banana.
El compilador puede decidir si las enumeraciones están firmadas o no.
Otro método para validar las enumeraciones es usar la enumeración en sí como un tipo de variable. Por ejemplo:
enumFruit{Apple=0,Banana,Pineapple,Orange,Kumquat};enumFruit fruitVariable =Banana;// Okay, Banana is a member of the Fruit enum
fruitVariable =1;// Error, 1 is not a member of enum Fruit even though it has the same value as banana.
Incluso algunas respuestas antiguas obtuvieron 44 votos a favor, tiendo a no estar de acuerdo con todas ellas. En resumen, no creo que debamos preocuparnos por elunderlying type la enumeración.
En primer lugar, el tipo Enum de C ++ 03 es un tipo distinto en sí mismo que no tiene concepto de signo. Desde el estándar C ++ 03dcl.enum
7.2Enumeration declarations
5Each enumeration defines a type that is different from all other types....
Entonces, cuando hablamos del signo de un tipo enum, digamos que al comparar 2 operandos enum usando el <operador, en realidad estamos hablando de convertir implícitamente el tipo enum a algún tipo integral. Es el signo de este tipo integral lo que importa . Y al convertir enum a tipo integral, se aplica esta declaración:
9The value of an enumerator or an object of an enumeration type is converted to an integer by integral promotion (4.5).
Y, aparentemente, el tipo subyacente de la enumeración no tiene nada que ver con la Promoción Integral. Dado que el estándar define Promoción Integral así:
4.5Integral promotions conv.prom
..An rvalue of an enumeration type (7.2) can be converted to an rvalue of the first of the following types that can represent all the values of the enumeration
(i.e. the values in the range bmin to bmax as described in 7.2:int,unsignedint,long, or unsignedlong.
Entonces, si un tipo de enumeración se convierte signed into unsigned intdepende de si signed intpuede contener todos los valores de los enumeradores definidos, no el tipo subyacente de la enumeración.
Importa cuando está compilando con -Wsign-conversion. Lo usamos para ayudar a detectar errores no deseados en nuestro código. Pero +1 por citar el estándar y señalar que una enumeración no tiene ningún tipo ( signedversus unsigned) asociado.
jww
4
En el futuro, con C ++ 0x, las enumeraciones fuertemente tipadas estarán disponibles y tendrán varias ventajas (como seguridad de tipos, tipos subyacentes explícitos o alcance explícito). Con eso, podría estar mejor seguro del signo del tipo.
Además de lo que otros ya han dicho sobre firmado / no firmado, esto es lo que dice el estándar sobre el rango de un tipo enumerado:
7.2 (6): "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 del tipo subyacente en el rango b (min) ab (max ), donde b (min) yb (max) son, respectivamente, los valores más pequeño y más grande del campo de bits más pequeño que puede almacenar e (min) y e (max). Es posible definir una enumeración que tiene valores no definidos por cualquiera de sus enumeradores ".
Así por ejemplo:
enum{ A =1, B =4};
define un tipo enumerado donde e (min) es 1 ye (max) es 4. Si el tipo subyacente está firmado como int, entonces el campo de bits requerido más pequeño tiene 4 bits, y si las entradas en su implementación son el complemento de dos, entonces el rango válido de la enumeración es de -8 a 7. Si el tipo subyacente no está firmado, entonces tiene 3 bits y el rango es de 0 a 7. Consulte la documentación del compilador si le interesa (por ejemplo, si desea convertir valores integrales distintos de los enumeradores al tipo enumerado, entonces necesita saber si el valor está en el rango de la enumeración o no, si no, el valor de enumeración resultante no está especificado).
Si esos valores son una entrada válida para su función puede ser un problema diferente de si son valores válidos del tipo enumerado. Su código de verificación probablemente esté preocupado por lo primero en lugar de lo último, por lo que en este ejemplo debería al menos verificar> = A y <= B.
Respuestas:
No debe confiar en ninguna representación específica. Lea el siguiente enlace . Además, el estándar dice que está definido por la implementación qué tipo integral se usa como tipo subyacente para una enumeración, excepto que no debe ser mayor que int, a menos que algún valor no pueda caber en int o unsigned int.
En resumen: no puede confiar en que una enumeración esté firmada o sin firmar.
fuente
Vayamos a la fuente. Esto es lo que dice el documento del estándar C ++ 03 (ISO / IEC 14882: 2003) en 7.2-5 (Declaraciones de enumeración):
En resumen, su compilador puede elegir (obviamente, si tiene números negativos para algunos de sus valores de enumeración, se firmará).
fuente
No debe depender de que estén firmados o sin firmar. Si desea que estén firmados o sin firmar explícitamente, puede utilizar lo siguiente:
fuente
No debe confiar en que esté firmado o sin firmar. De acuerdo con el estándar, está definido por la implementación qué tipo integral se usa como tipo subyacente para una enumeración. En la mayoría de las implementaciones, sin embargo, es un entero con signo.
En C ++ 0x se agregarán enumeraciones fuertemente tipadas que le permitirán especificar el tipo de una enumeración como:
Incluso ahora, sin embargo, se puede lograr una validación simple usando la enumeración como una variable o tipo de parámetro como este:
fuente
El compilador puede decidir si las enumeraciones están firmadas o no.
Otro método para validar las enumeraciones es usar la enumeración en sí como un tipo de variable. Por ejemplo:
fuente
Incluso algunas respuestas antiguas obtuvieron 44 votos a favor, tiendo a no estar de acuerdo con todas ellas. En resumen, no creo que debamos preocuparnos por el
underlying type
la enumeración.En primer lugar, el tipo Enum de C ++ 03 es un tipo distinto en sí mismo que no tiene concepto de signo. Desde el estándar C ++ 03
dcl.enum
Entonces, cuando hablamos del signo de un tipo enum, digamos que al comparar 2 operandos enum usando el
<
operador, en realidad estamos hablando de convertir implícitamente el tipo enum a algún tipo integral. Es el signo de este tipo integral lo que importa . Y al convertir enum a tipo integral, se aplica esta declaración:Y, aparentemente, el tipo subyacente de la enumeración no tiene nada que ver con la Promoción Integral. Dado que el estándar define Promoción Integral así:
Entonces, si un tipo de enumeración se convierte
signed int
ounsigned int
depende de sisigned int
puede contener todos los valores de los enumeradores definidos, no el tipo subyacente de la enumeración.Vea mi pregunta relacionada Signo de tipo de enumeración de C ++ incorrecto después de convertir a tipo integral
fuente
-Wsign-conversion
. Lo usamos para ayudar a detectar errores no deseados en nuestro código. Pero +1 por citar el estándar y señalar que una enumeración no tiene ningún tipo (signed
versusunsigned
) asociado.En el futuro, con C ++ 0x, las enumeraciones fuertemente tipadas estarán disponibles y tendrán varias ventajas (como seguridad de tipos, tipos subyacentes explícitos o alcance explícito). Con eso, podría estar mejor seguro del signo del tipo.
fuente
Además de lo que otros ya han dicho sobre firmado / no firmado, esto es lo que dice el estándar sobre el rango de un tipo enumerado:
7.2 (6): "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 del tipo subyacente en el rango b (min) ab (max ), donde b (min) yb (max) son, respectivamente, los valores más pequeño y más grande del campo de bits más pequeño que puede almacenar e (min) y e (max). Es posible definir una enumeración que tiene valores no definidos por cualquiera de sus enumeradores ".
Así por ejemplo:
define un tipo enumerado donde e (min) es 1 ye (max) es 4. Si el tipo subyacente está firmado como int, entonces el campo de bits requerido más pequeño tiene 4 bits, y si las entradas en su implementación son el complemento de dos, entonces el rango válido de la enumeración es de -8 a 7. Si el tipo subyacente no está firmado, entonces tiene 3 bits y el rango es de 0 a 7. Consulte la documentación del compilador si le interesa (por ejemplo, si desea convertir valores integrales distintos de los enumeradores al tipo enumerado, entonces necesita saber si el valor está en el rango de la enumeración o no, si no, el valor de enumeración resultante no está especificado).
Si esos valores son una entrada válida para su función puede ser un problema diferente de si son valores válidos del tipo enumerado. Su código de verificación probablemente esté preocupado por lo primero en lugar de lo último, por lo que en este ejemplo debería al menos verificar> = A y <= B.
fuente
Compruébelo con
std::is_signed<std::underlying_type
+ enumeraciones con ámbito predeterminado paraint
https://en.cppreference.com/w/cpp/language/enum implica:
main.cpp
GitHub aguas arriba .
Compilar y ejecutar:
Salida:
Probado en Ubuntu 16.04, GCC 6.4.0.
fuente