O que acontece se você valor estático_cast inválido para a classe enum?

146

Considere este código C ++ 11:

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

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

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

o padrão garante que o padrão seja atingido? Caso contrário, qual é a maneira correta, mais eficiente e mais elegante de verificar um erro aqui?

EDITAR:

Como bônus, o padrão oferece garantias quanto a isso, mas com enumeração simples?

darth happyface
fonte

Respostas:

131

Qual é a cor definida de acordo com o padrão?

Respondendo com uma citação dos padrões C ++ 11 e C ++ 14:

[expr.static.cast] / 10

Um valor do 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. De maneira mais geral, à medida que você converte do tipo subjacente para o tipo de enumeração, nenhum valor em data[0]pode levar a UB para o static_cast.

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

Observe que o CWG 1766 é considerado um defeito no Padrão; portanto, é aceito que os implementadores de compiladores se apliquem aos seus modos de compilação C ++ 11 e C ++ 14.

(*) chardeve ter pelo menos 8 bits de largura, mas não é necessário unsigned. O valor máximo armazenável deve ser pelo menos 127conforme o 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 no intervalo de valores representáveis ​​para seu tipo, o comportamento será indefinido.

Antes do CWG 1766, o tipo de conversão integral -> 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 for afirmativa , não haveria 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 um comportamento indefinido".

Por isso, antes de CWG 1766, ainda static_cast<Color>(10000)seria não invocar UB; mas depois de CWG 1766, que faz invocar UB.


Agora, a switchdeclaração:

[stmt.switch] / 2

A condição deve ser do tipo integral, tipo de enumeração ou tipo de classe. [...] promoções integrais são realizadas.

[conv.prom] / 4

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

Nota: O tipo subjacente de uma enum com escopo sem enum-base é int. Para enumerações sem escopo, o tipo subjacente é definido pela implementação, mas não deve ser maior que intse intpuder conter os valores de todos os enumeradores.

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

Um prvalue de um tipo inteiro diferente de bool, char16_t, char32_t, ou wchar_tcujo número inteiro conversão posto (4,13) é menor do que o grau de intpode ser convertido para um prvalue de tipo intse intpodem representar todos os valores do tipo de fonte; caso contrário, o pré-valor de origem pode ser convertido em um pré-valor do tipo unsigned int.

No caso de uma enumeração sem escopo , estaríamos lidando com ints aqui. Para enumerações com escopo definido ( enum classe enum struct), nenhuma promoção integral se aplica. 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 switchinstrução é 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 caserótulo correspondente . Se nenhuma caseconstante corresponder à condição e se houver um defaultrótulo, o controle passará para a instrução rotulada pelo defaultrótulo.

o default rótulo deve ser atingido.

Nota: Pode-se examinar novamente o operador de comparação, mas ele não é explicitamente usado na "comparação" referida. De fato, não há indícios de que isso introduza o UB para enumerações com ou sem escopo no nosso caso.


Como bônus, o padrão oferece garantias quanto a isso, mas com enumeração simples?

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

Para uma enumeração cujo tipo subjacente é fixo, os valores da enumeração são os valores do tipo subjacente. Caso contrário, uma enumeração onde E min é o menor entrevistador e E max é o maior, os valores de enumeração são os valores na gama b min para b no máximo , definido do seguinte modo: Vamos Kser 1para a representação de um de complemento de dois e 0por um complemento ou representação em magnitude de sinal. b max é o menor valor maior que ou igual a max (| e min | - K, | e max |) e igual a 2M - 1 , ondeMé um número inteiro não negativo. b min é zero se e min não for negativo e - (b max + 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 uma enumeração com escopo, pois todas as enumerações com escopo fixaram tipos subjacentes.

Felizmente, ColorUnfixedo menor enumerador é red = 0x1, então max (| e min | - K, | e max |) é igual a | e max | em qualquer caso, o que é yellow = 0x2. O menor valor maior ou igual a 2, que é igual a 2 M - 1 para um número inteiro positivo, Mé 3( 2 2 - 1 ). (Acho que a intenção é permitir que o alcance seja estendido em etapas de 1 bit.) Segue que b max é 3e bmin é 0.

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

dyp
fonte
3
O tipo subjacente é fixo; portanto, o intervalo dos valores de enumeração (§7.2 [dcl.enum] p7) é "os valores do tipo subjacente". 100 é certamente um valor de char, portanto "O valor é inalterado se o valor original estiver dentro do intervalo dos valores de enumeração (7.2)." aplica-se.
Casey
2
Eu tive que procurar para procurar o que "UB" significava. ('comportamento indefinido') A questão não mencionou a possibilidade de comportamento indefinido; então não me ocorreu que você poderia estar falando sobre isso.
22413 karadoc
2
@karadoc Adicionei um link na primeira ocorrência do termo.
dyp
1
Ame esta resposta. Para aqueles que navegam rápido demais, observe que a última frase "Portanto, 100 estaria fora do intervalo ..." só se aplica se o código foi modificado para remover a especificação de tipo subjacente (neste caso, char). Eu acho que foi isso que quis dizer, de qualquer maneira.
Eric Seppanen 29/03
1
@Ruslan CWG 1766 (ou a sua resolução) não faz parte do C ++ 14, mas acho que fará parte do C ++ 17. Mesmo com as regras do C ++ 17, não entendo bem o que você quer dizer com "invalidar texto adicional da sua resposta". As outras partes da minha resposta estão principalmente preocupadas com o fato de que o "intervalo de valores de enumeração" é o que se refere a expr.static.cast p10.
dyp 29/08/16