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)?
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.
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).
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 :signedint{...};// signed enumenum Y :unsignedint{...};// unsigned enum
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 :signedint{...};// signed enumenum Y :unsignedint{...};// 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:
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.
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:
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.
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.2Enumeration declarations 5Each 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:
9The 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.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.
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.
É 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.
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.
Respostas:
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.
fonte
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):
Resumindo, seu compilador pode escolher (obviamente, se você tiver números negativos para alguns de seus valores de enumeração, ele será assinado).
fonte
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:
fonte
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:
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:
fonte
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:
fonte
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 type
da enum.Em primeiro lugar, o tipo Enum do C ++ 03 é um tipo distinto, sem conceito de sinal. Desde o padrão C ++ 03
dcl.enum
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: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:
Portanto, se um tipo de enum se torna
signed int
ouunsigned int
depende de sesigned int
pode 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
fonte
-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 (signed
versusunsigned
) associado a ele.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.
fonte
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:
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.
fonte
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
GitHub upstream .
Compile e execute:
Resultado:
Testado no Ubuntu 16.04, GCC 6.4.0.
fonte