This is an unofficial snapshot of the ISO/IEC JTC1 SC22 WG21 Core Issues List revision 115e. See http://www.open-std.org/jtc1/sc22/wg21/ for the official list.
2024-11-11
[Voted into WP at August, 2010 meeting.]
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:
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, 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. |
Proposed resolution (October, 2004):
Change 14.2 [except.throw] paragraph 3 as follows:
A throw-expression initializes a temporary object, called the exception object,theby copying the thrown object (i.e., the result of evaluating its assignment-expression operand) to it. The type ofwhichthe exception object is determined by removing any top-level cv-qualifiers from the static type of the operand of throw and adjusting the type from “array of T” or “function returning T” to “pointer to T” or “pointer to function returning T,” respectively. [Note: the temporary object createdforby a throw-expressionthatwhose operand is a string literal is never of type char* or wchar_t*; that is, the special conversions for string literals from the types “array of const char” and “array of const wchar_t” to the types “pointer to char” and “pointer to wchar_t,” respectively (7.3.3 [conv.array]), are never applied to the operand of a throw-expression. —end note] The temporary is an lvalue and is used to initialize the variable named in the matching handler (14.4 [except.handle]). The type of the operand of a throw-expression shall not be an incomplete type, or a pointer to an incomplete type other than (possibly cv-qualified) void. [...]
Change the note in 14.4 [except.handle] paragraph 3 as follows:
[Note: a throw-expression operand thatwhichis an integral constant expression of integer type that evaluates to zero does not match a handler of pointer type; that is, the null pointer constant conversions (7.3.12 [conv.ptr], 7.3.13 [conv.mem]) do not apply. —end note]
Change 14.6.2 [except.terminate] bullet 1.1 as follows:
when the exception handling mechanism, after completing evaluation of theexpression to be thrownoperand of throw but before the exception is caught (14.2 [except.throw]), calls a user function that exits via an uncaught exception,
Change 14.6.3 [except.uncaught] paragraph 1 as follows:
The function std::uncaught_exception() returns true after completing evaluation of theobject to be thrownoperand of throw until completing the initialization of the exception-declaration in the matching handler (_N4140_.18.8.4 [uncaught]).
Change _N4140_.18.8.4 [uncaught] paragraph 1 by adding the indicated words:
Returns: true after completing evaluation of the operand of a throw-expression until either completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate(). [Note: This includes stack unwinding (14.3 [except.ctor]). —end note]
Notes from the April, 2005 meeting:
The CWG discussed this resolution both within the group and with other interested parties. Among the points that were made:
Martin Sebor pointed to a posting in which he argues that writing copy constructors is more difficult if an exception during the copy to the exception object will result in a call to std::terminate().
In response to a question about why the copy to the exception object is different from the copy from the exception object to the object in the exception-declaration, it was observed that the writer of the handler can avoid the second copy (by using a reference declaration), but the first copy is unavoidable.
John Spicer observed that not exiting via exception should be a design constraint for copy constructors in exception objects, regardless of whether std::terminate() is called or not.
Adopting the position that uncaught_exception() returns false during the copy to the exception object would reduce the differences between the case where that copy is elided and the case where it is performed.
Jason Merrill observed that making uncaught_exception() return false during the copy to the exception object would simplify the code generated by g++; as it currently stands, the compiler must generate code to catch exceptions during that copy so std::terminate() can be called.
Bjarne Stroustrup worried that allowing the copy constructor to throw an exception during the copy to the exception object could result in a serious and specific exception being silently transformed into a more trivial and generic one (although the CWG later noted that this risk already exists if something in the expression being thrown throws an exception before the expression completes).
The CWG felt that more input from a wider audience was necessary before a decision could be made on the appropriate resolution.
Notes from the April, 2006 meeting:
The CWG agreed with the position that std::uncaught_exception() should return false during the copy to the exception object and that std::terminate() should not be called if that constructor exits with an exception. The issue was returned to “drafting” status for rewording to reflect this position.
Additional notes (September, 2007):
Although this issue deals primarily with when std::uncaught_exception() begins to return true, the specification of when it begins to return false is also problematic. There are two parallel sections that define the meaning of std::uncaught_exception() and each has a different problem. 14.6.3 [except.uncaught] reads,
The function std::uncaught_exception() returns true after completing evaluation of the object to be thrown until completing the initialization of the exception-declaration in the matching handler (_N4140_.18.8.4 [uncaught]).
The problem here is that whether an exception is considered caught (the underlying condition tested by the function) is here presented in terms of having initialized the exception-declaration, while in other places it is specified by having an active handler for the exception, e.g., 14.2 [except.throw] paragraph 6:
An exception is considered caught when a handler for that exception becomes active (14.4 [except.handle]).
This distinction is important because of 14.4 [except.handle] paragraph 3:
A handler is considered active when initialization is complete for the formal parameter (if any) of the catch clause. [Note: the stack will have been unwound at that point. —end note] Also, an implicit handler is considered active when std::terminate() or std::unexpected() is entered due to a throw.
Note that there is no exception-declaration to be initialized for the std::terminate() and std::unexpected() cases; nevertheless, according to _N4140_.18.8.4 [uncaught], std::uncaught_exception() is supposed to return false when one of those two functions is entered.
The specification in _N4140_.18.8.4 [uncaught] is not well phrased, however, and is open to misinterpretation. It reads,
Returns: true after completing evaluation of a throw-expression until either completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate().
The problem here is lack of parallelism: does “after entering terminate” refer to the condition for returning true or false? This would be better phrased along the lines of
Returns: true after completing evaluation of a throw-expression until a handler for the exception becomes active (14.4 [except.handle]).
Proposed resolution (March, 2010):
Change 14.6.2 [except.terminate] bullet 1.1 as follows:
In the following situations exception handling must be abandoned for less subtle error handling techniques:
when the exception handling mechanism, after completing
evaluation of the expression to be thrownthe initialization of the exception object but before theexception is caughtactivation of a handler for the exception (14.2 [except.throw]), calls a function that exits via an uncaught exception, [Footnote: For example, if the object being thrown is of a class with a copy constructor, std::terminate() will be called if that copy constructor exits with an exception duringa throwthe initialization of the formal parameter of a catch clause. —end footnote]...
Change 14.6.3 [except.uncaught] paragraph 1 as follows:
The function std::uncaught_exception() returns true after completingevaluation of the object to be thrownthe initialization of the exception object (14.2 [except.throw]) until completing theinitialization of the exception-declaration in the matching handleractivation of a handler for the exception (14.4 [except.handle], _N4140_.18.8.4 [uncaught])...
Change _N4140_.18.8.4 [uncaught] paragraph 1 as follows:
Returns: true aftercompleting evaluation of a throw-expressioninitializing an exception object 14.2 [except.throw] untileither completing initialization of the exception-declaration in the matching handler or entering unexpected() due to the throw; or after entering terminate() for any reason other than an explicit call to terminate()a handler for the exception (including unexpected() or terminate()) is activated (14.4 [except.handle]). [Note: This includes stack unwinding (14.3 [except.ctor]). —end note]