Date: Fri, 17 Oct 2025 07:23:09 -0700
> Today's
> void fun(Foo* ptr) {
> my_supper_assert_macro (ptr!=nullpter);
> my_supper_assert_macro(ptr->hasData());
> }
> should not have any problems, ever
No, that’s not my understanding: Any assertion facility that offers log-and-continue (or similar “observe”) semantics will have the same issue.
Many assertion libraries do offer that, don’t they? I did a quick Google search (errors are mine):
* It seems that BOOST_CHECK and BOOST_TEST both continue, and so would have the same issue with this example.
* Various assertion frameworks support a custom violation handler that could return instead of terminate/abort. If they return and do not abort, those would have the same issue again.
It’s true that C assert() specifically doesn’t have that problem, but that’s only because it’s not powerful enough – it only offers “ignore” and “quick_enforce.” We do need observe semantics (we’ve already added it in our assertion libraries when C assert wasn’t enough), and as far as I can see any assertion facility with that feature has to have this characteristic for this example. So it seems to me this is a feature, not a bug.
From: Harald Achitz <harald_at_[hidden]>
Sent: Friday, October 17, 2025 6:54 AM
To: Herb Sutter <herb.sutter_at_[hidden]>; sg15_at_[hidden]; 'Ville Voutilainen' <ville.voutilainen_at_[hidden]>; sg21_at_[hidden]
Cc: 'Tom Honermann' <tom_at_[hidden]>
Subject: Re: [isocpp-sg15] [isocpp-sg21] P3835 -- Different contract checking for different libraries
On 2025-10-17 15:28, Herb Sutter wrote:
Harald asked:
> On the internet I saw someone saying
>
> void fun(Foo* ptr) pre (ptr!=nullpter), pre(ptr->hasData()) { ... }
>
> might be a problem (for the second pre) and should be written like this
>
> void fun(Foo* ptr) pre (ptr!=nullpter && ptr->hasData()){ ... }
>
> is that true?
With ignore, enforce, or quick_enforce there’s no issue. The second check will not be evaluated, no UB.
With observe semantics (only), if the pointer is null then:
1. the first check fails, and gives a contract violation so you know it was violated
2. the second check then gives UB (no time travel, does not affect the first check that already happened)
IMO the issue isn’t about contracts, it’s about short-circuit evaluation. This kind of issue can come up generally in the language if you split the (p) and (p->foo()) conditions, so we already teach that to get short-circuit evaluation you need to write the && using (p && p->foo()).
This also comes up (again, generally throughout the language) with other examples like
(is_sorted(v) && binary_search(v, x))
because implementations of binary_search might try to perform unsafe out-of-bounds access if the range is not already sorted.
In case it's helpful, here's the current slide I teach about that example (updated and extended since CppCon):
Herb
Thank you.
Today's
void fun(Foo* ptr) {
my_supper_assert_macro (ptr!=nullpter);
my_supper_assert_macro(ptr->hasData());
}
should not have any problems, ever
so replacing this blindly against
void fun(Foo* ptr) {
contract_assert (ptr!=nullpter);
contract_assert(ptr->hasData());
}
does not work, as I understand, since observe could be a problem?
(I know now that when using pre I have to re-write with &&)
/Harald
> void fun(Foo* ptr) {
> my_supper_assert_macro (ptr!=nullpter);
> my_supper_assert_macro(ptr->hasData());
> }
> should not have any problems, ever
No, that’s not my understanding: Any assertion facility that offers log-and-continue (or similar “observe”) semantics will have the same issue.
Many assertion libraries do offer that, don’t they? I did a quick Google search (errors are mine):
* It seems that BOOST_CHECK and BOOST_TEST both continue, and so would have the same issue with this example.
* Various assertion frameworks support a custom violation handler that could return instead of terminate/abort. If they return and do not abort, those would have the same issue again.
It’s true that C assert() specifically doesn’t have that problem, but that’s only because it’s not powerful enough – it only offers “ignore” and “quick_enforce.” We do need observe semantics (we’ve already added it in our assertion libraries when C assert wasn’t enough), and as far as I can see any assertion facility with that feature has to have this characteristic for this example. So it seems to me this is a feature, not a bug.
From: Harald Achitz <harald_at_[hidden]>
Sent: Friday, October 17, 2025 6:54 AM
To: Herb Sutter <herb.sutter_at_[hidden]>; sg15_at_[hidden]; 'Ville Voutilainen' <ville.voutilainen_at_[hidden]>; sg21_at_[hidden]
Cc: 'Tom Honermann' <tom_at_[hidden]>
Subject: Re: [isocpp-sg15] [isocpp-sg21] P3835 -- Different contract checking for different libraries
On 2025-10-17 15:28, Herb Sutter wrote:
Harald asked:
> On the internet I saw someone saying
>
> void fun(Foo* ptr) pre (ptr!=nullpter), pre(ptr->hasData()) { ... }
>
> might be a problem (for the second pre) and should be written like this
>
> void fun(Foo* ptr) pre (ptr!=nullpter && ptr->hasData()){ ... }
>
> is that true?
With ignore, enforce, or quick_enforce there’s no issue. The second check will not be evaluated, no UB.
With observe semantics (only), if the pointer is null then:
1. the first check fails, and gives a contract violation so you know it was violated
2. the second check then gives UB (no time travel, does not affect the first check that already happened)
IMO the issue isn’t about contracts, it’s about short-circuit evaluation. This kind of issue can come up generally in the language if you split the (p) and (p->foo()) conditions, so we already teach that to get short-circuit evaluation you need to write the && using (p && p->foo()).
This also comes up (again, generally throughout the language) with other examples like
(is_sorted(v) && binary_search(v, x))
because implementations of binary_search might try to perform unsafe out-of-bounds access if the range is not already sorted.
In case it's helpful, here's the current slide I teach about that example (updated and extended since CppCon):
Herb
Thank you.
Today's
void fun(Foo* ptr) {
my_supper_assert_macro (ptr!=nullpter);
my_supper_assert_macro(ptr->hasData());
}
should not have any problems, ever
so replacing this blindly against
void fun(Foo* ptr) {
contract_assert (ptr!=nullpter);
contract_assert(ptr->hasData());
}
does not work, as I understand, since observe could be a problem?
(I know now that when using pre I have to re-write with &&)
/Harald
Received on 2025-10-17 14:23:12
