No, noexcept confusingly doesn't mean "does not throw exceptions" in that sense. There is no constraint that says you can only call noexcept code from noexcept code - quite the opposite. Noexcept puts NO constraints on the code.
All noexcept does is catch any exception and immediately std::terminate. Confusingly this means that noexcept should really be called deathexcept, since any exception thrown within kills the program.
> All noexcept does is catch any exception and immediately std::terminate.
While that's a possible implementation; the standard is a bit more relaxed: `noexcept` may also call `std::terminate` immediately when an exception is thrown, without calling destructors in the usual way a catch block would do.
More than an optimization is a different exception handling philosophy.
AFAIK itanium ABI exception handling requires two phase unwinding: first the stack is traversed looking for a valid landing pad: if it succeeds then the stack is traversed again calling all destructors. If it fails it calls std terminate. This is actually slower as it need to traverse twice, but the big advantage is that if the program would abort, the state of the program is preserved in the core file. This is easily generalized with noexcept functions: no unwind info is generated for those, so unwind always fail.
MSVC used to do one pass unwind, but I thought they changed it when they implemented table based unwind for x64.
> All noexcept does is catch any exception and immediately std::terminate.
I don't think this is a decent interpretation of what no except does. It misses the whole point of this feature, and confuses a failsafe with the point of using it.
The whole point of noexcept is to tell the compiler that the function does not throw exceptions. This allows the compiler to apply optimizations, such as not needing to track down the necessary info to unwind the call stack when an exception is thrown. Some containers are also designed to only invoke move constructors if they are noexcept and otherwise will copy values around.
As the compiler omits the info required to recover from exceptions, if one is indeed thrown and bubbles up to the noexcept function then it's not possible to do the necessary janitorial work. Therefore, std::terminate is called instead.
Except the compiler still needs to ensure that if an exception bubbles through a noexcept function, std::terminate will be called, even if the exception were caught at some level above. So it still needs to keep track of the noexcept frames, even when inlining the noexcept function.
Generally what a language feature is intended to do is less relevant than what it actually does, over time. Just like the "inline" keyword was intended as a compiler hint, but what it actually does is change the visibility of symbols across compilation units, and alter the rules for static variables.
Of course, noexcept isn't as useless as inline, yet. There are real uses of it as a hint in template metaprogramming, as you said.
> Except the compiler still needs to ensure that if an exception bubbles through a noexcept function, std::terminate will be called, even if the exception were caught at some level above.
That's the failsafe I mentioned.
> Generally what a language feature is intended to do is less relevant than what it actually does, over time.
Not true, and again this take misses the whole point. The point of noexcept is to declare that a function does not throw exceptions. If a function is declared as noexcept, the whole point is that it does not throw exceptions. How the runtime handles a violation of this contract is something that only expresses in a bug. The same applies for exceptions thrown in destructors. It makes no sense to describe destructors as something that calls std::terminate .
Also, of course destructors are not "something that calls std::terminate", because they do many other things.
For function definitions, noexcept is perfectly equivalent to wrapping the body of your function in try {} catch (...) {std::terminate();}. In fact, noexcept doesn't even add any extra optimization options for the compiler: the compiler can already use the presence of such a block to decide that your function can't throw exceptions and optimize accordingly. Whether it can also ellide this block is a different matter, and noexcept doesn't help with that. It's just a nifty piece of syntax instead of this block.
Conversely, the noexcept declaration on the function doesn't allow the compiler to deduce anything about exceptions not being thrown inside the function, because the standard doesn't forbid throwing exceptions.
The only purpose noexcept serves other than a try/catch block is when used with declarations - there is indeed a difference in how the compiler can optimize a call to a function declared `extern void foo() noexcept;` and a function declared as `extern void foo();`
Panic=abort is more like -fno-exceptions since it applies to all the code being compiled and not just function. Codegen can also take advantage of the fact that it won't have to unwind.
I don't think there is a rust equivalent of noexcept.
The behavior of `-fno-exceptions` isn't standardized, because it's a compiler feature, not a part of the C++ standard. The standard says:
> In the situation where no matching [exception] handler is found, it is implementation-defined whether or not the stack is unwound before std::terminate is invoked. In the situation where the search for a handler encounters the outermost block of a function with a non-throwing exception specification, it is implementation-defined whether the stack is unwound, unwound partially, or not unwound at all before the function std::terminate is invoked.
So, the whole thing is basically implementation-defined (including `-fno-exceptions`, since that is something that implementing compilers provide).
The compiler can tell about the immediate function, but not any functions it calls.
If a function marked noexcept calls a function that throws an exception, then the program is terminated with an uncaught exception. A called function can throw through a non-noexcept function to a higher-level exception handler no problem.
So in order to avoid changing the semantics of the function, the compiler would have to be able to determine that that transitive closure of called functions dynamically don't throw, and that problem is undecidable, even assuming the requirement that "the compiler can see the source of all those functions" is somehow met, which it won't be.
> A called function can throw through a non-noexcept function to a higher-level exception handler no problem.
This is exactly the problem. To have made this a useful feature, it should have been more restrictive: a noexcept function should not have been allowed to call any function or operator or lambda that is not marked noexcept. Some extra syntax to allow function templates to be made "conditionally noexcept" would have been necessary, but overall the feature would have had a real use and real power to help make code safer, and more performant.
Java has the first part down, for the class of checked exceptions: a function that doesn't throw can't call functions that do (except in try/catch blocks, but that's largely irrelevant). The annoyances come because of the missing second part - the ability to make a generic function that throws for some type parameters, but doesn't for others.
> Java has the first part down, for the class of checked exceptions: a function that doesn't throw can't call functions that do (except in try/catch blocks, but that's largely irrelevant).
That's not actually true, it's possible to do "sneaky throws" (https://www.baeldung.com/java-sneaky-throws) of a checked exception from a method which isn't declared to throw that checked exception. The classic example is Class.newInstance(), which propagates any exception from the called constructor. Other ways are calling code from JVM languages other than Java, which do not have the same "checked exception" concept (like Kotlin: see https://kotlinlang.org/docs/exceptions.html and https://kotlinlang.org/docs/java-to-kotlin-interop.html#chec...), generics trickery to confuse the compiler, and manually creating JVM bytecode.
Sure, but that's essentially the same as using explicit jumps through raw assembly instructions to go around C++'s destructor guarantees. That is, when your Java process runs non-Java code, of course this can defeat certain Java guarantees. No programming language can make promises for semantics of external code like this.
That's still the case in pure Java code. The "Class.newInstance()" method is a public method on the core Java API, calling "MyClass.class.newInstance()" is mostly equivalent to "new MyClass()". And the generics trick in the "sneaky throws" article I linked is also pure Java code, without any calls to "sun.misc.Unsafe".
Class.newInstance() is a known unsafe method and has been deprecated for quite some time now (since Java 9). It's similar to Haskell's unsafePerformIO from this point of view.
The generics hole is indeed interesting, but it's ultimately a known limitation of how generics were implemented in Java, the presence of type inference, and the design of the exception hierarchy, than an intentional feature. When inferring the type of T to apply in that example, there is no good unique solution: inferring T = Throwable would have been safer, but it makes many simple cases behave unexpectedly, especially with lambdas. Inferring T = RuntimeException is unexpected and unsafe, but in practice it makes many common cases be way more usable, so a call was made to do it, despite the hole.
C++'s templates wouldn't have a similar problem, as they actually instantiate the definition at compile time and can re-check it. Also, there is no equivalent issue to the ambiguous inference, because C++ doesn't do type inference of this kind at all, and anyway there is no problem of the exception hierarchy. Even if there were, C++ could also take the opposite choice than Java, and explicitly infer the safer option when both `noexcept` and `potentially-throws` were possible.
And of course Lombok is a tool for modifying the compilation of Java, so writing Lombok code is not exactly writing pure Java.
That would have been too limiting since there are many (e.g. C) functions that can never throw but are not marked noexcept. Not being able to mark your function noexcept just because you call some standard math function would be counterproductive.
There were ways around this. C functions in the standard library could easily be marked noexcept, for one. Extern C could also imply noexcept. Also, explicit try/catch blocks could be required when calling a potentially-throwing function from a noexcept function - slightly annoying, but not a huge problem in practice.
Overall this was just another case of the C++ community's preference for speed over safety and robustness. Nothing new, but still a shame to see that the attitude hasn't changed at all.
> There were ways around this. C functions in the standard library could easily be marked noexcept, for one.
C++ standard library function yes, C standard library functions, incloding those imported into C++ are more complicated since on most platforms those are not provided by the C++ implementation but some other lower level library. Third-party libraries are the bigger concern though.
> Extern C could also imply noexcept.
In theory this might be correct but in practice this will cause problems with C functions that take callbacks which the user might want to throw from. These are already problematic especially since the C code won't unwind anything but you'd still need to concern yourself with potentially breaking user code.
> Also, explicit try/catch blocks could be required when calling a potentially-throwing function from a noexcept function - slightly annoying, but not a huge problem in practice.
Yes, but avoiding that is the whole point. The try-cach for potentially when you call potentially throwing functions is already implicitly provided by current noexcept and if your noexcept function only calls only noexcept functions then the compiler can already elide all that.
> Overall this was just another case of the C++ community's preference for speed over safety and robustness.
Not at all. If that was the case then noexcept would only be a compiler hint and exceptions bubbling up to noexcept functions would be undefined behavior. Arguably that would be better than what we have now.
> C++ standard library function yes, C standard library functions, incloding those imported into C++ are more complicated since on most platforms those are not provided by the C++ implementation but some other lower level library. Third-party libraries are the bigger concern though.
Since noexcept is a compilation-level hint, it only matters that the declaration contains them in the <cstdXX> headers, not who provides the implementations. And since C functions can't deal with exceptions thrown from callbacks, it would make sense to annotate any function pointer that you provide with noexcept as well.
> Yes, but avoiding that is the whole point. The try-cach for potentially when you call potentially throwing functions is already implicitly provided by current noexcept and if your noexcept function only calls only noexcept functions then the compiler can already elide all that.
The current implementation calls std::terminate() if an exception is caught. That needn't be the case in practice: you could well handle the exception in a meaningful way and continue execution. Also, having the programmer explicitly do this is valuable in itself, even if they also just call std::terminate(). For example, with the way noexcept is implemented today, you have no notification if a function you're calling goes from noexcept to potentially throwing until your program actually crashes. If noexcept was an actual compiler-checked keyword, your code would stop compiling if one of the functions you rely on started throwing exceptions, and you could decide what to do (maybe catch it, maybe find an alternative, etc).
> Not at all. If that was the case then noexcept would only be a compiler hint and exceptions bubbling up to noexcept functions would be undefined behavior. Arguably that would be better than what we have now.
Crashing the program is of course better than UB. But it's still the worse possible thing to happen other than that. It basically makes noexcept functions be the most scary functions to ever call, instead of being the safest. And if you want to write a piece of code that can never throw an exception, the compiler still won't help you in any way to do that, it's up to you to do it correctly or crash.
No, we compile in bottom up order, starting with leaf functions, and collecting information about functions as we go. So "not throwing" sort of trickles up when possible to a certain degree.
In LTCG (MSVC)/O3 (GCC/Clang) there are prepasses over the entire callgraph to collect this order
No, it can't do that. My speculation is that likely it is so because in general case this might be a NP hard problem similar to the halting problem https://en.wikipedia.org/wiki/Halting_problem.
I think their point was that the halting problem is NP hard, it is in fact undecidable. Since there is no algorithm that can solve it, there is no point in talking about complexity of that algorithm.
Alternatively, Java shows that it is very much possible to do this - the compiler can enforce that a function can't throw exceptions (limited to checked exceptions in Java, but that is besides the point). The way to do it is easy: just check that it doesn't call throw directly, and that all of the functions it calls are also marked noexcept. No need to explore things any deeper than that.
Of course, the designers of C++ didn't want to impose this restriction for various reasons.
Determining if a function throws is a pretty basic bit of information collected in bottom up codegen (or during pre pass of a whole program optimization) and in no sense NP hard. Compilers have been doing it for decades and it’s useful
Noexcept on the surface is useful, except for the terminate guarantee, which requires a ton of work to avoid metadata size growth and hurts inlining. If violations of noexcept were UB and it was a pure optimization hint the world would be much better
Interestingly, AUTOSAR C++14 Guidance (https://www.autosar.org/fileadmin/standards/R22-11/AP/AUTOSA...) had a "Rule A15-4-4 (required, implementation, automated) A declaration of non-throwing function shall contain noexcept specification." which was thankfully removed in MISRA C++2023 (the latest guidance for C++17, can't give a link, it's a paid document), - it mandates it only for a few special functions (destructors, move constructors/assignments and a few more).
Yes, true, thanks. I confused "will it throw given inputs&state?" with "can it potentially throw?".
I wonder, why compilers don't expose that information? Some operator returning tri-state "this code provably doesn't throw | could throw | can't see, break the compilation" could help writing generic code immensely. Instead we have to resolve to multi-storey noexcept() operator inside a noexcept qualifier which is very detrimental for the code readability...
You very likely can't always answer the question "will this function throw?", but it should be relatively easy to identify the subset of function that recursively call functions that are guaranteed not to throw. That's only a subset of all non-throwing functions of course.
Yes, my mistake was in confusion a run-time question "will it throw?" with "can it throw in theory?". The latter if I'm not mistaken again only requires a throw statement somewhere in a non-dead code, which is totally possible to find out for a code compiler could see.
All noexcept does is catch any exception and immediately std::terminate. Confusingly this means that noexcept should really be called deathexcept, since any exception thrown within kills the program.