¿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.


28
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


15

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.

Creo que su segundo ejemplo es un poco confuso :)
Miral

5

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.

5

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 into unsigned intdepende de si signed intpuede 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


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.


4

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.


0

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>()));
}

GitHub aguas arriba .

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.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.