Os enums C ++ são assinados ou não?

107

Os enums C ++ são assinados ou não? E, por extensão, é seguro validar uma entrada verificando se ela é <= seu valor máximo e deixar de fora> = seu valor mínimo (assumindo que você começou em 0 e foi incrementado em 1)?

Matt
fonte
Quando estamos usando um tipo de enum em um contexto que requer o sinal dele, na verdade estamos falando sobre a conversão de enum em um tipo integral implicitamente. O padrão C ++ 03 diz que isso é feito pela Promoção Integral, nada relacionado ao tipo subjacente do enum. Então, eu não entendo porque todas as respostas aqui mencionam que o tipo subjacente não é definido pelo padrão? Descrevi o comportamento esperado aqui: stackoverflow.com/questions/24802322/…
JavaMan

Respostas:

60

Você não deve confiar em nenhuma representação específica. Leia o seguinte link . Além disso, o padrão diz que é definido pela implementação qual tipo integral é usado como o tipo subjacente para um enum, exceto que não deve ser maior do que int, a menos que algum valor não possa caber em int ou em um int sem sinal.

Resumindo: você não pode confiar que um enum seja assinado ou não.

zvrba
fonte
28
A resposta de Michael Burr (que cita o padrão) na verdade implica que você pode confiar que ele será assinado se definir um valor enum como negativo devido ao tipo ser capaz de "representar todos os valores do enumerador definidos na enumeração".
Samuel Harmer
101

Vamos à fonte. Aqui está o que o documento padrão C ++ 03 (ISO / IEC 14882: 2003) diz em 7.2-5 (declarações de enumeração):

O tipo subjacente de uma enumeração é um tipo integral que pode representar todos os valores do enumerador definidos na enumeração. É definido pela implementação qual tipo integral é usado como o tipo subjacente para uma enumeração, exceto que o tipo subjacente não deve ser maior que int, a menos que o valor de um enumerador não possa caber em um int ou int não assinado.

Resumindo, seu compilador pode escolher (obviamente, se você tiver números negativos para alguns de seus valores de enumeração, ele será assinado).

Michael Burr
fonte
Como podemos evitar suposições do compilador e dizer a ele para usar um tipo subjacente sem sinal quando todos os valores de enumeração são inteiros pequenos e positivos? (Estamos capturando um achado UBsan porque o compilador está escolhendo um int, e os int's sofrem estouro. O valor não tem sinal e é positivo, e nosso uso depende de uma quebra de sinal sem sinal para fornecer um decremento ou "passo negativo").
jww
@jww - isso vai depender de qual compilador exatamente você está usando, eu acho. Uma vez que o padrão não dita o tipo subjacente e deixa isso para a implementação, é necessário examinar a documentação da ferramenta e ver se essa opção é possível. Se você deseja garantir um determinado comportamento em seu código, por que não converter o membro enum que você usa na expressão?
ysap
22

Você não deve depender deles serem assinados ou não. Se quiser torná-los explicitamente assinados ou não, você pode usar o seguinte:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum
Adam Rosenfield
fonte
11
Apenas no futuro padrão C ++ 0x.
dalle
3
O compilador @dalle Microsoft também permite enums digitados msdn.microsoft.com/en-us/library/2dzy4k6e(v=vs.80).aspx
teodozjan
15

Você não deve confiar que ele seja assinado ou não. De acordo com o padrão, é definido pela implementação qual tipo integral é usado como o tipo subjacente para um enum. Na maioria das implementações, porém, é um número inteiro assinado.

Em C ++ 0x, enumerações fortemente tipadas serão adicionadas, o que permitirá que você especifique o tipo de enum, como:

enum X : signed int { ... };    // signed enum
enum Y : unsigned int { ... };  // unsigned enum

Mesmo agora, porém, alguma validação simples pode ser alcançada usando o enum como uma variável ou 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.
Matt
fonte
Acho que seu segundo exemplo é um pouco confuso :)
Miral
5

O compilador pode decidir se os enums são assinados ou não.

Outro método de validação de enums é usar o próprio enum como um tipo de variável. Por exemplo:

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.
Cristián Romo
fonte
5

Mesmo algumas respostas antigas tiveram 44 votos positivos, eu tendo a discordar de todas elas. Em suma, não acho que devemos nos preocupar com o underlying typeda enum.

Em primeiro lugar, o tipo Enum do C ++ 03 é um tipo distinto, sem conceito de sinal. Desde o padrão C ++ 03dcl.enum

7.2 Enumeration declarations 
5 Each enumeration defines a type that is different from all other types....

Portanto, quando falamos sobre o sinal de um tipo enum, digamos, ao comparar 2 operandos enum usando o <operador, estamos na verdade falando sobre a conversão implícita do tipo enum em algum tipo integral. É o sinal desse tipo integral que importa . E ao converter enum em tipo integral, esta declaração se aplica:

9 The value of an enumerator or an object of an enumeration type is converted to an integer by integral promotion (4.5).

E, aparentemente, o tipo subjacente do enum não tem nada a ver com a Promoção Integral. Já que o padrão define Promoção Integral assim:

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.

Portanto, se um tipo de enum se torna signed intou unsigned intdepende de se signed intpode conter todos os valores dos enumeradores definidos, não o tipo subjacente do enum.

Veja minha pergunta relacionada Sinal do tipo Enum C ++ incorreto após a conversão para o tipo integral

JavaMan
fonte
É importante quando você está compilando com -Wsign-conversion. Nós o usamos para ajudar a detectar erros não intencionais em nosso código. Mas +1 por citar o padrão e apontar que um enum não tem nenhum tipo ( signedversus unsigned) associado a ele.
jww
4

No futuro, com C ++ 0x, enumerações fortemente tipadas estarão disponíveis e terão várias vantagens (como segurança de tipo, tipos subjacentes explícitos ou escopo explícito). Com isso você poderá ter mais certeza do sinal do tipo.

Kris Kumler
fonte
4

Além do que outros já disseram sobre assinado / não assinado, aqui está o que o padrão diz sobre o intervalo de um tipo enumerado:

7.2 (6): "Para uma enumeração onde e (min) é o menor enumerador e e (max) é o maior, os valores da enumeração são os valores do tipo subjacente no intervalo b (min) a b (max ), onde b (min) eb (max) são, respectivamente, os menores e maiores valores do menor campo de bits que pode armazenar e (min) ee (max). É possível definir uma enumeração que possui valores não definidos por qualquer um de seus enumeradores. "

Então, por exemplo:

enum { A = 1, B = 4};

define um tipo enumerado onde e (min) é 1 e e (max) é 4. Se o tipo subjacente é int assinado, então o menor campo de bits necessário tem 4 bits, e se os ints em sua implementação são complemento de dois, então o intervalo válido de o enum é de -8 a 7. Se o tipo subjacente não tem sinal, então ele tem 3 bits e o intervalo é de 0 a 7. Verifique a documentação do compilador se você se importar (por exemplo, se você deseja converter valores integrais diferentes de enumeradores para o tipo enumerado, então você precisa saber se o valor está no intervalo da enumeração ou não - se não, o valor de enum resultante não é especificado).

Se esses valores são uma entrada válida para sua função pode ser uma questão diferente de serem valores válidos do tipo enumerado. Seu código de verificação provavelmente está preocupado com o primeiro e não com o último e, portanto, neste exemplo, deve pelo menos verificar> = A e <= B.

Steve Jessop
fonte
0

Marque com std::is_signed<std::underlying_type+ enums com escopo padrão 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 upstream .

Compile e execute:

g++ -std=c++17 -Wall -Wextra -pedantic-errors -o main main.cpp
./main

Resultado:

0

Testado no Ubuntu 16.04, GCC 6.4.0.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fonte