On Sun, Apr 27, 2025 at 8:20 AM Jonathan Wakely via Std-Proposals <std-proposals@lists.isocpp.org> wrote:
On Sun, 27 Apr 2025, 00:17 Frederick Virchanza Gotham via Std-Proposals, <std-proposals@lists.isocpp.org> wrote:
The whole 'noexcept' thing has gotten a bit hairy in C++. We've
already thrown 'throw' specifiers out the window, but even now in
C++26 the situation isn't ideal, given that people are now writing
code as follows:

    template<typename T>
    bool Func(T &arg) noexcept ( noexcept(++arg) && noexcept((bool)!!arg) )
    {
        ++arg;
        return !!arg;
    }

. . .which is why some people are now asking for:

    template<typename T>
    bool Func(T &arg) noexcept(auto)
    {
        ++arg;
        return !!arg;
    }
[...]
I think we should have a way of marking a function so that the
compiler will refuse to compile it if it:
    (1) throws an exception (e.g. by using 'throw' or 'std::rethrow_exception')
or
    (2) calls another function that isn't marked as 'noexcept'

I think this would also allow the compiler to optimise more aggressively.

How? The compiler can already see whether the function calls anything which can throw. It seems to me that with this suggested feature, every function is either compiled identically to how it's compiled today, or it becomes ill-formed and doesn't compile at all.

When would this help optimisation?

It wouldn't.
But it would help with the problem I call "rogue terminates" — where you write a noexcept-spec but you get it just slightly too strict, so that the compiler's implicitly generated "invisible exception handler" is not a no-op, and actually ends up calling `std::terminate` in a corner case.
I wrote about this in 2018:
https://quuxplusone.github.io/blog/2018/06/12/attribute-noexcept-verify/
The example of a "rogue terminate" in my blog post is:

template<class T>
auto frobnicate(T t)
  noexcept(std::is_nothrow_copy_constructible_v<T>)
{
  return t;
}
 
static_assert(noexcept(frobnicate(42)));
static_assert(not noexcept(frobnicate(std::string{})));

I concluded:

The problem with noexcept(auto), outlined above, boils down to: noexcept(auto) instructs the compiler to take the wheel. For this to work, the compiler has to be a dang good driver — it has to guess correctly for each function whether we intended it to be noexcept or not. As we saw in the first example, humans are terrible at this game — but as we saw in the second example, compilers seem also to be terrible at it. We can’t hand the wheel to the compiler unless we know the compiler is going to drive right.
So, trust, but verify? Suppose we had a vendor-specific attribute — let’s for the sake of argument call it [[clang::noexcept_verify]] — which would instruct the compiler to compute the same exact thing as noexcept(auto), but then, instead of applying it blindly to our function’s signature, the compiler would merely look for an existing (possibly defaulted) noexcept specification on our function and verify that the computed noexcept-ness matched the noexcept-ness expressed by our existing specification!
The compiled program would still use the noexcept-ness expressed by the programmer. The computed noexcept-ness would be used only for diagnostics; and the diagnostics would not necessarily have to be fatal. (They could be dialed down to warnings.) This would turn our “rogue std::terminate” example above into a compile-time failure.

Nobody ever took me up on the idea of implementing it in a compiler, though, AFAIK.
(I never pursued it myself.)

–Arthur