The noexcept Specifier & Block

http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2855.html#noexcept I gather that noexcept void f() { throw 0; } is supposed to be ill-formed versus well-formed noexcept void f() { noexcept { throw 0; } } invoking undefined behaviour. Well, this is not helpful. Both forms shall be well-formed with defined behaviour: invocation of unexpected() at throw point. regards, alexander.

Alexander Terekhov wrote:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2855.html#noexcept
I gather that
noexcept void f() { throw 0; }
is supposed to be ill-formed versus well-formed
noexcept void f() { noexcept { throw 0; } }
invoking undefined behaviour.
Well, this is not helpful.
Yes it is. The noexcept block is the programmer's way of telling the compiler, "Trust me, I know what I'm doing." If the programmer doesn't, that's not the compiler's fault.
Both forms shall be well-formed with defined behaviour: invocation of unexpected() at throw point.
We don't need noexcept for that; that's what an empty throw declaration does. Sebastian

Sebastian Redl wrote: [...]
unexpected() at throw point.
We don't need noexcept for that; that's what an empty throw declaration does.
I'll let David Abrahams explain it to you. He knows quite well that violation of "throw declaration" under the current standard doesn't result in invocation of unexpected() at *throw point*. Beside that, you're simply not paying attention: http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2855.html#exception... "We therefore propose to deprecate C++ exception specifications in C++0x" regards, alexander.

Alexander Terekhov wrote:
Sebastian Redl wrote: [...]
unexpected() at throw point.
We don't need noexcept for that; that's what an empty throw declaration does.
I'll let David Abrahams explain it to you. He knows quite well that violation of "throw declaration" under the current standard doesn't result in invocation of unexpected() at *throw point*.
Minor detail that I overlooked in your post. No need to get so condescending. I really don't appreciate it.
Beside that, you're simply not paying attention:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2855.html#exception...
"We therefore propose to deprecate C++ exception specifications in C++0x"
So throw() is deprecated. That doesn't change the fact that its behavior is very similar to what you propose for noexcept. But if I were to implement noexcept in a compiler (and actually, there's a good chance I will be the one to implement it in Clang), I might give debug mode a stack of noexcept block locations, which is checked on throw. Add a flag that we're inside a try, and we've pretty much covered it. However, this approach is not suitable for what you want, for two reasons: 1) There's a (small) cost at entering a noexcept or try block. I very much doubt the standards committee would vote for something that prevents zero-cost exceptions. 2) It's not completely reliable. It won't detect the case where we're in a try block that doesn't catch all exceptions, and in particular won't catch the one you're about to throw. I still think that the compile error for noexcept-marked functions is something we definitely want. Sebastian

Sebastian Redl wrote: [...]
So throw() is deprecated. That doesn't change the fact that its behavior is very similar to what you propose for noexcept.
No, throw() injects catch(...) and I certainly don't want that for noexcept. With or without*** static checking, noexcept specifier is much better than throw(). [...]
I still think that the compile error for noexcept-marked functions is something we definitely want.
So that you can turn such compile errors into undefined behaviour using noexcept blocks or just swallow exceptions? ***) See e.g. http://www.mindview.net/Etc/Discussions/CheckedExceptions regards, alexander.

Alexander Terekhov wrote:
Sebastian Redl wrote: [...]
So throw() is deprecated. That doesn't change the fact that its behavior is very similar to what you propose for noexcept.
No, throw() injects catch(...) and I certainly don't want that for noexcept.
The only difference between that and your proposal is whether destructors between the call site and the catch are called before unexpected() is called. Which is pretty irrelevant, really.
With or without*** static checking, noexcept specifier is much better than throw().
Leaving implementation issues, such as the overhead of catch(...), aside, what exactly are the advantages of noexcept over throw() under your proposal?
I still think that the compile error for noexcept-marked functions is something we definitely want.
So that you can turn such compile errors into undefined behaviour using noexcept blocks or just swallow exceptions?
I can turn most type mismatch compile errors into undefined behavior by using reinterpret_cast. What's your point? Regards, Sebastian

Sebastian Redl wrote:
Alexander Terekhov wrote:
Sebastian Redl wrote: [...]
So throw() is deprecated. That doesn't change the fact that its behavior is very similar to what you propose for noexcept.
No, throw() injects catch(...) and I certainly don't want that for noexcept.
The only difference between that and your proposal is whether destructors between the call site and the catch are called before unexpected() is called. Which is pretty irrelevant, really.
I don't quite agree. The fact that users can set an unexpected handler seems to indicate that the application ought to be in a well-defined state at the point where that function is called. I would certainly assume that the stack has correctly unwound until it hit a point where the exception is not allowed to pass. Thanks, Stefan -- ...ich hab' noch einen Koffer in Berlin...

Sebastian Redl wrote: [...]
I still think that the compile error for noexcept-marked functions is something we definitely want.
So that you can turn such compile errors into undefined behaviour using noexcept blocks or just swallow exceptions?
I can turn most type mismatch compile errors into undefined behavior by using reinterpret_cast. What's your point?
To repeat: noexcept void f() { throw 0; } is supposed to be ill-formed versus well-formed noexcept void f() { noexcept { throw 0; } } invoking undefined behaviour. My point is that both forms shall be well-formed with defined behaviour: invocation of unexpected() at throw point. regards, alexander.

Alexander Terekhov wrote:
To repeat:
noexcept void f() { throw 0; }
is supposed to be ill-formed versus well-formed
noexcept void f() { noexcept { throw 0; } }
invoking undefined behaviour.
My point is that both forms shall be well-formed with defined behaviour: invocation of unexpected() at throw point.
What's the point of the noexcept block then? If you want to submit your proposal, have it remove that block entirely, since it doesn't change anything. Sebastian

Sebastian Redl wrote:
Alexander Terekhov wrote:
To repeat:
noexcept void f() { throw 0; }
is supposed to be ill-formed versus well-formed
noexcept void f() { noexcept { throw 0; } }
invoking undefined behaviour.
My point is that both forms shall be well-formed with defined behaviour: invocation of unexpected() at throw point.
What's the point of the noexcept block then?
To have noexcept blocks in addition to functions. void f() { ... noexcept { std::cout << __func__; } ... } regards, alexander.

On Thu, Apr 16, 2009 at 15:28, Alexander Terekhov <terekhov@web.de> wrote:
http://www.open-std.org/JTC1/SC22/WG21/docs/papers/2009/n2855.html#noexcept
I gather that
noexcept void f() { throw 0; }
is supposed to be ill-formed versus well-formed
noexcept void f() { noexcept { throw 0; } }
invoking undefined behaviour.
Well, this is not helpful.
Both forms shall be well-formed with defined behaviour: invocation of unexpected() at throw point.
But then if you have this: noexcept void f() { noexcept { bar(); } } I don't see how you're going to get the "at throw point" you're asking for. There's always catch (...) if you need to make sure, so treating it as a "ok, I'll believe you" mark in the compiler makes good sense to me. I figure it'd end up like const: A real pain to add in just a few places if you don't use it much, but once it propagates around it'd because automatic and rarely bypassed. ~ Scott

On Thu, Apr 16, 2009 at 7:11 PM, Alexander Terekhov <terekhov@web.de> wrote:
Scott McMurray wrote: [...]
I don't see how you're going to get the "at throw point" you're asking for.
Two-phase EH.
An implementation strategy used by some compilers. It is not something required by C++. It is mysterious why some popular compilers insist on using that strategy when they know they are dealing with C++ applications.

Gabriel Dos Reis wrote:
On Thu, Apr 16, 2009 at 7:11 PM, Alexander Terekhov <terekhov@web.de> wrote:
Scott McMurray wrote: [...]
I don't see how you're going to get the "at throw point" you're asking for.
Two-phase EH.
An implementation strategy used by some compilers. It is not something required by C++. It is mysterious why some popular compilers insist on using that strategy when they know they are dealing with C++ applications.
Because that strategy allows to not unwind the stack for unexpected exceptions. regards, alexander.

On Fri, Apr 17, 2009 at 6:55 AM, Alexander Terekhov <terekhov@web.de> wrote:
Gabriel Dos Reis wrote:
On Thu, Apr 16, 2009 at 7:11 PM, Alexander Terekhov <terekhov@web.de> wrote:
Scott McMurray wrote: [...]
I don't see how you're going to get the "at throw point" you're asking for.
Two-phase EH.
An implementation strategy used by some compilers. It is not something required by C++. It is mysterious why some popular compilers insist on using that strategy when they know they are dealing with C++ applications.
Because that strategy allows to not unwind the stack for unexpected exceptions.
So, now we are talking of cost of doing something not required by C++ semantics; rarely used in practice, but with a cost imposed on those who don't want it. A strategy that hurts performance of C++ programs. -- Gaby

Gabriel Dos Reis wrote:
On Fri, Apr 17, 2009 at 6:55 AM, Alexander Terekhov <terekhov@web.de> wrote:
Gabriel Dos Reis wrote:
On Thu, Apr 16, 2009 at 7:11 PM, Alexander Terekhov <terekhov@web.de> wrote:
Scott McMurray wrote: [...]
I don't see how you're going to get the "at throw point" you're asking for.
Two-phase EH.
An implementation strategy used by some compilers. It is not something required by C++. It is mysterious why some popular compilers insist on using that strategy when they know they are dealing with C++ applications.
Because that strategy allows to not unwind the stack for unexpected exceptions.
So, now we are talking of cost of doing something not required by C++ semantics; rarely used in practice, but with a cost imposed on those who don't want it. A strategy that hurts performance of C++ programs.
Wow. Are you sure? Let all the folks like http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf know about such disastrous nature of two-phase EH. regards, alexander.

On Fri, Apr 17, 2009 at 10:27 AM, Alexander Terekhov <terekhov@web.de> wrote:
Gabriel Dos Reis wrote:
On Fri, Apr 17, 2009 at 6:55 AM, Alexander Terekhov <terekhov@web.de> wrote:
Gabriel Dos Reis wrote:
On Thu, Apr 16, 2009 at 7:11 PM, Alexander Terekhov <terekhov@web.de> wrote:
Scott McMurray wrote: [...]
I don't see how you're going to get the "at throw point" you're asking for.
Two-phase EH.
An implementation strategy used by some compilers. It is not something required by C++. It is mysterious why some popular compilers insist on using that strategy when they know they are dealing with C++ applications.
Because that strategy allows to not unwind the stack for unexpected exceptions.
So, now we are talking of cost of doing something not required by C++ semantics; rarely used in practice, but with a cost imposed on those who don't want it. A strategy that hurts performance of C++ programs.
Wow. Are you sure?
Yes.
Let all the folks like
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038a/IHI0038A_ehabi.pdf
know about such disastrous nature of two-phase EH.
That document only provides the complement to the psABI for the so-called 'common vendor ABI' with its 'official' website at http://www.codesourcery.com/public/cxx-abi/abi-eh.html And quoting for the document: # A two-phase exception-handling model is not strictly necessary to implement # C++ language semantics, but it does provide some benefits. For example, # the first phase allows an exception-handling mechanism to dismiss an # exception before stack unwinding begins, which allows resumptive exception # handling (correcting the exceptional condition and resuming execution at # the point where it was raised). While C++ does not support resumptive # exception handling, other languages do, and the two-phase model allows # C++ to coexist with those languages on the stack. How many industrial strength languages support resumptive exception handling? -- Gaby

Gabriel Dos Reis wrote: [...]
http://www.codesourcery.com/public/cxx-abi/abi-eh.html
And quoting for the document:
# A two-phase exception-handling model is not strictly necessary to implement # C++ language semantics, but it does provide some benefits. For example, ^^^^^^^^^^^^
# the first phase allows an exception-handling mechanism to dismiss an # exception before stack unwinding begins, which allows resumptive exception # handling (correcting the exceptional condition and resuming execution at # the point where it was raised). While C++ does not support resumptive # exception handling, other languages do, and the two-phase model allows # C++ to coexist with those languages on the stack.
How many industrial strength languages support resumptive exception handling?
Irrelevant. There are other benefits provided by two-phase EH apart from resumption. Avoiding unwinding for unexpected exceptions, for example. regards, alexander.

On Fri, Apr 17, 2009 at 12:59 PM, Alexander Terekhov <terekhov@web.de> wrote:
Gabriel Dos Reis wrote: [...]
http://www.codesourcery.com/public/cxx-abi/abi-eh.html
And quoting for the document:
# A two-phase exception-handling model is not strictly necessary to implement # C++ language semantics, but it does provide some benefits. For example, ^^^^^^^^^^^^
# the first phase allows an exception-handling mechanism to dismiss an # exception before stack unwinding begins, which allows resumptive exception # handling (correcting the exceptional condition and resuming execution at # the point where it was raised). While C++ does not support resumptive # exception handling, other languages do, and the two-phase model allows # C++ to coexist with those languages on the stack.
How many industrial strength languages support resumptive exception handling?
Irrelevant.
Not, if you're talking about the impact of EH.
There are other benefits provided by two-phase EH apart from resumption.
Such as?
Avoiding unwinding for unexpected exceptions, for example.
Which is not C++ semantics.
regards, alexander.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Gabriel Dos Reis wrote:
On Fri, Apr 17, 2009 at 12:59 PM, Alexander Terekhov <terekhov@web.de> wrote:
Gabriel Dos Reis wrote: [...]
http://www.codesourcery.com/public/cxx-abi/abi-eh.html
And quoting for the document:
# A two-phase exception-handling model is not strictly necessary to implement # C++ language semantics, but it does provide some benefits. For example, ^^^^^^^^^^^^
# the first phase allows an exception-handling mechanism to dismiss an # exception before stack unwinding begins, which allows resumptive exception # handling (correcting the exceptional condition and resuming execution at # the point where it was raised). While C++ does not support resumptive # exception handling, other languages do, and the two-phase model allows # C++ to coexist with those languages on the stack.
How many industrial strength languages support resumptive exception handling?
Irrelevant.
Not, if you're talking about the impact of EH.
If you insist... this is very vendor/platform specific. On IBM z/OS for example, resumptive exception handling (aka "condition handling") is available for COBOL, PL/I, C, and C++ (not sure about FORTRAN). I still don't see the relevancy, though.
There are other benefits provided by two-phase EH apart from resumption.
Such as?
Such as the one below.
Avoiding unwinding for unexpected exceptions, for example.
Which is not C++ semantics.
What? It's spelled out in 15.3/9 as implementation-defined C++ semantics. regards, alexander.

On Sat, Apr 18, 2009 at 8:10 AM, Alexander Terekhov <terekhov@web.de> wrote:
Gabriel Dos Reis wrote:
On Fri, Apr 17, 2009 at 12:59 PM, Alexander Terekhov <terekhov@web.de> wrote:
Gabriel Dos Reis wrote: [...]
http://www.codesourcery.com/public/cxx-abi/abi-eh.html
And quoting for the document:
# A two-phase exception-handling model is not strictly necessary to implement # C++ language semantics, but it does provide some benefits. For example, ^^^^^^^^^^^^
# the first phase allows an exception-handling mechanism to dismiss an # exception before stack unwinding begins, which allows resumptive exception # handling (correcting the exceptional condition and resuming execution at # the point where it was raised). While C++ does not support resumptive # exception handling, other languages do, and the two-phase model allows # C++ to coexist with those languages on the stack.
How many industrial strength languages support resumptive exception handling?
Irrelevant.
Not, if you're talking about the impact of EH.
If you insist... this is very vendor/platform specific.
Well, we are deeply into quality of implementation when we are talking about the performance of exception handling and its impact on C++ programs, so I don't where you are driving at.
On IBM z/OS for example, resumptive exception handling (aka "condition handling") is available for COBOL, PL/I, C, and C++ (not sure about FORTRAN).
None of those requires resumptive exception handling.
I still don't see the relevancy, though.
Well, you were blaming 2-phase exception handling, and now you say you don't see the relevance. Make up your friend, comrad.

Gabriel Dos Reis wrote: [...]
Well, you were blaming 2-phase exception handling,
What? I was blaming 1-phase exception handling.
and now you say you don't see the relevance.
You seem to *equate* resumption with 2-phase. This is not the case. Resumption is based on 2-phase, but I was neither blaming nor praising resumption. Does the following help? class ResumptiveEH : TwoPhaseEH { ... };
Make up your friend, comrad.
My comrade is 2-phase EH, friend. regards, alexander.

Alexander Terekhov:
Avoiding unwinding for unexpected exceptions, for example.
The reason the standard specifies calling unexpected() at the exception spec point is conceptually sound (but practically not that useful). Consider void f() { throw X(); } void g() throw(Y) { f(); } void h() throw(Z) { g(); } If unexpected() is always invoked at the throw point, there is no way for an exception to escape this call stack. If it's invoked at the exception specification points, it can, in principle, first throw a Y, then a Z. The primary purpose of unexpected(), as originally envisaged (I presume), is exception translation.

Peter Dimov wrote:
Alexander Terekhov:
Avoiding unwinding for unexpected exceptions, for example.
The reason the standard specifies calling unexpected() at the exception spec point is conceptually sound (but practically not that useful). Consider
void f() { throw X(); }
void g() throw(Y) { f(); }
void h() throw(Z) { g(); }
If unexpected() is always invoked at the throw point, there is no way for an exception to escape this call stack. If it's invoked at the exception specification points, it can, in principle, first throw a Y, then a Z. The primary purpose of unexpected(), as originally envisaged (I presume), is exception translation.
But exception translation can be done much more sensibly without unexpected(). void f() { throw X(); } void g() throw(Y) { translate_any_exception_to<Y>(f); } void h() throw(Z) { translate_exception<Y, Z>(g); } No? regards, alexander.

Alexander Terekhov:
But exception translation can be done much more sensibly without unexpected().
... or not be done at all, which is even more sensible. Regardless; in the pre-Abrahams era, when exception specifications were designed, the prevailing mindset was that the set of exception types a function can throw is of much importance, and that subsystems would design their own exception hierarchies and never throw something outside their hierarchy. Under these assumptions, exception specs and unexpected() do work. You can do
void g() throw(Y) { translate_any_exception_to<Y>(f); }
but this requires intrusive modifications of the g subsystem which you can sometimes avoid with a sufficiently smart unexpected handler. It is not realistic to expect this behavior to be changed, but it might be possible to add another handler, separate from std::unexpected, that is invoked prior to stack unwinding. But I still do not understand your motivation for insisting that this is the correct way to do things. What specific practical problems does this solve?

Peter Dimov wrote: [... exception specifications and std::unexpected() ...]
It is not realistic to expect this behavior to be changed, but it might be possible to add another handler, separate from std::unexpected, that is invoked prior to stack unwinding.
Since that would effectively mandate two-phase EH, I'd be pretty happy (with that handler globally set to abort() in all my programs) except that ...
But I still do not understand your motivation for insisting that this is the correct way to do things. What specific practical problems does this solve?
... why-to-abort()-at-unexpected-throw-point aside for a moment, consider: struct X { int v; X() : v(0) { } ~X() { std::cout << v; } }; void f(); void g() throw() { X x; f(); x.v = 42; } with a possibility of stack unwinding due to a throw from f(), compiler has to either generate redundant initialization store (x.v = 0) or increase the size of cleanup code to let the X's destructor see 0 in x.v instead of 42 (on the path of unwinding due to a throw from f()). I don't want to have either. regards, alexander.

Alexander Terekhov wrote:
Gabriel Dos Reis wrote:
On Thu, Apr 16, 2009 at 7:11 PM, Alexander Terekhov <terekhov@web.de> wrote:
Scott McMurray wrote: [...]
I don't see how you're going to get the "at throw point" you're asking for.
Two-phase EH.
An implementation strategy used by some compilers. It is not something required by C++. It is mysterious why some popular compilers insist on using that strategy when they know they are dealing with C++ applications.
Because that strategy allows to not unwind the stack for unexpected exceptions.
Can you elaborate on that a little ? 15.5.2/1 says "If a function with an exception-specification throws an exception that is not listed in the exception-specification, the function void unexpected(); is called (18.6.2) immediately after completing the stack unwinding for the former function." So, stack unwinding certainly has to happen, from the call site down to the point where the exception is not allowed to pass. Am I missing something ? Thanks, Stefan -- ...ich hab' noch einen Koffer in Berlin...

On Fri, Apr 17, 2009 at 9:30 AM, Stefan Seefeld <seefeld@sympatico.ca> wrote:
Alexander Terekhov wrote:
Gabriel Dos Reis wrote:
On Thu, Apr 16, 2009 at 7:11 PM, Alexander Terekhov <terekhov@web.de> wrote:
Scott McMurray wrote: [...]
I don't see how you're going to get the "at throw point" you're asking for.
Two-phase EH.
An implementation strategy used by some compilers. It is not something required by C++. It is mysterious why some popular compilers insist on using that strategy when they know they are dealing with C++ applications.
Because that strategy allows to not unwind the stack for unexpected exceptions.
Can you elaborate on that a little ? 15.5.2/1 says
"If a function with an exception-specification throws an exception that is not listed in the exception-specification, the function void unexpected(); is called (18.6.2) immediately after completing the stack unwinding for the former function."
So, stack unwinding certainly has to happen, from the call site down to the point where the exception is not allowed to pass.
Am I missing something ?
Lisp has the notion of 'restartable exception' (condition in Lisp speak), so the general facility was designed to accomodate for that. However, in practice most C++ programs don't need that expensive 2-phase search of handlers. Others argue that some programs may want the debugger to drop them at the point where an exception was thrown, but again it is not clear that that expensive 2-phase search is actually used. In any case, it is an implementation detail cost that is uniformly imposed on C++ programs (especially the vast majority that do not use it). -- Gaby

Stefan Seefeld wrote:
Alexander Terekhov wrote:
Gabriel Dos Reis wrote:
On Thu, Apr 16, 2009 at 7:11 PM, Alexander Terekhov <terekhov@web.de> wrote:
Scott McMurray wrote: [...]
I don't see how you're going to get the "at throw point" you're asking for.
Two-phase EH.
An implementation strategy used by some compilers. It is not something required by C++. It is mysterious why some popular compilers insist on using that strategy when they know they are dealing with C++ applications.
Because that strategy allows to not unwind the stack for unexpected exceptions.
Can you elaborate on that a little ? 15.5.2/1 says
"If a function with an exception-specification throws an exception that is not listed in the exception-specification, the function void unexpected(); is called (18.6.2) immediately after completing the stack unwinding for the former function."
So, stack unwinding certainly has to happen, from the call site down to the point where the exception is not allowed to pass.
That's only if you use exception-specifications.
Am I missing something ?
15.3/9. regards, alexander.

Stefan Seefeld wrote:
Alexander Terekhov wrote:
Two-phase EH.
An implementation strategy used by some compilers. It is not something required by C++. It is mysterious why some popular compilers insist on using that strategy when they know they are dealing with C++ applications.
Because that strategy allows to not unwind the stack for unexpected exceptions.
Can you elaborate on that a little ?
Sorry for jumping in. I think he meant unhandled, or uncaught exception, see 15.3/9 and 15.5.1/2: "If no matching handler is found in a program, the function terminate() is called; whether or not the stack is unwound before this call to terminate() is implementation-defined (15.5.1)."
participants (7)
-
Alexander Terekhov
-
Gabriel Dos Reis
-
Ilya Sokolov
-
Peter Dimov
-
Scott McMurray
-
Sebastian Redl
-
Stefan Seefeld