This paper explains why the observe and ignore semantics are useful in constant evaluation, i.e. for contract assertions that are evaluated at translation time.
So, we start out with a function:
int f(int x) { if (x < 0) // defensive check return -1; return x * 2; // a totally concocted example definition, in reality much more complex }
Okay, now we add a contract
int f(int x) pre(x >= 0) { if (x < 0) // defensive check return -1; return x * 2; // a totally concocted example definition, in reality much more complex }
Not all callers have necessarily accommodated the contract, and the defensive check is still there. So callers that call with negative numbers work fine. To find them, you evaluate the pre with the 'observe' semantic. Incorrect callers are incorrect, but an incorrect call is perfectly recoverable.
Make it constexpr,
constexpr int f(int x) pre(x >= 0) { if (x < 0) // defensive check return -1; return x * 2; // a totally concocted example definition, in reality much more complex }
and nothing changes. Incorrect calls are recoverable. Evaluate the pre with the 'observe' semantic, and your program compiles fine. The defensive check protects the innocent.
And then, if you don't want any diagnostics, you compile with the 'ignore' semantic, and you don't even get a warning. Quite like you wouldn't get any violation handler call with a run-time check.
Yes, to use such an approach effectively, you eventually want a label that can mark a contract as "ignore or observe, but not enforce". But it can be used without labels already.