C++ Logo

std-proposals

Advanced search

Re: [std-proposals] noexcept(static)

From: Thiago Macieira <thiago_at_[hidden]>
Date: Mon, 07 Sep 2026 05:59:38 -0700
On Monday, 7 September 2026 00:48:28 Pacific Daylight Time Frederick Virchanza
Gotham via Std-Proposals wrote:
> I reply in series below to Thiago, Rainer and Bingzhi.
>
> Thiago:
> > What's the enforcement in case one of them does, despite not being marked
> > so?
> If a function marked as 'noexcept(static)' invokes a function marked
> 'noexcept', and if the invoked function throws an exception, then you
> will get the expected behaviour as stipulated in the C++23 standard:
> specifically 'std::terminate' gets called.

That's not what I meant. And you're misunderstanding what noexcept(true) does.
A noexcept(true) function guarantees itself that it will not throw. The caller
will never, ever see an exception thrown.

What I had meant was that you described the noexcept(static) as a full-promise
not to throw because it can only call other noexcept functions or properly
catch in blocks. But you forgot to describe what happens if something *it*
calls throws and *it* fails to catch that.

> > Is it going to just call std::terminate()? If so, what's the difference?

> I can say this:
>
> A function marked as 'noexcept(static)' can result in 'std::terminate'
> being called if a fatal unrecoverable exception is thrown.

Right, so the exactly same behaviour from outside the function as
noexcept(true).

> Thiago:
> > Not all C functions are written in C - they may actually be just
> > C++ content under an extern "C".
>
> Fair point -- extern "C" is about linkage, not about throwing, and a
> C++ function with C linkage can certainly throw. The region asserts
> something about the declarations it covers; if that assertion is wrong
> you get the same outcome as writing 'noexcept' by hand and being
> wrong, i.e. std::terminate. That is a user error, not a new hazard.

But the difference is that now the *caller* needs to take action, because the
function was not marked as noexcept itself. I predict this portion is DOA. Let
me explain.

If I write:
 void f() noexcept;

I am promising that f() *never ever* throws. This is because compiler insists
that the function definition also write "noexcept" and, thus, when the compiler
compiles that function, it will ensure to emit the necessary exception
handling structures to ensure it won't throw. While this isn't mandatory, the
common way of doing it is to ensure that the exception-unwinding mechanism
can't escape this function in the first place.

Let's say we accept your proposal to decorate around like extern "C":

noexcept(static) {
  void f();
}

What does this mean? Does it mean that f() has the same EH tables that prevent
an exception from escaping? Or does it mean that the caller is now responsible
for intercepting?

You're proposing the second. That prevents the former from being added to the
language, which would be the expected reading of the code above. Look at all
the other proposals about doing labels like

   private static noexcept:

in class declarations.

> What I'd say in practice is: if a library documents itself as C, I
> take that to mean none of its functions throw, unless I hand it a
> callback that does -- and I try not to do that.

Except all the POSIX thread cancellation points. See
https://pubs.opengroup.org/onlinepubs/009695399/functions/
xsh_chap02_09.html#tag_02_09_05_02

Plus, a lot of APIs are designed now with the Lakos rule, meaning functions
with narrow contracts are noexcept(false), in spite if never directly or
intentionally throwing an exception. An example of that would be:

  double sqrt(double);

This is a C function library that never throws, but it does have a domain of
validity. If it had adopted the Lakos rule, it would be noexcept(false).

> #ifndef __cplusplus
> int Func(void)
> {
> return 6;
> }
> #endif
>
> Looking specfically at 'Z', if we were to create a C/C++ header file
> to declare this function, it would be:
>
> #ifdef __cplusplus
> extern "C" int Func(void) noexcept;
> #else
> extern int Func(void);
> #endif

Except that it is not how it is not currently done. It is:

#ifdef __cplusplus
extern "C" {
#endif

int func(void);
int func2(void);
...

#ifdef __cplusplus
}
#endif

There are thousands of projects in C that provide headers like this. And there
are thousands of projects in C++ that provide headers like this too. You're
not going to convince a significant amount of them to change their headers to
significantly make adoption of your syntax acceptable. Heck, I predict that
many of them would actively object to adopting this syntax.

> If I'm told that a library is written in C, such as OpenSSL, then
> here's what I garner from this information: "None of the functions in
> this library will throw an exception -- not unless you pass in a
> callback function that throws an exception, but please don't do that".

Except they can and do. For one thing, it may be calling a function written in
C++ from another library. In reality, all functions in that library that call
POSIX thread cancellation points may unwind the stack.

For example, BIO_write() in OpenSSL.

C does not have exceptions, but C implementations do have stack unwinding.

> Thiago:
> > The interface does not need it, because from the caller's
> > perspective, it's no different than noexcept(true)
>
> The way I've written the compiler patch is that you can only apply
> 'noexcept(static)' to a function definition. The declaration must
> simply be 'noexcept'. You'll get a compiler error if you try to mark a
> function declaration as 'noexcept(static)'.

That might be more acceptable as a C++ feature.

Consider applying it to statements and blocks instead of whole functions.
Because:

> Because 'noexcept(static)' gives you peace of mind to write:
>
> void Func(void) noexcept(static)
> {
> Handle h = OpenResource(...);
>
> // Do Stuff
>
> CloseResource(h);
> }
>
> You have peace of mind that the last line in the function will be
> reached, thus freeing the resource. RAII solves this too, but it needs
> a wrapper type per resource and the compiler never checks that you
> used one. This is a local, enforced property of one function.

Indeed, but are you really going to advocate going away from RAII? Why?

And what if OpenResource() throws? No resource was acquired if it does, so
there is no need to protect it. It should be fine to let the exception escape.

In fact, the way you've designed it, it's incompatible with RAII in the first
plcae. You can't allocate memory at all with operator new. You'd need
something like:

static void helper(std::unique_ptr<Something>) noexcept(static)
{
    // do stuff
} // memory freed here

void func() noexcept(false)
{
    helper(std::make_unique<Something>());
}

Challenge: acquire two resources.

-- 
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
  Principal Engineer - Intel Data Center - Platform & Sys. Eng.

Received on 2026-09-07 12:59:53