Date
2025-03-27.21:45:20
Message id
1073

Content

See also issue 37.

Given this piece of code and S having a user-defined ctor, at precisely which point must std::uncaught_exception() return true and where false?

    try { S s0; throw s0; } catch (S s2) { }

My understanding of the semantics of the code is as follows:

  1. The throw expression creates a temporary for a copy of s0, say s1, using the copy ctor of S. In this invocation of the copy ctor uncaught_exception() must return true.
  2. s0 is destroyed during stack unwinding. In the invocation of S dtor uncaught_exception() must still return true.
  3. The variable s2 is initialized from s1 by invoking the copy ctor of S. In this invocation uncaught_exception() must also return true.
  4. s2 and s1 are destroyed. In the invocations of S dtor uncaught_exception() must return false.

Is my understanding correct?

14.2 [except.throw] paragraph 3 talks about “the exception object” when describing the semantics of the throw-expression:

a throw-expression initializes a temporary object, called the exception object...

However, 14.6.2 [except.terminate] paragraph 1 talks about “the expression to be thrown” when enumerating the conditions under which terminate() is called:

when the exception handling mechanism, after completing evaluation of the expression to be thrown but before the exception is caught (14.2 [except.throw]), calls a user function that exits via an uncaught exception...

And, _N5001_.14.6.3 [except.uncaught] paragraph 1 refers to “the object to be thrown” in the description of uncaught_exception():

The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown...

Are all these objects one and the same? I believe the answer is important in case the construction of the temporary exception object throws another exception.

Suppose they are the same. Then uncaught_exception() invoked from the copy ctor for s1 (from the example [above]) must return false and a new exception (e.g., bad_alloc) may be thrown and caught by a matching handler (i.e., without calling terminate()).

But if they are not the same, then uncaught_exception() invoked from the copy ctor for s1 must return true and throwing another exception would end up calling terminate(). This would, IMO, have pretty severe consequences on writing exception safe exception classes.

As in the first case, different compilers behave differently, with most compilers not calling terminate() when the ctor for the temporary exception object throws. Unfortunately, the two compilers that I trust the most do call terminate().

FWIW, my feeling is that it should be possible for the copy ctor invoked to initialize the temporary exception object to safely exit by throwing another exception, and that the new exception should be allowed to be caught without calling terminate.

Mike Miller: The way I see this, a throw-expression has an assignment-expression as an operand. This expression is “the expression to be thrown.” Evaluation of this expression yields an object; this object is “the object to be thrown.” This object is then copied to the exception object.

Martin Sebor: Here's a survey of the return value from uncaught_exception() in the various stages of exception handling, implemented by current compilers:

expr temp unwind handlr 2nd ex
HP aCC 6 0 0 1 0 OK
Compaq C++ 6.5 0 0 1 1 ABRT
EDG eccp 3.4 0 1 1 1 ABRT
g++ 3.4.2 0 0 1 0 OK
Intel C++ 7.0 0 0 1 0 OK
MIPSpro 7.4.1 0 0 1 1 ABRT
MSVC 7.0 0 0 1 0 OK
SunPro 5.5 1 1 1 0 OK
VisualAge 6.0 0 1 1 1 OK

In the table above:

    expr is the evaluation of the assignment-expression in the throw-expression
    temp is the invocation of the copy ctor for the unnamed temporary exception object created by the runtime.
    unwind is stack unwinding.
    handlr is the invocation of the copy ctor in the exception-declaration in the catch handler.
    2nd ex describes the behavior of the implementation when the invocation of the copy ctor for the unnamed temporary exception object [temp] throws another exception.