> 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’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@swedencpp.se>
Sent: Friday, October 17, 2025 6:54 AM
To: Herb Sutter <herb.sutter@gmail.com>; sg15@lists.isocpp.org; 'Ville Voutilainen' <ville.voutilainen@gmail.com>; sg21@lists.isocpp.org
Cc: 'Tom Honermann' <tom@honermann.net>
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:
- the first check fails, and gives a contract violation so you know it was violated
- 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