Date: Mon, 7 Sep 2026 08:48:28 +0100
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.
> Is it going to just call std::terminate()? If so, what's the difference?
When a function is marked as 'noexcept', the programmer who typed
'noexcept' on their keyboard had one of two things in mind:
(1) No exception will be thrown.
(2) An exception most likely won't be thrown, but in the unlikely
event that an exception is thrown, it's unrecoverable, so kill the
program.
An example of No. 1 would be:
void CopyStr(char *dst, char const *src) noexcept
{
while ( *dst++ = *src++ );
}
An example of No. 2 would be:
std::mutex global_mutex;
char storage[512u];
void CopyStrToGlobal(char const *src) noexcept
{
std::lock_guard mylock(global_mutex);
char *dst = storage;
while ( *dst++ = *src++ );
}
The second function can throw if 'mutex::lock' throws, but if that
happens then your entire process is fried anyway. I think in summation
I can say this:
A function marked as 'noexcept(static)' can result in 'std::terminate'
being called if a fatal unrecoverable exception is thrown.
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.
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.
I see a finite list of possibilities here:
x - Function compiled by C++ compiler with the linkage settings of C++
y - Function compiled by C++ compiler with the linkage settings of C
z - Function compiled by C compiler with the linkage settings of C
Here's how you get X:
#ifdef __cplusplus
int Func(void) noexcept
{
return 6;
}
#endif
Here's how you get Y:
#ifdef __cplusplus
extern "C" int Func(void) noexcept
{
return 6;
}
#endif
Here's how you get Z:
#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
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".
Thiago:
> Not to mention that doing extern "C" { #include } is a blast from the past,
> from the early 90s, and has hardly been needed in modern times.
It's simply been moved into the header file. A typically C/C++ header
file looks like this:
#ifndef SOME_LIBRARY_H
#define SOME_LIBRARY_H
#ifdef __cplusplus
extern "C" {
#endif
// Function declarations go here
#ifdef __cplusplus
}
#endif
#endif
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)'.
Thiago:
>> you don't want an exception to be thrown between "OpenResource" and "CloseResource".
> Very true... and irrelevant? How is this applicable to the discussion?
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.
Rainer:
> This is a step backwards from noexcept(expression), because it removes
> the expression. An expression cannot return `static`.
Agreed that it doesn't fit the existing grammar -- 'static' is not an
expression. I chose it because 'static' already means "at compile
time" in 'static_assert', and because it needs no new keyword. I'm not
attached to the spelling; noexcept(checked), an attribute, or
something else would all work. I care about the semantics, not so much
about the exact token.
Rainer:
> If a statically checked noexcept is a thing, then there should be a
> way to optionally enable it for template functions.
Yeah it should be possible to mark a template function as 'noexcept(static)'.
Bingzhi:
> Nice feature!
> I have some questions:
> Should it be SFINAE-friendly? Like, I would use:
> namespace force_safe { template <class Container> constexpr auto size(const Container& x) noexcept(static) { return x.size(); } }
> template <class Container> concept safe_size_usable = requires (Container& x) { fore_safe::size(x); };
>
> Should it matter in function template overloading? I'd like to write:
> namespace optimized_versions
> {
> template <class Book> void reorder(Book& b) noexcept(static) { ... } // optimized noexcept version
> template <class Book> void reorder(Book& b) noexcept(false) { try { ... } catch (...) { ... } }
> }
> instead of writing ``requires requires { { ... } noexcept; }`` constraints for that noexcept version.
I'm open to exploring the possibility of allowing this, but the
definitions of the functions would have to be in header files and
would have to be marked 'inline'.
By the way, let me explain why I came up with the following:
extern "C" noexcept(static) { . . . }
instead of:
extern "C" noexcept { . . . }
The latter will work fine so long as you only have function
declarations between the curly braces. I made it 'noexcept(static)' in
order to accommodate inline functions (i.e. function definitions), and
yes I realise it gets a little hairy here because if you have C/C++
header file, then the inline function could get compiled by either a C
or C++ compiler.
noexcept(static) checks one level deep: it verifies that everything
the body calls is declared noexcept. Those callees can still
terminate, so a noexcept(static) function can still terminate. What it
removes is the possibility that this function is where the mistake
was.
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.
> Is it going to just call std::terminate()? If so, what's the difference?
When a function is marked as 'noexcept', the programmer who typed
'noexcept' on their keyboard had one of two things in mind:
(1) No exception will be thrown.
(2) An exception most likely won't be thrown, but in the unlikely
event that an exception is thrown, it's unrecoverable, so kill the
program.
An example of No. 1 would be:
void CopyStr(char *dst, char const *src) noexcept
{
while ( *dst++ = *src++ );
}
An example of No. 2 would be:
std::mutex global_mutex;
char storage[512u];
void CopyStrToGlobal(char const *src) noexcept
{
std::lock_guard mylock(global_mutex);
char *dst = storage;
while ( *dst++ = *src++ );
}
The second function can throw if 'mutex::lock' throws, but if that
happens then your entire process is fried anyway. I think in summation
I can say this:
A function marked as 'noexcept(static)' can result in 'std::terminate'
being called if a fatal unrecoverable exception is thrown.
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.
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.
I see a finite list of possibilities here:
x - Function compiled by C++ compiler with the linkage settings of C++
y - Function compiled by C++ compiler with the linkage settings of C
z - Function compiled by C compiler with the linkage settings of C
Here's how you get X:
#ifdef __cplusplus
int Func(void) noexcept
{
return 6;
}
#endif
Here's how you get Y:
#ifdef __cplusplus
extern "C" int Func(void) noexcept
{
return 6;
}
#endif
Here's how you get Z:
#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
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".
Thiago:
> Not to mention that doing extern "C" { #include } is a blast from the past,
> from the early 90s, and has hardly been needed in modern times.
It's simply been moved into the header file. A typically C/C++ header
file looks like this:
#ifndef SOME_LIBRARY_H
#define SOME_LIBRARY_H
#ifdef __cplusplus
extern "C" {
#endif
// Function declarations go here
#ifdef __cplusplus
}
#endif
#endif
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)'.
Thiago:
>> you don't want an exception to be thrown between "OpenResource" and "CloseResource".
> Very true... and irrelevant? How is this applicable to the discussion?
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.
Rainer:
> This is a step backwards from noexcept(expression), because it removes
> the expression. An expression cannot return `static`.
Agreed that it doesn't fit the existing grammar -- 'static' is not an
expression. I chose it because 'static' already means "at compile
time" in 'static_assert', and because it needs no new keyword. I'm not
attached to the spelling; noexcept(checked), an attribute, or
something else would all work. I care about the semantics, not so much
about the exact token.
Rainer:
> If a statically checked noexcept is a thing, then there should be a
> way to optionally enable it for template functions.
Yeah it should be possible to mark a template function as 'noexcept(static)'.
Bingzhi:
> Nice feature!
> I have some questions:
> Should it be SFINAE-friendly? Like, I would use:
> namespace force_safe { template <class Container> constexpr auto size(const Container& x) noexcept(static) { return x.size(); } }
> template <class Container> concept safe_size_usable = requires (Container& x) { fore_safe::size(x); };
>
> Should it matter in function template overloading? I'd like to write:
> namespace optimized_versions
> {
> template <class Book> void reorder(Book& b) noexcept(static) { ... } // optimized noexcept version
> template <class Book> void reorder(Book& b) noexcept(false) { try { ... } catch (...) { ... } }
> }
> instead of writing ``requires requires { { ... } noexcept; }`` constraints for that noexcept version.
I'm open to exploring the possibility of allowing this, but the
definitions of the functions would have to be in header files and
would have to be marked 'inline'.
By the way, let me explain why I came up with the following:
extern "C" noexcept(static) { . . . }
instead of:
extern "C" noexcept { . . . }
The latter will work fine so long as you only have function
declarations between the curly braces. I made it 'noexcept(static)' in
order to accommodate inline functions (i.e. function definitions), and
yes I realise it gets a little hairy here because if you have C/C++
header file, then the inline function could get compiled by either a C
or C++ compiler.
noexcept(static) checks one level deep: it verifies that everything
the body calls is declared noexcept. Those callees can still
terminate, so a noexcept(static) function can still terminate. What it
removes is the possibility that this function is where the mistake
was.
Received on 2026-09-07 07:48:47
