Date: Mon, 20 Oct 2025 13:34:08 +0200
Some thoughts on the topic of "contract assertions should be always-checked or always-enforced / the semantic should be represented in code".
The fact that by default, the evaluation semantic of a C++26 contract assertion is not represented in code is not a design flaw, it's a feature, and more than that: it's the central idea that C++26 contracts have been designed around. See P2900R14 <https://wg21.link/p2900r14>, Section 3.1.3: "While a contract assertion provides an algorithm to validate correctness, nothing about a contract assertion guarantees any particular runtime behavior associated with that syntactic construct." This is not something you can "fix" — changing it would mean designing an entirely different feature for an entirely different purpose.
This central idea of "Write the assertion in code, then choose the semantic at build time" is how C assert and similar assertion facilities have always worked. Pretty much every C and C++ codebase I have worked on in my professional career used such build-time-switchable assertion macros, and I am sure many of you are using them too. I hope it is not being questioned here that assertions designed in that way are widely used, and have been for decades, work exactly the way they are intended to work, and can be used effectively today to find bugs in your code.
This is also how every single C++ Contracts proposal for the last 21 years has worked — including C++20 Contracts (P0542): that proposal had an indirection in the form of "assertion levels" expressed in code, and there was this other thing called "axiom", but fundamentally the choice whether a checkable assertion will actually be checked was a build-time one, not an in-code one.
Now, let me formulate a hypothesis. I can't know for sure, but I highly suspect that none of the current controversy would have happened if we had appropriately named the C++26 feature now under discussion simply "assertions", or maybe "standard assertions", or something similar — and would have avoided the word "Contracts" entirely. Then people would have said: "oh cool, so you're taking C assert and make it not a macro, a bit more configurable, and allow people to stick them on declarations so the human/compiler/IDE/static analysis tool can find them more easily (which is exactly what we are doing — no more no less)? Cool, that's a win — let's add it to C++26 and move on!"
However, because we did use the word "Contracts" people have expectations about what a feature with that word in the name should be, or do, and then are disappointed that those expectations aren't satisfied, and some individuals are now opposed to the entire feature because of that.
Now, it's really important that it *is* valid to say that C++26 contract assertions are not what you need, don't satisfy your safety requirements, etc. and that you'd rather have a feature that behaves in a different way (such as always on / always enforced). People who say that are not wrong. However, it is important to recognise that checks you can turn on and off at build time and checks that are always on / always enforced are VERY different features that are used in very different ways and for different use cases, and have very different tradeoffs, deployment scenarios, and adoption trajectories. Josh and I did our best to explain this in <https://wg21.link/p3846r0>P3846R0 <https://wg21.link/p3846>, Concern 1.
Given the above, it seems to me like opposing C++26 contract assertions because you want that checks are always on / always enforced is kinda like this:
– Alice: "I want safer roads for pedestrians." (reasonable and good request)
– Bob: "Here's a proposal to fund bike lanes in the city." (reasonable and good proposal roughly in the same area but with a different goal)
– Alice: "But bike lanes don't add more crosswalks or reduce speed limits. So they don't make roads safer for pedestrians. Therefore, we should not build bike lanes."
Here, Alice is committing a logical fallacy. Just because bike lanes are not useful for Alice, it doesn't mean that they're not useful for Bob, and taking away bike lanes from Bob does nothing to give Alice what she wants.
The case is similar here. C++26 have been designed to do thing X (and I happen to be of the opinion that they have been designed extremely well for thing X) but if they do not give you what you want because you need thing Y instead, then that's OK — just wait for C++29 where you will get an extension that does thing Y. Meanwhile, you are not being significantly harmed by other people who have different use cases/requirements than you now being able to do thing X. On the other hand, if we remove C++26 contract assertions now, nobody gets anything — it's a lose-lose scenario.
Now, of course, there is one aspect in all this controversy that makes the above not quite true, and is worth discussing now: what should the default syntax do? While P3400 labels (proposed C++29 extension) will let pretty much everyone express exactly what they want, in code, one important concern seems to be that once we standardise C++26 contracts then the default syntax — pre(x), post(x), and contract_assert(x), without any labels — will forever mean "choose the semantic at build time" and can never mean "always checked" or "always enforced". Ville is correct in pointing out that this is indeed a consequence of the proposal, and we should make sure that this is really what we want before we standardise it (or reverse course if it is not what we want). Maybe it would be helpful to focus this discussion on exactly that: are we OK with the default syntax meaning what it means today? (because, again, extra syntax can give people everything they want in C++29, so the default syntax really seems like the only pressing concern for C++26).
So, should the default syntax be "choose the semantic at build time" or "always enforced"? I think it's helpful again to look at P3846R0 <https://wg21.link/p3846r0> Concern 1 as it explains the differences and meta-differences between those two features.
The real question, however, is as follows. Regardless of what your take is on how assertions should work, the C++26 contracts syntax offers a number of specific benefits compared to what we have in C++ today — in particular it lets you put assertions on declarations with `pre` and `post`. For which of these two features — "choose the semantic at build time" or "always enforced" — would the C++ ecosystem as a whole benefit more if those new benefits would become usable for that feature? Both features are available today: "choose the semantic at build time" is "assertion macro" and "always enforced" is "if (!condition) std::abort()". For which of these two existing pieces of functionality would the overall benefit to the world be greater if you could now stick it onto a declaration and have a standard, toolable syntax for it?
Here it could also be helpful to think about which of these two features you have yourself seen used at a wider scale and/or with greater overall benefit, and which of these two features is held back more by lack of direct language support. I can't speak for other people and I think it's entirely possible that the situation is very different in industries I have not worked in, but from my personal career experience (scientific computing / numerical physics simulations, then audio & music software, generic reusable libraries/frameworks, IDEs and developer tools, and now finance) the "assertion macro" feature seems much more widely used in all those areas than the unconditional "if (!condition) std::abort()" feature, and the benefit in making it work even better seems significantly larger to me.
Of course, so far this is all entirely subjective. It would be amazing to get actual hard data on this that could inform our opinion. However, I feel like it's a bit unfair to put the onus for producing such data on the authors of C++26 contract assertions. It makes more sense to me to put that onus on the people who seek to overturn the very strong consensus (100 in favour, 14 against) that we had in Hagenberg for including contract assertions as designed in C++26. Overturning such strong consensus should normally require some significant new information, which I am not seeing in the current discussion.
I hope that these thoughts are at least a little bit helpful.
Cheers,
Timur
> On 20 Oct 2025, at 10:51, Ville Voutilainen via SG21 <sg21_at_[hidden]> wrote:
>
> On Mon, 20 Oct 2025 at 08:23, Herb Sutter <herb.sutter_at_[hidden]> wrote:
>>
>>> It certainly sounds very similar to some concerns multiple WG21 members
>>> have about P2900
>>
>> No.
>
> Oh, yes. They are saying exactly what you are saying, just that they
> will have to ban using the P2900 contracts.
>
>> Except, and this really is crucial, you are talking about making the _default_ syntax unconditionally terminate if violated, with _no_ opt-out or configuration. That's the combination that makes it way too easy to write by mistake, and impossible to mechanically review and audit afterwards -- because the default syntax can't ever express explicit intent, it's the whitespace default after all. And for something like unconditional termination, we (at least, at my company) absolutely need the source code to express such a serious intent.
>>
>> Consider if we had a HandGrenade type with two constructors:
>>
>> HandGrenade g(pin_pulled); // tick tick tick tick tick
>> HandGrenade g(pin_not_pulled); // safe
>>
>> Now consider, if we chose to support default construction, which corresponds to a default syntax:
>>
>> HandGrenade g; // which of the above would obviously be the wrong default?
>
> To truly apply your analogy in this wonderful and
> not-at-all-scaremongering Hand Grenade example, the P2900 answer
> is that that syntax may mean the pin is pulled, it may mean the pin
> isn't pulled, and you don't know that just by looking
> at the grenade. Similar-looking grenades may behave differently in
> different parts of your program. Similar-looking
> grenades may end up having their pin pulled by another piece of your
> program using a pulled-pin grenade despite
> your attempts to make sure your locally-visible grenades have their
> pin "safely" in place.
>
> So pretty please spare me from these analogies. They don't even
> communicate what you think they communicate.
> _______________________________________________
> SG21 mailing list
> SG21_at_[hidden]
> Subscription: https://lists.isocpp.org/mailman/listinfo.cgi/sg21
> Link to this post: http://lists.isocpp.org/sg21/2025/10/11417.php
The fact that by default, the evaluation semantic of a C++26 contract assertion is not represented in code is not a design flaw, it's a feature, and more than that: it's the central idea that C++26 contracts have been designed around. See P2900R14 <https://wg21.link/p2900r14>, Section 3.1.3: "While a contract assertion provides an algorithm to validate correctness, nothing about a contract assertion guarantees any particular runtime behavior associated with that syntactic construct." This is not something you can "fix" — changing it would mean designing an entirely different feature for an entirely different purpose.
This central idea of "Write the assertion in code, then choose the semantic at build time" is how C assert and similar assertion facilities have always worked. Pretty much every C and C++ codebase I have worked on in my professional career used such build-time-switchable assertion macros, and I am sure many of you are using them too. I hope it is not being questioned here that assertions designed in that way are widely used, and have been for decades, work exactly the way they are intended to work, and can be used effectively today to find bugs in your code.
This is also how every single C++ Contracts proposal for the last 21 years has worked — including C++20 Contracts (P0542): that proposal had an indirection in the form of "assertion levels" expressed in code, and there was this other thing called "axiom", but fundamentally the choice whether a checkable assertion will actually be checked was a build-time one, not an in-code one.
Now, let me formulate a hypothesis. I can't know for sure, but I highly suspect that none of the current controversy would have happened if we had appropriately named the C++26 feature now under discussion simply "assertions", or maybe "standard assertions", or something similar — and would have avoided the word "Contracts" entirely. Then people would have said: "oh cool, so you're taking C assert and make it not a macro, a bit more configurable, and allow people to stick them on declarations so the human/compiler/IDE/static analysis tool can find them more easily (which is exactly what we are doing — no more no less)? Cool, that's a win — let's add it to C++26 and move on!"
However, because we did use the word "Contracts" people have expectations about what a feature with that word in the name should be, or do, and then are disappointed that those expectations aren't satisfied, and some individuals are now opposed to the entire feature because of that.
Now, it's really important that it *is* valid to say that C++26 contract assertions are not what you need, don't satisfy your safety requirements, etc. and that you'd rather have a feature that behaves in a different way (such as always on / always enforced). People who say that are not wrong. However, it is important to recognise that checks you can turn on and off at build time and checks that are always on / always enforced are VERY different features that are used in very different ways and for different use cases, and have very different tradeoffs, deployment scenarios, and adoption trajectories. Josh and I did our best to explain this in <https://wg21.link/p3846r0>P3846R0 <https://wg21.link/p3846>, Concern 1.
Given the above, it seems to me like opposing C++26 contract assertions because you want that checks are always on / always enforced is kinda like this:
– Alice: "I want safer roads for pedestrians." (reasonable and good request)
– Bob: "Here's a proposal to fund bike lanes in the city." (reasonable and good proposal roughly in the same area but with a different goal)
– Alice: "But bike lanes don't add more crosswalks or reduce speed limits. So they don't make roads safer for pedestrians. Therefore, we should not build bike lanes."
Here, Alice is committing a logical fallacy. Just because bike lanes are not useful for Alice, it doesn't mean that they're not useful for Bob, and taking away bike lanes from Bob does nothing to give Alice what she wants.
The case is similar here. C++26 have been designed to do thing X (and I happen to be of the opinion that they have been designed extremely well for thing X) but if they do not give you what you want because you need thing Y instead, then that's OK — just wait for C++29 where you will get an extension that does thing Y. Meanwhile, you are not being significantly harmed by other people who have different use cases/requirements than you now being able to do thing X. On the other hand, if we remove C++26 contract assertions now, nobody gets anything — it's a lose-lose scenario.
Now, of course, there is one aspect in all this controversy that makes the above not quite true, and is worth discussing now: what should the default syntax do? While P3400 labels (proposed C++29 extension) will let pretty much everyone express exactly what they want, in code, one important concern seems to be that once we standardise C++26 contracts then the default syntax — pre(x), post(x), and contract_assert(x), without any labels — will forever mean "choose the semantic at build time" and can never mean "always checked" or "always enforced". Ville is correct in pointing out that this is indeed a consequence of the proposal, and we should make sure that this is really what we want before we standardise it (or reverse course if it is not what we want). Maybe it would be helpful to focus this discussion on exactly that: are we OK with the default syntax meaning what it means today? (because, again, extra syntax can give people everything they want in C++29, so the default syntax really seems like the only pressing concern for C++26).
So, should the default syntax be "choose the semantic at build time" or "always enforced"? I think it's helpful again to look at P3846R0 <https://wg21.link/p3846r0> Concern 1 as it explains the differences and meta-differences between those two features.
The real question, however, is as follows. Regardless of what your take is on how assertions should work, the C++26 contracts syntax offers a number of specific benefits compared to what we have in C++ today — in particular it lets you put assertions on declarations with `pre` and `post`. For which of these two features — "choose the semantic at build time" or "always enforced" — would the C++ ecosystem as a whole benefit more if those new benefits would become usable for that feature? Both features are available today: "choose the semantic at build time" is "assertion macro" and "always enforced" is "if (!condition) std::abort()". For which of these two existing pieces of functionality would the overall benefit to the world be greater if you could now stick it onto a declaration and have a standard, toolable syntax for it?
Here it could also be helpful to think about which of these two features you have yourself seen used at a wider scale and/or with greater overall benefit, and which of these two features is held back more by lack of direct language support. I can't speak for other people and I think it's entirely possible that the situation is very different in industries I have not worked in, but from my personal career experience (scientific computing / numerical physics simulations, then audio & music software, generic reusable libraries/frameworks, IDEs and developer tools, and now finance) the "assertion macro" feature seems much more widely used in all those areas than the unconditional "if (!condition) std::abort()" feature, and the benefit in making it work even better seems significantly larger to me.
Of course, so far this is all entirely subjective. It would be amazing to get actual hard data on this that could inform our opinion. However, I feel like it's a bit unfair to put the onus for producing such data on the authors of C++26 contract assertions. It makes more sense to me to put that onus on the people who seek to overturn the very strong consensus (100 in favour, 14 against) that we had in Hagenberg for including contract assertions as designed in C++26. Overturning such strong consensus should normally require some significant new information, which I am not seeing in the current discussion.
I hope that these thoughts are at least a little bit helpful.
Cheers,
Timur
> On 20 Oct 2025, at 10:51, Ville Voutilainen via SG21 <sg21_at_[hidden]> wrote:
>
> On Mon, 20 Oct 2025 at 08:23, Herb Sutter <herb.sutter_at_[hidden]> wrote:
>>
>>> It certainly sounds very similar to some concerns multiple WG21 members
>>> have about P2900
>>
>> No.
>
> Oh, yes. They are saying exactly what you are saying, just that they
> will have to ban using the P2900 contracts.
>
>> Except, and this really is crucial, you are talking about making the _default_ syntax unconditionally terminate if violated, with _no_ opt-out or configuration. That's the combination that makes it way too easy to write by mistake, and impossible to mechanically review and audit afterwards -- because the default syntax can't ever express explicit intent, it's the whitespace default after all. And for something like unconditional termination, we (at least, at my company) absolutely need the source code to express such a serious intent.
>>
>> Consider if we had a HandGrenade type with two constructors:
>>
>> HandGrenade g(pin_pulled); // tick tick tick tick tick
>> HandGrenade g(pin_not_pulled); // safe
>>
>> Now consider, if we chose to support default construction, which corresponds to a default syntax:
>>
>> HandGrenade g; // which of the above would obviously be the wrong default?
>
> To truly apply your analogy in this wonderful and
> not-at-all-scaremongering Hand Grenade example, the P2900 answer
> is that that syntax may mean the pin is pulled, it may mean the pin
> isn't pulled, and you don't know that just by looking
> at the grenade. Similar-looking grenades may behave differently in
> different parts of your program. Similar-looking
> grenades may end up having their pin pulled by another piece of your
> program using a pulled-pin grenade despite
> your attempts to make sure your locally-visible grenades have their
> pin "safely" in place.
>
> So pretty please spare me from these analogies. They don't even
> communicate what you think they communicate.
> _______________________________________________
> SG21 mailing list
> SG21_at_[hidden]
> Subscription: https://lists.isocpp.org/mailman/listinfo.cgi/sg21
> Link to this post: http://lists.isocpp.org/sg21/2025/10/11417.php
Received on 2025-10-20 11:34:15
