Some of the recent work.

This commit is contained in:
2026-07-16 17:44:36 -04:00
parent 7f3f888fef
commit 97328be7ee
+111 -92
View File
@@ -47,6 +47,10 @@ cannotTolerateExceptions() noexcept( true )
try try
{ {
element= someVector.at( 42 ); element= someVector.at( 42 );
if( someVector.capacity() == someVector.size() )
{
someVector.reserve( someVector.size() * 2 );
}
} }
catch( ... ) catch( ... )
{ {
@@ -58,6 +62,7 @@ cannotTolerateExceptions() noexcept( true )
return Failure; return Failure;
} }
someVector.push_back( std::move( element ) ); someVector.push_back( std::move( element ) );
tickingTimeBomb(); tickingTimeBomb();
@@ -65,7 +70,7 @@ cannotTolerateExceptions() noexcept( true )
} }
~~~ ~~~
While some of the code and functions called in `cannotTolerateExceptions` is capable of While some of the code and functions called in `cannotTolerateExceptions` are capable of
throwing, the caller attempts to resolve exceptions and handle them without program throwing, the caller attempts to resolve exceptions and handle them without program
termination. However, the `tickingTimeBomb` function is a hidden hazard. While termination. However, the `tickingTimeBomb` function is a hidden hazard. While
it declares itself to not throw exceptions (which is true), the exceptions thrown it declares itself to not throw exceptions (which is true), the exceptions thrown
@@ -98,40 +103,40 @@ We propose the introduction of `static noexcept` as both a function declaration
"decorator" and an operator. `static noexcept( true )` or `static noexcept` "decorator" and an operator. `static noexcept( true )` or `static noexcept`
tagged functions have similar semantics to `noexcept` tagged or colored functions: tagged functions have similar semantics to `noexcept` tagged or colored functions:
- An evaluation of `noexcept( someStaticNoexceptFunction )` will evaluate as `true`. 1. An evaluation of `noexcept( someStaticNoexceptFunction )` will evaluate as `true`.
- An evaluation of `static noexcept( someStaticNoexceptFunction )` will evaluate as `true`. 2. An evaluation of `static noexcept( someStaticNoexceptFunction )` will evaluate as `true`.
- An evaluation of `static noexcept( someClassicNoexceptFunction )` will evaluate as `false`. 3. An evaluation of `static noexcept( someClassicNoexceptFunction )` will evaluate as `false`.
- A call to a `static noexcept( true )` function will not throw. 4. A call to a `static noexcept( true )` function will not throw.
- If an exception unwind (somehow) attempts to "emerge" from a 5. If an exception unwind (somehow) attempts to "emerge" from a
`static noexcept( true )` function, then the program will terminate. `static noexcept( true )` function, then the program will terminate.
However, we propose to add the following behaviors and restrictions to such functions: However, we propose to add the following behaviors and restrictions to such functions:
- If a `noexcept( false )` function is called outside of the body of a `try` block, 1. If a `noexcept( false )` function is called outside of the body of a `try` block,
then the program is ill formed, and a compile-time diagnostic is required. then the program is ill formed, and a compile-time diagnostic is required.
- If a `noexcept( false )` function is called inside the body of a `try` block, then 2. If a `noexcept( false )` function is called inside the body of a `try` block, then
a `catch( ... )` block must exist. a `catch( ... )` block must exist.
- A `catch` block's body is not considered part of a `try` block. 3. A `catch` block's body is not considered part of its `try` block.
- When checking if a statement is "in" a `try` block, one must walk up the nested 4. When checking if a statement is "in" a `try` block, one must walk up the nested
block structure until a try block is reached. A `catch` block attached to a block structure until a try block is reached. A `catch` block attached to a
`try` block which is inside of a try block at broader scope is considered to be `try` block which is inside of a try block at broader scope is considered to be
"inside" the broader try block, for the purpose of these rules. "inside" the broader try block, for the purpose of these rules.
- A simple model of this is that invoking `noexcept( false )` functions is ill formed 5. A simple model of this is that invoking `noexcept( false )` functions is ill formed
unless a `try` block scope can be found by walking outward from the calling scope. unless a `try` block scope can be found by walking outward from the calling scope.
- It is unclear at this time if language UB on expressions should be considered 6. It is unclear at this time if language UB on expressions should be considered
throwing or not. There are tradeoffs. throwing or not. There are tradeoffs.
- If UB is considered `noexcept( true )`, then there is a potential that UB 1. If UB is considered `noexcept( true )`, then there is a potential that UB
can cause program termination by "leaking" an exception out from checked can cause program termination by "leaking" an exception out from checked
try blocks. try blocks.
- If UB is considered `noexcept( false )`, then it makes writing 2. If UB is considered `noexcept( false )`, then it makes writing
`static noexcept( true )` extremely difficult. Many situations will `static noexcept( true )` extremely difficult. Many situations will
require noisy and defensive require noisy and defensive
`try { /* code */ } catch( ... ) { /* silently ignore exception and do nothing */ }` `try { /* code */ } catch( ... ) { /* silently ignore exception and do nothing */ }`
patterns. patterns.
The authors of this paper favor the interpretation that UB and language primitive behavior The authors of this paper favor the interpretation that despite UB, most language primitive
should be considered `noexcept( true )` for the purpose of this checking. There already behavior should be considered `noexcept( true )` for the purpose of this checking. There
exists the possibility of "deeper down" time bombs in this construct and language UB already exists the possibility of "deeper down" time bombs in this construct, and thus language UB
leading to exceptions is a somewhat niche case. leading to exceptions is a somewhat niche case.
#### Rewriting the original painful example using this feature #### Rewriting the original painful example using this feature
@@ -157,6 +162,10 @@ illFormedCannotTolerateExceptions() static noexcept( true )
try try
{ {
element= someVector.at( 42 ); element= someVector.at( 42 );
if( someVector.capacity() == someVector.size() )
{
someVector.reserve( someVector.size() * 2 );
}
} }
catch( ... ) catch( ... )
{ {
@@ -182,7 +191,9 @@ cannotTolerateExceptions() static noexcept( true )
auto element= someVector.at( 42 ); auto element= someVector.at( 42 );
// This line has to be moved into the try block to satisfy the `static noexcept` // This line has to be moved into the try block to satisfy the `static noexcept`
// requirements // requirements.
//
// As such the "somewhat defensive against exceptions" reserve can be eliminated.
someVector.push_back( std::move( element ) ); someVector.push_back( std::move( element ) );
} }
catch( ... ) catch( ... )
@@ -203,12 +214,12 @@ cannotTolerateExceptions() static noexcept( true )
----------------------- -----------------------
### Second new feature: A stronger (strawman) statically checked function - `false static noexcept( true )` ### Attempt at a second new feature: A stronger (strawman) statically checked function - `false static noexcept( true )`
Because the troublesome `tickingTimeBomb` function, which was marked `noexcept` still Because the troublesome `tickingTimeBomb` function, which was marked `noexcept` still
could be called, it's clear that `static noexcept` is insufficiently "sharp" as to catch could be called, it's clear that `static noexcept` is insufficiently "sharp" as to catch
all time bombs. Yet is still has utility as a tool to help carefully construct functions all time bombs. Yet is still has utility as a tool to help carefully construct functions
which are more resilient to unexpected termination. That form provides a comfortable middle which are more resilient to unexpected termination. That form (`static noexcept`) provides a comfortable middle
ground for many cases. This will become apparent when we introduce our stronger checking mechanisms, ground for many cases. This will become apparent when we introduce our stronger checking mechanisms,
`false static noexcept` and `double static noexcept`. It should be noted that `false static noexcept` and `double static noexcept`. It should be noted that
`false static noexcept` is a "straw man". It appears to be the correct solution, but it `false static noexcept` is a "straw man". It appears to be the correct solution, but it
@@ -217,38 +228,38 @@ solution helps expose and explore the problem space.
For the `false static noexcept` operator, we propose the following: For the `false static noexcept` operator, we propose the following:
- An evaluation of `noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`. 1. An evaluation of `noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`.
- An evaluation of `static noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`. 2. An evaluation of `static noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`.
- An evaluation of `false static noexcept( someClassicNoexceptFunction )` will evaluate as `false`. 3. An evaluation of `false static noexcept( someClassicNoexceptFunction )` will evaluate as `false`.
- An evaluation of `false static noexcept( someStaticNoexceptFunction )` will evaluate as `false`. 4. An evaluation of `false static noexcept( someStaticNoexceptFunction )` will evaluate as `false`.
- An evaluation of `false static noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`. 5. An evaluation of `false static noexcept( someFalseStaticNoexceptFunction )` will evaluate as `true`.
- A call to a `false static noexcept( true )` function will not throw. 6. A call to a `false static noexcept( true )` function will not throw.
- If an exception unwind (somehow) attempts to "emerge" from a 7. If an exception unwind (somehow) attempts to "emerge" from a
`false static noexcept( true )` function, then the program will terminate. `false static noexcept( true )` function, then the program will terminate.
However, we propose to add the following behaviors and restrictions to such functions: However, we propose to add the following behaviors and restrictions to such functions:
- If a `false static noexcept( false )` function is called outside of the body of a `try` block, 1. If a `false static noexcept( false )` function is called outside of the body of a `try` block,
then the program is ill formed, and a compile-time diagnostic is required. then the program is ill formed, and a compile-time diagnostic is required.
- If a `false static noexcept( false )` function is called inside the body of a `try` block, then 2. If a `false static noexcept( false )` function is called inside the body of a `try` block, then
a `catch( ... )` block must exist. a `catch( ... )` block must exist.
- A `catch` block's body is not considered part of a `try` block. 3. A `catch` block's body is not considered part of its `try` block.
- When checking if a statement is "in" a `try` block, one must walk up the nested 4. When checking if a statement is "in" a `try` block, one must walk up the nested
block structure until a try block is reached. A `catch` block attached to a block structure until a try block is reached. A `catch` block attached to a
`try` block which is inside of a try block at broader scope is considered to be `try` block which is inside of a try block at broader scope is considered to be
"inside" the broader try block, for the purpose of these rules. "inside" the broader try block, for the purpose of these rules.
- A simple model of this is that invoking `false static noexcept( false )` functions is ill formed 5. A simple model of this is that invoking `false static noexcept( false )` functions is ill formed
unless a `try` block scope can be found by walking outward from the calling scope. unless a `try` block scope can be found by walking outward from the calling scope.
- It is unclear at this time if language UB on expressions should be considered 6. It is unclear at this time if language UB on expressions should be considered
throwing or not. There are tradeoffs. throwing or not. There are tradeoffs.
- If UB is considered `noexcept( true )`, then there is a potential that UB 1. If UB is considered `noexcept( true )`, then there is a potential that UB
can cause program termination by "leaking" an exception out from checked can cause program termination by "leaking" an exception out from checked
try blocks. try blocks.
- If UB is considered `noexcept( false )`, then it makes writing 2. If UB is considered `noexcept( false )`, then it makes writing
`static noexcept( true )` extremely difficult. Many situations will `static noexcept( true )` extremely difficult. Many situations will
require noisy and defensive require noisy and defensive
`try { /* code */ } catch( ... ) { /* silently ignore exception and do nothing */ }` `try { /* code */ } catch( ... ) { /* silently ignore exception and do nothing */ }`
patterns. patterns.
The conclusion about whether language UB should be considered `false static noexcept( true )` The conclusion about whether language UB should be considered `false static noexcept( true )`
for the purpose of this checking is irrelevant, as this form exists merely for expository for the purpose of this checking is irrelevant, as this form exists merely for expository
@@ -267,46 +278,23 @@ try
{ {
if( not ( rand() % 32 ) ) if( not ( rand() % 32 ) )
{ {
silentlyThrowing(); silentlyThrowing(); // This gets handled in the catch below.
} }
if( not ( rand() % 32 ) ) if( not ( rand() % 32 ) )
{ {
// The rules for `false static noexcept` permit this function to be called,
// despite it being a program termination hazard.
wrappedSilentlyThrowing(); wrappedSilentlyThrowing();
} }
} }
catch( ... ) catch( ... )
{ {
// Handle unexpected exceptions.
} }
enum Result { Success, Failure }; enum Result { Success, Failure };
Result
illFormedCannotTolerateExceptions() false static noexcept( true )
{
try
{
auto element= someVector.at( 42 );
someVector.push_back( std::move( element ) );
}
catch( ... )
{
try
{
std::cerr << "Something went wrong" << std::endl;
}
catch( ... ) { /* Failure to log is troublesome but not fatal */ }
return Failure;
}
// This line is ill formed and has to be moved into the try block to satisfy the `false static noexcept`
// requirements
tickingTimeBomb();
return Success;
}
Result Result
cannotTolerateExceptions() false static noexcept( true ) cannotTolerateExceptions() false static noexcept( true )
{ {
@@ -315,8 +303,6 @@ cannotTolerateExceptions() false static noexcept( true )
auto element= someVector.at( 42 ); auto element= someVector.at( 42 );
someVector.push_back( std::move( element ) ); someVector.push_back( std::move( element ) );
tickingTimeBomb();
} }
catch( ... ) catch( ... )
{ {
@@ -328,11 +314,44 @@ cannotTolerateExceptions() false static noexcept( true )
return Failure; return Failure;
} }
// Despite the `false static noexcept` rules claiming that this is safe,
// it is still a ticking timebomb in its own right, and thus for this function, too.
//
// The key take-away here is that despite `false static noexcept`'s recursive enforcement
// rule, we still have termination leaks.
tickingTimeBomb();
return Success; return Success;
} }
~~~ ~~~
-----------------------
### A second new feature: A static no-termination guarantee -- `do not break if using this`
All of the major troubles we find with recursive exception safety guarantees bump into a limitation. It is
currently not possible in C++ to know whether a function has a termination hazard. We thus propose
a `do not break if using this` operator and function decorator.
#### A note on name choice
While `noterminate` is probably the most obvious name here, there are some concerns with it. Particularly that
`noterminate` implies that it would never terminate or call `std::terminate`.
#### The `do not break if using this` specification
We propose the introduction of `static noexcept` as both a function declaration
"decorator" and an operator. `static noexcept( true )` or `static noexcept`
tagged functions have similar semantics to `noexcept` tagged or colored functions:
1. An evaluation of `noexcept( someStaticNoexceptFunction )` will evaluate as `true`.
2. An evaluation of `static noexcept( someStaticNoexceptFunction )` will evaluate as `true`.
3. An evaluation of `static noexcept( someClassicNoexceptFunction )` will evaluate as `false`.
4. A call to a `static noexcept( true )` function will not throw.
5. If an exception unwind (somehow) attempts to "emerge" from a
`static noexcept( true )` function, then the program will terminate.