Em C ++, se throw é uma expressão, qual é o seu tipo?

115

Peguei isso em uma de minhas breves incursões ao reddit:

http://www.smallshire.org.uk/sufficientlysmall/2009/07/31/in-c-throw-is-an-expression/

Basicamente, o autor aponta que em C ++:

throw "error"

é uma expressão. Na verdade, isso é explicado com bastante clareza no padrão C ++, tanto no texto principal quanto na gramática. Porém, o que não está claro (pelo menos para mim) é qual é o tipo da expressão? Achei " void", mas alguns experimentos com g ++ 4.4.0 e Comeau produziram este código:

    void f() {
    }

    struct S {};

    int main() {
        int x = 1;
        const char * p1 = x == 1 ? "foo" : throw S();  // 1
        const char * p2 = x == 1 ? "foo" : f();        // 2
    }

Os compiladores não tiveram problemas com // 1, mas vomitaram em // 2 porque os tipos no operador condicional são diferentes. Portanto, o tipo de throwexpressão não parece ser vazio.

Então o que é?

Se você responder, faça backup de suas declarações com citações do Padrão.


Isso acabou não sendo tanto sobre o tipo de expressão de lançamento, mas como o operador condicional lida com expressões de lançamento - algo que eu certamente não sabia antes de hoje. Obrigado a todos que responderam, mas particularmente a David Thornley.


fonte
10
+1 pergunta incrível. E uma maneira inteligente de testá-lo.
Jeremy Powell
1
Esse link parece deixar bastante claro que o tipo é determinado pelo compilador para ser o que precisa ser.
Draemon
Acho que o artigo vinculado foi atualizado desde que o li, e tenho certeza de que é esse o caso. No entanto, não consigo encontrar no padrão.
Ae talvez não - double d = lance "foo"; é um erro com g + = (não testei com comeau)
1 Estou curioso para saber a resposta.
AraK

Respostas:

96

De acordo com o padrão, 5.16 parágrafo 2 primeiro ponto, "O segundo ou o terceiro operando (mas não ambos) é uma expressão de lançamento (15.1); o resultado é do tipo do outro e é um rvalue." Portanto, o operador condicional não se importa com o tipo de expressão de lançamento, mas apenas usará o outro tipo.

Na verdade, 15.1, parágrafo 1 diz explicitamente "Uma expressão de lançamento é do tipo vazio."

David Thornley
fonte
9
OK - acho que temos um vencedor.
Observe que as expressões de lançamento são expressões de atribuição. Portanto, eles são um erro de sintaxe como um argumento para a maioria dos operadores. Obviamente, você pode ocultá-los entre parênteses, mas se eles não forem ignorados (primeiro argumento do operador embutido, por exemplo), é um erro de tipo.
AProgrammer
4
O que realmente me surpreende é que eles pensaram neste caso e fizeram algo razoável acontecer.
Onifário
31

"Uma expressão de lançamento é do tipo vazio"

ISO14882 Seção 15

Draemon
fonte
Então g ++ e Comeau são negligentes em não fornecer um erro para o meu caso // 1?
2
@ Neil, não realmente porque de acordo com C ++ / 5.16 / 2, o segundo e o terceiro operandos do operador condicional podem ser do tipovoid
mloskot
13

De [expr.cond.2] (operador condicional ?:):

Se o segundo ou o terceiro operando tem tipo (possivelmente cv-qualificado) nulo, então as conversões padrão de valor para r, matriz para ponteiro e função para ponteiro são realizadas no segundo e terceiro operandos, e um dos seguintes deve conter:

- O segundo ou terceiro operando (mas não ambos) é uma expressão de lançamento; o resultado é do tipo do outro e é um rvalue.

- O segundo e o terceiro operandos possuem o tipo void; o resultado é do tipo void e é um rvalue. [Nota: isso inclui o caso em que ambos os operandos são expressões de lançamento. - nota final]

Então, com //1você estava no primeiro caso, com //2, você estava violando "um dos seguintes deve manter", já que nenhum deles faz, nesse caso.

Marc Mutz - mmutz
fonte
3

Você pode ter um tipo de impressora que expõe para você :

template<typename T>
struct PrintType;

int main()
{
    PrintType<decltype(throw "error")> a; 
}

Basicamente, a falta de implementação de PrintTypefará com que o relatório de erro de compilação diga:

instanciação implícita de modelo indefinido PrintType<void>

então podemos realmente verificar se as throwexpressões são do tipo void(e sim, as citações padrão mencionadas em outras respostas verificam que este não é um resultado específico da implementação - embora o gcc tenha dificuldade em imprimir informações valiosas)

Nikos Athanasiou
fonte