Date: Fri, 17 Oct 2025 15:53:48 +0200
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
>
> 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
Received on 2025-10-17 13:53:53
