O que acontece se você valor inválido de static_cast na class enum?

Considere este código C ++ 11:

enum class Color : char { red = 0x1, yellow = 0x2 } // ... char *data = ReadFile(); Color color = static_cast(data[0]); 

Suponha que os dados [0] sejam realmente 100. O que é a cor definida de acordo com o padrão? Em particular, se depois eu fizer

 switch (color) { // ... red and yellow cases omitted default: // handle error break; } 

o padrão garante que o padrão será atingido? Se não, qual é a maneira mais adequada, mais eficiente e mais elegante de verificar um erro aqui?

EDITAR:

Como bônus, o padrão faz alguma garantia sobre isso, mas com o enum simples?

O que é a cor definida de acordo com o padrão?

Respondendo com uma citação do C ++ 11 e C ++ 14 Standards:

[expr.static.cast] / 10

Um valor de tipo integral ou de enumeração pode ser explicitamente convertido em um tipo de enumeração. O valor é inalterado se o valor original estiver dentro do intervalo dos valores de enumeração (7.2). Caso contrário, o valor resultante não é especificado (e pode não estar nesse intervalo).

Vamos procurar o intervalo dos valores de enumeração : [dcl.enum] / 7

Para uma enumeração cujo tipo subjacente é fixo, os valores da enumeração são os valores do tipo subjacente.

Antes do CWG 1766 (C ++ 11, C ++ 14) Portanto, para data[0] == 100 , o valor resultante é especificado (*) e nenhum comportamento indefinido (UB) está envolvido. Mais geralmente, à medida que você passa do tipo subjacente para o tipo de enumeração, nenhum valor nos data[0] pode levar ao UB para o static_cast .

Após o CWG 1766 (C ++ 17) Consulte CWG defeito 1766 . O parágrafo [expr.static.cast] p10 foi reforçado, portanto, agora você pode chamar o UB se converter um valor que esteja fora do intervalo representável de um enum para o tipo enum. Isso ainda não se aplica ao cenário da questão, pois data[0] é do tipo subjacente da enumeração (veja acima).

Observe que o CWG 1766 é considerado um defeito no padrão, por isso é aceito que os implementadores de compilador apliquem aos seus modos de compilation C ++ 11 e C ++ 14.

(*) é necessário que o char tenha pelo menos 8 bits de largura, mas não precisa ser unsigned . O valor máximo armazenável deve ser de pelo menos 127 por Anexo E da Norma C99.


Compare com [expr] / 4

Se durante a avaliação de uma expressão, o resultado não for matematicamente definido ou não estiver na faixa de valores representáveis ​​para o seu tipo, o comportamento é indefinido.

Antes do CWG 1766, o tipo integral de conversão -> tipo de enumeração pode produzir um valor não especificado . A pergunta é: um valor não especificado pode estar fora dos valores representáveis ​​para seu tipo? Eu acredito que a resposta é não – se a resposta fosse sim , não haveria nenhuma diferença nas garantias que você obtém para operações em tipos assinados entre “esta operação produz um valor não especificado” e “esta operação tem comportamento indefinido”.

Portanto, antes do CWG 1766, mesmo static_cast(10000) não chamaria UB; mas depois do CWG 1766, ele invoca o UB.


Agora, a instrução switch :

[stmt.switch] / 2

A condição deve ser do tipo integral, tipo de enumeração ou tipo de class. Promoções integrais são realizadas.

[conv.prom] / 4

Um prvalor de um tipo de enumeração sem escopo, cujo tipo subjacente é fixo (7.2), pode ser convertido em um prvalor de seu tipo subjacente. Além disso, se a promoção integral puder ser aplicada ao seu tipo subjacente, um próvalo de um tipo de enumeração sem escopo, cujo tipo subjacente é fixo, também pode ser convertido em um prvalor do tipo subjacente promovido.

Nota: O tipo subjacente de um enum com escopo sem enum-base é int . Para enums sem escopo, o tipo subjacente é definido pela implementação, mas não deve ser maior que int se int puder conter os valores de todos os enumeradores.

Para uma enumeração sem escopo , isso nos leva a / 1

Um prvalor de um tipo inteiro diferente de bool , char16_t , char32_t ou wchar_t cuja sorting de conversão de inteiro (4.13) é menor que a sorting de int pode ser convertida em um prvalue do tipo int se int puder representar todos os valores do tipo de origem ; caso contrário, o prvalor de origem pode ser convertido em um valor prv do tipo unsigned int .

No caso de uma enumeração sem escopo , estaríamos lidando com int s aqui. Para enumerações com escopo definido ( enum class e enum struct ), nenhuma promoção integral é aplicada. De qualquer forma, a promoção integral também não leva ao UB, pois o valor armazenado está no intervalo do tipo subjacente e no intervalo de int .

[stmt.switch] / 5

Quando a instrução switch é executada, sua condição é avaliada e comparada com cada constante de caso. Se uma das constantes de caso for igual ao valor da condição, o controle será passado para a instrução após o label de case correspondente. Se nenhuma constante de case corresponder à condição e se houver um label default , o controle passará para a instrução rotulada pelo label default .

O label default deve ser atingido.

Nota: Pode-se dar outra olhada no operador de comparação, mas não é explicitamente usado na “comparação” referida. Na verdade, não há nenhuma dica de que introduziria o UB para enums com escopo ou sem escopo no nosso caso.


Como bônus, o padrão faz alguma garantia sobre isso, mas com o enum simples?

Se o enum tem ou não escopo, não faz diferença aqui. No entanto, faz diferença se o tipo subjacente é fixo ou não. O completo [decl.enum] / 7 é:

Para uma enumeração cujo tipo subjacente é fixo, os valores da enumeração são os valores do tipo subjacente. Caso contrário, para uma enumeração em que e min é o menor enumerador e e max é o maior, os valores da enumeração são os valores no intervalo b min a b max , definidos da seguinte forma: Seja K 1 para uma representação de complemento de dois e 0 para um complemento ou representação de magnitude de sinal. b max é o menor valor maior ou igual a max (| e min | – K , | e max |) e igual a 2 M – 1 , onde M é um inteiro não negativo. b min é zero se e min é não negativo e – ( bmax + K ) caso contrário.

Vamos dar uma olhada na seguinte enumeração:

 enum ColorUnfixed /* no fixed underlying type */ { red = 0x1, yellow = 0x2 } 

Observe que não podemos definir isso como um enum com escopo definido, pois todas as enums com escopo definido têm tipos subjacentes fixos.

Felizmente, o menor enumerador do ColorUnfixed é red = 0x1 , então max (| e min | – K , | e max |) é igual a | e max | em qualquer caso, que é yellow = 0x2 . O menor valor maior ou igual a 2 , que é igual a 2 M – 1 para um inteiro positivo M é 3 ( 2 2 – 1 ). (Eu acho que a intenção é permitir que o intervalo se estenda em passos de 1 bit.) Segue que b max é 3 e bmin é 0 .

Portanto, 100 estaria fora do intervalo de ColorUnfixed , e o static_cast produziria um valor não especificado antes do CWG 1766 e o ​​comportamento indefinido após o CWG 1766.