C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Benchmarking contract evaluation semantics mode "assume" in Eigen.

From: David Brown <david.brown_at_[hidden]>
Date: Tue, 31 Mar 2026 18:35:06 +0200
On 31/03/2026 10:05, Jonathan Wakely wrote:
>
>
> On Mon, 30 Mar 2026, 12:07 David Brown, <david.brown_at_[hidden]
> <mailto:david.brown_at_[hidden]>> wrote:
>
>
> I don't really agree with the distinctions made in that paper. But
> perhaps that is just my background and the kind of programming I do.
> The standards must be general, and must err on the side of conservative
> choices. However, I think it is important to understand there are
> different viewpoints here, and different needs.
>
>
> Yes, and contracts and assumptions are two different features that suit
> those different needs.
>
>
> To me, it is a natural thing that a function has a pre-condition and a
> post-condition. The caller guarantees that the pre-condition is
> fulfilled. The function can assume that the pre-condition is
> fulfilled,
> and uses that to ensure the post-condition is fulfilled. The caller
> can
> then assume that the post-condition is fulfilled. That is the whole
> point of the process - it is what gives meaning to programming. A
> "function" is something that, given suitable inputs, produces suitable
> outputs. (To handle side-effects, we can pretend that "the world
> before
> the call" is an input and "the world after the call" is an output.
> That
> is not very practical for writing pre and post conditions in C++, but
> it's fine for talking about the theory.)
>
>
> You're describing contracts.
>
>
>
> I am hugely in favour of being able to have the language (compiler,
> run-time support, etc.) check for correctness at compile time, as much
> as practically possible. For run-time checks, a balance must always be
> found - it is good that it is easy for the programmer to enable checks,
> but also important that checks can be disabled easily. For a lot of
> C++
> code, efficiency is not particularly important - but for some code
> it is
> vital. So having compiler options (standardised where possible and
> practical) to modify these balances is a good idea. Even the best
> programmers make mistakes, and tools that help find these mistakes are
> always helpful.
>
>
> Still describing contracts.
>
>
> However, I feel that a lot of the discussions about contracts is
> putting
> things in the wrong place, and misunderstanding what such function
> specifications mean in different places. In particular, it is about
> responsibilities.
>
> The pre-condition of a function specifies the caller's responsibility.
> The p2064 paper says the function cannot assume the pre-condition is
> correct, because it is code written by someone else that determines if
> it holds. It is, IMHO, precisely because the code is written by
> someone
> else that the function author should be able to assume the pre-
> condition
> holds. It is not their job to get the function inputs correct.
>
>
> So why bother with contracts then? If the caller has to do it and the
> callee should not check, a comment works well enough. You don't need a
> language mechanism for checking.

No, a comment is not good enough. We all know that keeping comments and
code in sync is never the top priority for developers.

As I would have liked them, a precondition on a function declaration
would tell the programmer what kind of input they need to provide - in a
precise C++ expression. Compilers could enforce that as a check in the
calling code (since the precondition is in the declaration, and visible
to the caller). The function author can read the precondition and use
that knowledge about the inputs when writing the function implementation
- and the compiler can optimise using that knowledge. Of course the
precondition (and similarly, the postcondition) needs to be in C++ and
not a comment.

In addition, your compile could optionally generate checking code (in
various points) to aid testing and debugging.

>
>
> It is
> not the function author's job to hand-hold the caller, or figure out if
> the caller has done a good job or not. It is not the job of the hammer
> to determine if the user is likely to hit their thumb rather than the
> nail - or if the user is trying to hammer in a screw. The function
> author should be free to assume the pre-condition holds - likewise, the
> compiler optimiser can assume it holds true.
>
>
> So just assume bugs never happen, undefined behaviour never happens, and
> optimize based on that assumption. What could go wrong?
>
> I think we have enough experience with non-memory-safe language by now
> to know that isn't a great approach.
>

Hand-holding languages with lots of automatic checks have their place -
as do "trust the programmer" languages, and all levels in between. I
think it is fair to say that most code should be written with more
automatic checks than you normally have in C++. But is is equally wrong
to say that toolchains should put in all sorts of extra checks in many
different places. I'm okay with toolchains defaulting to more checks -
as long as those can be disabled by people who know what they are doing.
  Undefined behaviour is a sharp knife - it is a useful and flexible
tool, if you understand it and know how to use it carefully.

The worst thing for me is the kind of "all or nothing" approach to
checks with contracts. I want to write contracts where I can have
checks in the right place (caller for preconditions, callee for
postconditions), on the code that I want to check. I want to have
contracts on lots of functions, but not have unnecessary checks on code
that I know is correct - there I want improved optimisation. I want
safer code, clearer code, better debugging facilities, and more
efficient generated code - all of which could have been achieved by
contracts. (But I do appreciate the balances would be different for
different people and different uses.)


I think I would be a great deal happier if C++ contracts if the
evaluation semantics were more nuanced. At the moment, you can pick
from "ignored", "observed", "enforce" and "quick_enforce" - for a
compilation unit as a whole. I'd add "assume" to that list, and have
some way of marking contracts in groups with different semantics. If I
want to enable extra checking on a function I am checking in a unit, it
is wrong that this means crippling the efficiency of other functions in
the unit, or removing specifications on correct code just to avoid
unnecessary extra checks.

>
> On the caller side, it is the caller author's job to make sure the
> pre-condition is fulfilled. If it needs to be checked at run-time (and
> such checks can be vital in development and debugging, and often worth
> the cost even in final releases) then it should be done on the caller
> side. After all, if the pre-condition is not satisfied, it is the
> caller code that is wrong - not the function implementation.
>
>
> This is a valid point of view, but not how C++ contracts are defined.
> The checks might run on the caller side, or the callee side, or both.
>

Yes, that is how I understand it.

For API boundary functions, or certain other types of code (such as
perhaps security-critical code), it can make sense to check
pre-conditions inside a function. The distinction should be based on
the consequences of invalid inputs. If garbage in means garbage is
returned to the caller, then the caller should be left to deal with its
own mess. If invalid input can lead to security breaches, problems for
other code, etc., then it makes sense for the called function to check
inputs from untrusted sources.



>
>
> The inverse applies to the post-condition. The caller code can assume
> the post-condition is true (unless the caller has messed up and not
> satisfied the pre-condition). The function implementation is
> responsible for satisfying the post-condition, and therefore any checks
> should be done at that point.
>
> Getting this wrong is a waste of everyone's time. It is a waste of the
> developer's time, whether they are implementing the caller or the
> callee. It is a waste of run-time at both sides. It can ruin the
> analysability of code. Suppose you have this function :
>
> double square_root(double x)
> pre (x >= 0)
> post (y : abs(y * y - x) < 0.001);
>
> When treated correctly, this is a pure function. There are no
> side-effects. It is a complete function - it gives a correct result
> for
> any valid input. There are no exceptions. Implementations can be
> efficient, calls can be optimised (such as moving it around other code,
> eliminating duplicates, compile-time pre-calculation, etc.).
> Correctness analysis by tools or humans is straightforward, both for
> the
> function itself and for caller code. There is no undefined
> behaviour in
> the function - a call to "square_root(-1)" is undefined behaviour in
> the
> caller.
>
> But if the implementation cannot assume the pre-condition is true, this
> is all gone. At best, you now have UB in the function, because you
> have
> declared that it is possible to call the function with a negative
> input.
> At worst, the function implementation now comes with a check leading
> to a logging message, program termination, a thrown exception, or some
> other such effect. Now the function implementer has to think about how
> to handle incompetent callers.
>
>
> Why? If it's using contracts, which your declaration does, then that's
> all clearly handled via the contracts feature. The implementer doesn't
> have to recreate all of that handling.
>

The implementer /does/ have to deal with it all. Yes, the actual checks
are generated by the toolchain from the contracts. But the implementers
of both the calling code and the called code have to deal with the
consequences of the checks. The biggest burden is perhaps for the
calling code. Now it is no longer code that calls a calculation
function and continues to the next line - it is, depending on
compile-time options, code that might have additional side-effects like
logging or error messages, or might terminate or abort the program, or
kill it suddenly. Even when everything is good, the call can take
longer to execute, or use more stack space, or call other functions -
for some kinds of programming, that is not acceptable. And this is all
simply because the person writing the function declaration has tried to
be helpful by explicitly stating the specifications in a clear and
unambiguous format rather than a comment.



>
> Callers have to think about how the
> function interacts with other aspects of the code - the function may
> crash the program, or interact badly with threading.
>
> If the function implementer cannot trust code to call it correctly, and
> function callers cannot trust function implementers to code correctly,
> then the whole concept of programming falls apart.
>
>
> This is just hyperbole. The point of contracts (or manually checking
> with `assert` or similar checks) is to limit the damage when bugs like
> that happen. It allows the program to work without trusting that no bugs
> are present anywhere.
>

I am perhaps exaggerating.

I do appreciate the "damage limitation" argument - I am not against
appropriate checks in appropriate places.

But I don't see assertions or other tools (including checked contracts)
as a way to let programs "work" even if there are bugs - it merely gives
a bit more control of how they fail, and more information that can be
helpful in finding the bugs (this is, of course, a good thing).

>
> Every function
> becomes a paranoid code snippet that must double-check and triple-check
> everything, including the function calls used to check the function
> calls.
>
>
> You are again describing why contracts are useful.
>
>
>
>
> There are, of course, points in code where you do not trust others.
> You
> don't trust data coming in from outside. You don't trust caller inputs
> at API boundaries, at least for "major" functions or where the
> consequences of errors can be significant. But if you can't trust code
> internal code and function calls, everything falls apart.
>
>
> OK. So don't use contract checks at those internal points.
>

I would prefer if I could put specifications /everywhere/. If I am
writing an internal function "foo(int x)" and the value of "x" must be
between 0 and 3, then it should be an unequivocally good thing for me to
write "pre: (x >= 0) && (x <= 3)" - it should be better, in every way,
than writing "// x should be between 0 and 3". It should be checkable
(in the right place) if I want to check it, and assumed true for
optimisation purposes if I don't need to check it. And that choice
should not be dependent on the choices I make for checking other
specifications.

At the moment, I would use an assumption within the function definition
(using "if ((x < 0) || (x > 3)) std::unreachable();", wrapped in a
macro) and a comment at the declaration. But I would have much
preferred to have that in a "pre" clause at the function declaration.

>
> And if "pre" and "post", along with contract assertions, cannot be
> assumed to be satisfied (without optional checks to aid debugging),
> then
> they are IMHO pointless in almost all code.
>
>
> This is a ridiculous claim.
>
> C++ contracts are not just about debugging, they're also about being
> defensive to prevent undefined behaviour from bugs. Not every program
> can just be re-run in a debugger with identical inputs to find where a
> bug happened. And even if they can, if the bug already happened in
> production, it might be too late. Debugging after the fact doesn't
> prevent the UB from having already happened. Some people want to enable
> contracts in production to defend against bugs that haven't been tested
> for or haven't been envisioned during development.
>

Again, I am fine with that. Put appropriate checks in code, to limit
the spread of problems.

But with that kind of contracts, and that kind of checking, you cannot
then put specifications on functions that /don't/ need such checks.

Perhaps what I am trying to say (but maybe failing to express well) is
not that C++ contracts, as defined, are a bad thing. It is that they
are not suitable as a way of expressing specifications for functions as
I would like. Ideally (as I see it), we could have pre and post
condition specifications for all function declarations. What could be
better in a function declaration than a clear and unambiguous
specification of the function, in C++ syntax, stating the requirements
for the inputs and the outputs? That would be a big step towards the
mythical self-documenting code. But they are not a way to write
specifications for functions - they are a way to write specifications
for some additional run-time checks. They are not contracts - they do
not say "the caller agrees to do this, and the callee agrees to do
that". To my mind, they are a mixup - the syntax looks like
specifications, but the semantics are something else.


> And since the version of contracts we have in C++26 only allows them to
> be enabled globally or disabled globally, for the whole program, you
> don't get to choose which API boundaries have checks enabled and which
> don't. So you can't use contract checks as performance hints in your
> code without disabling checks at API boundaries between untrusted (or
> less trusted, or less tested) components.
>
> I would prefer to be able
> to add these freely in all sorts of code, even when I control both
> caller and callee - specifications written in C++ are preferable to
> specifications written as comments. I had hoped that C++26 contracts
> would let me write clearer code, have better static checking, have
> optional run-time checking on chosen functions while debugging,
>
>
> You have all that.
>
> and lead
> to more efficient generated code.
>
>
> That's not what they're for. This is a misalignment between what you
> want and what they do.
>

I realise that. (And again, C++ is a language for many people - if C++
contracts will help other people write better code, then that's great
regardless of how well they suit my personal needs and wants.)

>
> I had hoped it would lead to
> programmers being clearer about their responsibilities - focusing more
> on getting their own code right, rather than how they should deal with
> other people's mistakes.
>
>
> You do get that with contracts.
>
>
> C++ is, of course, a language designed for the needs of a huge
> number of
> programmers with a huge variety of needs and wants - any single
> developer is going to have things they like and things they dislike
> about it. But I do wonder if contracts, assertions and assumptions
> have
> hit the best balance here.
>
>
>
> We have separate features for checking contracts at API boundaries and
> for stating assumptions that the compiler cannot verify by itself. That
> seems like exactly the right balance, because they're separate things.
> So separate features makes sense.
>

That is fair enough, as far as it goes.

But to my mind, it is somewhat in the wrong places.

In a function declaration, a "precondition" clause should (this is all
IMHO) tell the caller what is required. It tells the caller programmer
what they need to do, and can let the toolchain optionally add checks at
the call-site to confirm it. In the function definition, the norm would
be that the function can assume it is true - it does not need to verify
it. But for code that needs extra paranoia (like API boundaries, or
security-critical code), the programmer could ask the toolchain to
insert checks on the preconditions (like an assert).

Of course code could freely include both assumptions and (optionally
checked) assertions anywhere in the code where it makes sense.

> It has been shown repeatedly that turning all assertions into
> assumptions *decreases* performance in many (or even most) cases.
>
> You get better performance by carefully profiling and adding assumptions
> only where it really helps, not by overloading the compiler with too
> many branch predictions. That is orthogonal to contract checking, which
> is about correctness and verification, not performance.
>

My experience is that well-written assumptions help code efficiency.
But I will happily accept that the way I develop code is unusual - I
check the generated assembly a lot more than the average C++ programmer.

I would have preferred it if I could have put my assumptions in "pre"
and "post" clauses in declarations, for the benefits of caller and
callee. As well as possible efficiency gains, it would also improve
correctness checks (since the caller could check them). But C++26
contracts are not going to give me everything I am looking for here -
and if they give other people something they are looking for, fair enough.

On the other hand, reflection looks like it could be a lot of fun, so
there is still a lot to look forward to in C++26 :-)

Received on 2026-03-31 16:35:22