¿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)?
¿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)?
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.
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á).
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 : signed int { ... }; // signed enum
enum Y : unsigned int { ... }; // 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.
En C ++ 0x se agregarán enumeraciones fuertemente tipadas que le permitirán especificar el tipo de una enumeración como:
enum X : signed int { ... }; // signed enum
enum Y : unsigned int { ... }; // 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:
enum Fruit { Apple, Banana };
enum Fruit 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:
enum Fruit
{
Apple = 0,
Banana,
Pineapple,
Orange,
Kumquat
};
enum Fruit 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.2 Enumeration declarations
5 Each 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:
9 The 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.5 Integral 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, unsigned int, long, or unsigned long.
Entonces, si un tipo de enumeración se convierte signed int
o unsigned int
depende de si signed 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
-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
versus unsigned
) 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.
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.
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
#include <cassert>
#include <iostream>
#include <type_traits>
enum Unscoped {};
enum class ScopedDefault {};
enum class ScopedExplicit : long {};
int main() {
// Implementation defined, let's find out.
std::cout << std::is_signed<std::underlying_type<Unscoped>>() << std::endl;
// Guaranteed. Scoped defaults to int.
assert((std::is_same<std::underlying_type<ScopedDefault>::type, int>()));
// Guaranteed. We set it ourselves.
assert((std::is_same<std::underlying_type<ScopedExplicit>::type, long>()));
}
Compilar y ejecutar:
g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main main.cpp
./main
Salida:
0
Probado en Ubuntu 16.04, GCC 6.4.0.