En C ++, si throw es una expresión, ¿cuál es su tipo?

115

Recogí esto en una de mis breves incursiones a reddit:

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

Básicamente, el autor señala que en C ++:

throw "error"

es una expresion. En realidad, esto se explica con bastante claridad en el estándar C ++, tanto en el texto principal como en la gramática. Sin embargo, lo que no está claro (al menos para mí) es ¿cuál es el tipo de expresión? Supuse " void", pero un poco de experimentación con g ++ 4.4.0 y Comeau produjo 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
    }

Los compiladores no tuvieron ningún problema con // 1 pero se burlaron de // 2 porque los tipos en el operador condicional son diferentes. Por tanto, el tipo de throwexpresión no parece ser nulo.

¿Así que qué es lo?

Si responde, respalde sus afirmaciones con citas de la Norma.


Esto resultó no ser tanto sobre el tipo de expresión de lanzamiento como sobre cómo el operador condicional maneja las expresiones de lanzamiento, algo que ciertamente no conocía antes de hoy. Gracias a todos los que respondieron, pero particularmente a David Thornley.


fuente
10
+1 pregunta impresionante. Y una forma inteligente de probarlo.
Jeremy Powell
1
Ese vínculo parece dejar bastante claro que el tipo lo determina el compilador para que sea lo que sea necesario.
Draemon
Creo que el artículo vinculado se ha actualizado desde que lo miré, y estoy seguro de que ese es el caso. Sin embargo, no puedo encontrarlo en el estándar.
Y tal vez no - doble d = lanzar "foo"; es un error con g + = (no lo he probado con comeau)
+1 Tengo curiosidad por saber la respuesta.
AraK

Respuestas:

96

De acuerdo con el estándar 5.16, párrafo 2, primer punto, "El segundo o tercer operando (pero no ambos) es una expresión de lanzamiento (15.1); el resultado es del tipo del otro y es un valor r". Por lo tanto, al operador condicional no le importa el tipo de expresión de lanzamiento, sino que solo usará el otro tipo.

De hecho, 15.1, párrafo 1 dice explícitamente "Una expresión de lanzamiento es de tipo void".

David Thornley
fuente
9
OK, creo que tenemos un ganador.
Tenga en cuenta que expresión-lanzamiento son expresión-asignación. Por lo tanto, son un error de sintaxis como argumento para la mayoría de los operadores. Obviamente, puede ocultarlos entre paréntesis, pero si no se ignoran (primer argumento del operador incorporado, por ejemplo), es un error de tipo.
Programador
4
Lo que realmente me sorprende es que pensaron en este caso e hicieron que sucediera algo razonable.
Omnifarious
31

"Una expresión de lanzamiento es de tipo void"

ISO14882 Sección 15

Draemon
fuente
Entonces, ¿g ++ y Comeau son negligentes al no dar un error para mi caso // 1?
2
@Neil, no realmente porque de acuerdo con C ++ / 5.16 / 2, el segundo y tercer operandos del operador condicional pueden ser del tipo devoid
mloskot
13

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

Si el segundo o el tercer operando tiene el tipo (posiblemente calificado por cv) void, entonces las conversiones estándar de lvalor a r, matriz a puntero y función a puntero se realizan en el segundo y tercer operandos, y uno de los siguientes deberá ser válido:

- El segundo o tercer operando (pero no ambos) es una expresión de lanzamiento; el resultado es del tipo del otro y es un valor r.

- Tanto el segundo como el tercer operandos tienen el tipo void; el resultado es de tipo void y es un rvalue. [Nota: esto incluye el caso en el que ambos operandos son expresiones de lanzamiento. - nota final]

Entonces, con //1usted estaba en el primer caso, con //2, estaba violando "uno de los siguientes se mantendrá", ya que ninguno de ellos lo hace, en ese caso.

Marc Mutz - mmutz
fuente
3

Puede hacer que una impresora de tipos lo escupe :

template<typename T>
struct PrintType;

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

Básicamente, la falta de implementación de PrintTypehará que el informe de error de compilación diga:

instanciación implícita de plantilla indefinida PrintType<void>

para que podamos verificar que las throwexpresiones sean de tipo void(y sí, las citas estándar mencionadas en otras respuestas verifican que este no es un resultado específico de la implementación, aunque gcc tiene dificultades para imprimir información valiosa)

Nikos Athanasiou
fuente