En C ++, si throw est une expression, quel est son type?

115

J'ai ramassé ceci dans l'une de mes brèves incursions sur reddit:

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

En gros, l'auteur souligne qu'en C ++:

throw "error"

est une expression. Ceci est en fait assez clairement défini dans le standard C ++, à la fois dans le texte principal et dans la grammaire. Cependant, ce qui n'est pas clair (du moins pour moi), c'est quel est le type de l'expression? J'ai deviné " void", mais un peu d'expérimentation avec g ++ 4.4.0 et Comeau a donné ce code:

    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
    }

Les compilateurs n'ont eu aucun problème avec // 1 mais ont barfié // 2 parce que les types dans l'opérateur conditionnel sont différents. Le type d' throwexpression ne semble donc pas nul.

Alors c'est quoi?

Si vous répondez, veuillez sauvegarder vos déclarations avec des citations de la norme.


Il s'est avéré que cela ne concernait pas tant le type d'une expression throw que la façon dont l'opérateur conditionnel traite les expressions throw - quelque chose que je ne savais certainement pas avant aujourd'hui. Merci à tous ceux qui ont répondu, mais particulièrement à David Thornley.


la source
10
+1 question géniale. Et une manière intelligente de le tester.
Jeremy Powell
1
Ce lien semble indiquer assez clairement que le type est déterminé par le compilateur comme étant celui dont il a besoin.
Draemon
L'article lié a, je pense, été mis à jour depuis que je l'ai regardé, et je suis sûr que c'est effectivement le cas. Cependant, je ne le trouve pas dans la norme.
A et peut-être pas - double d = lancer "foo"; est une erreur avec g = + (ont pas testé avec comeau)
+1 Je suis curieux de connaître la réponse.
AraK

Réponses:

96

Selon la norme, 5.16 paragraphe 2 premier point, "Le deuxième ou le troisième opérande (mais pas les deux) est une expression de jet (15.1); le résultat est du type de l'autre et est une rvalue." Par conséquent, l'opérateur conditionnel ne se soucie pas du type d'une expression throw, mais utilisera simplement l'autre type.

En fait, 15.1, le paragraphe 1 dit explicitement "Une expression de jet est de type void."

David Thornley
la source
9
OK - je pense que nous avons un gagnant.
Notez que throw-expression est une expression d'affectation. Il s'agit donc d'une erreur de syntaxe comme argument pour la plupart des opérateurs. Évidemment, vous pouvez les cacher entre parenthèses, mais s'ils ne sont pas ignorés (premier argument de l'opérateur intégré, par exemple), c'est une erreur de type.
AProgrammer le
4
Ce qui me surprend vraiment, c'est qu'ils ont pensé à cette affaire et ont fait quelque chose de raisonnable.
Omnifarious
31

"Une expression de jet est de type void"

ISO14882, section 15

Draemon
la source
Alors g ++ et Comeau ont tort de ne pas donner d'erreur pour mon cas // 1?
2
@Neil, pas vraiment car selon C ++ / 5.16 / 2, les deuxième et troisième opérandes de l'opérateur conditionnel peuvent être de typevoid
mloskot
13

De [expr.cond.2] (opérateur conditionnel ?:):

Si le deuxième ou le troisième opérande a le type (éventuellement cv-qualifié) void, alors les conversions standard lvalue-to-rvalue, array-to-pointer et function-to-pointer sont effectuées sur les deuxième et troisième opérandes, et l'un des éléments suivants doit détenir:

- Le deuxième ou le troisième opérande (mais pas les deux) est une expression de jet; le résultat est du type de l'autre et est une rvalue.

- Les deuxième et troisième opérandes ont le type void; le résultat est de type void et est une rvalue. [Remarque: cela inclut le cas où les deux opérandes sont des expressions de jet. - note de fin]

Donc, avec //1vous étiez dans le premier cas, avec //2, vous violiez "l'un des éléments suivants tiendra", car aucun d'entre eux ne le fait, dans ce cas.

Marc Mutz - mmutz
la source
3

Vous pouvez demander à une imprimante de type de le recracher pour vous :

template<typename T>
struct PrintType;

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

Fondamentalement, le manque d'implémentation de PrintTypefera dire au rapport d'erreur de compilation:

instanciation implicite d'un modèle non défini PrintType<void>

nous pouvons donc vérifier que les throwexpressions sont de type void(et oui, les guillemets standard mentionnés dans d'autres réponses vérifient qu'il ne s'agit pas d'un résultat spécifique à l'implémentation - bien que gcc ait du mal à imprimer des informations précieuses)

Nikos Athanasiou
la source