0

The title says it all: why did C++ retire the perfectly satisfying, useful, empty throw specification throw() to replace it with another syntax, with the introduction of the new keyword noexcept?

The empty throw specification is the "only throw these enumerated exceptions guarantee" (written throw(X,Y,Z)), but with zero enumerated exceptions: instead of throwing X, Y or Z (and derived types), you can throw the empty set: that is the guarantee for a function to never throw anything at the caller, in other words, the never throw, or "no throw" specification.

That gratuitously made new code using essentially the same tool, expressing the same promise, incompatible with old code, and old code deprecated and then disallowed, breaking backward compatibility for no apparent reason?

What caused such hate of throw()?

Only the old unsafe gets and the silly useless implicit int were treated as harshly, as far as I can tell.

EDIT:

The alleged "duplicate" is predicated on a false statement.

There is nothing "dynamic" in so called "dynamic exception specification". This is what I hate the most with the new throw specification: the implication of the inane terminology that opposes "dynamic" and "static".

curiousguy
  • 8,038
  • 2
  • 40
  • 58
  • Possible duplicate of [Difference between C++03 throw() specifier C++11 noexcept](https://stackoverflow.com/questions/12833241/difference-between-c03-throw-specifier-c11-noexcept) – JaMiT Oct 27 '19 at 02:47
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/201460/discussion-between-nicol-bolas-and-curiousguy). – Nicol Bolas Oct 27 '19 at 03:39

1 Answers1

4

That gratuitously made new code using essentially the same tool, expressing the same promise, incompatible with old code

Correction: if a function violates a dynamic exception specification, std::unexpected is called, which invokes the unexpected handler that by default invokes std::terminate. But the handler can be replaced by a user function. If a function violates noexcept, std::terminate is called directly.

The promises are different. throw() meant "may do unexpected things if it tries to emit an exception". noexcept means "will terminate immediately if it tries to emit an exception."

It was only in C++17 when throw() was made exactly equivalent to noexcept. This was after 6 years of exception specifications (including throw()) being deprecated.

It should be noted that the unexpected difference was explicitly cited in the first papers about noexcept. Specifically:

Note that the usefulness of noexcept(true) as an optimization hint goes way beyond the narrow case introduced by N2855. In fact, it goes beyond move construction: when the compiler can detect non-throwing operations with certainty, it can optimize away a great deal of code and/or data that is devoted to exception handling. Some compilers already do that for throw() specifications, but since those incur the overhead of an implicit try/catch block to handle unexpected exceptions, the benefits are limited.

The implicit try/catch block is necessary because unexpected must be called after unwinding the stack to the throw() function. Essentially, every throw() function looks like this:

void func(params) throw()
try
{
  <stuff>
}
catch(...)
{
  std::unexpected();
}

So when an exception tries to leave a throw() function, the exception gets caught and the stack unwinds. More to the point, every throw() function must have exception machinery built into it. So whatever cost a try/catch block incurs will be incurred by every throw() function.

In the first version of noexcept, emitting an exception was straight-up UB, while later versions switched to being std::terminate. But even with that, there is no guarantee of unwinding. So implementations can implement noexcept in a more efficient way. As the system looks up the stack for the nearest catch clause, if it hits the bottom of a noexcept function, it can go straight to terminate without any of the catching machinery.

What caused such hate of throw()?

Correction: not re-purposing a construct does not imply malice. Especially when inventing a new construct would avoid compatibility breakage, as shown above.

It be noted that throw() is considered to be equivalent to a noexcept function in terms of a noexcept expression. That is, calling a throw() function doesn't throw exceptions, and a noexcept(empty_throw()) expression will result in true.

It should also be noted that a new keyword would be needed anyway. Why? Because throw(<stuff>) in C++98/03 already had a meaning. noexcept(<stuff>) has a very different meaning for the <stuff>. Trying to put the noexcept stuff inside of the throw specifier would be... difficult, from a parsing standpoint.

Plus, you could now employ this new keyword as a generalized expression: noexcept(<expression>) resolves to true if none of the calls within it will throw exceptions. This allows you to do conditional logic based on whether things would throw exceptions. You'd need a new keyword to do something like that (or you'd have to make ugly syntax, and C++ has way too much of that going on).

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
  • "_which invokes the unexpected handler_" Yes indeed, I completely forgot that one, probably because I have not seen any practical use of `set_unexpected`. So the unexpected handler is mostly a complication that changes nothing for the empty throw spec. – curiousguy Oct 27 '19 at 02:40
  • Interesting answer, but I have to disagree here: "_That's not something a dynamic exception specifier can do._" They can do that actually: `throw(...)` – curiousguy Oct 27 '19 at 02:47
  • @curiousguy: I don't have a copy of C++98/03, but I don't recall `throw(...)` being legitimate syntax. The [grammar seems to require a list of type-ids](https://timsong-cpp.github.io/cppwp/n4140/except.spec#nt:dynamic-exception-specification), and I don't think `...` is a type-id. – Nicol Bolas Oct 27 '19 at 03:10
  • I'm sure `throw(...)` exists in the C++ std voted in 97, except (put intended) it's useless and nobody ever used it. – curiousguy Oct 27 '19 at 03:13
  • @curiousguy: My memory of such syntax is limited, but I vaguely remember it, so I removed the paragraph. I also added some details about `unexpected` and such with regard to the original `noexcept` proposal. – Nicol Bolas Oct 27 '19 at 03:18
  • @curiousguy: "*What overhead? Over what?*" I explained this in the very next paragraph. The text itself is written for an audience of people who are expected to already know these kinds of things, so it's not spelled out. – Nicol Bolas Oct 27 '19 at 04:12
  • No, it isn't spelled out because they would be hard press to spell it out. Where would that try-catch be? What would it do? Anyway, you are citing a paper justifying MS semantics, not C++ semantics, so... – curiousguy Oct 27 '19 at 04:17
  • @curiousguy: "*Where would that try-catch be?*" `throw()` requires that, in the event the function attempts to emit an exception, the callstack must be unwound to the start of that function, then `unexpected` gets called. To do that unwinding, the implementation must emit the equivalent of a `try/catch` block around the function. Hence overhead. I don't know what "MS semantics" are; if you're referring to Microsoft (and presumably, VC++), the papers are not written by people with any affiliation with Microsoft. I don't know why you would bring them up in this context, as it is irrelevant. – Nicol Bolas Oct 27 '19 at 04:55
  • 1) Yes and what overhead is that? **Over what?** What is the alternative? And how much overhead? and where? 2) Yes I was referring to MSVC++ which is the current topic... – curiousguy Oct 27 '19 at 04:58
  • @curiousguy: "*Yes and what overhead is that? Over what? What is the alternative?*" The alternative is... not unwinding the stack. I'm not sure why you needed that spelled out. "*And how much overhead? and where?*" It's hard to quantify and is essentially irrelevant; it's there, and it *doesn't need to be*. Don't pay for what you don't use. "*I was referring to MSVC++ which is the current topic...*" Nothing about this question, my answer, or anything I've linked to within that answer, has anything to do with any particular compiler. So yes, it's off topic. – Nicol Bolas Oct 27 '19 at 05:01
  • "_noexcept means "will terminate immediately if it tries to emit an exception._" The only ways to terminate immediately are to call either `abort` or `_exit`. If the user replaced `terminate`, the function can do many things before it exits. That means that the whole state of variables must be in order. – curiousguy Nov 10 '19 at 04:57
  • @curiousguy: Yes, but the *stack* doesn't have to be unwound. Which means you don't have to build the machinery needed to unwind the stack to that point. That's the overhead in question. – Nicol Bolas Nov 10 '19 at 04:58