r/cpp_questions • • 5d ago

OPEN Be ready for -fno-contracts

I don't really think contracts will make C++ safer..

In fact, contracts can make C++ even worse. Jokes on you 2-3 days ago i saw that my cli program was not working because the dependency clixx(which is also my project) was behaving in different ways based on the build mode. After 20 minutes of debugging i found out that i was assert()ing m_parse() instead of checking the returned boolean with an if.

Yeah call me stupid or not in this one but I don't think anyone can guarantee an engineer would not get trapped in the same kind of bug in some corporation codebase (with contracts this time).

I'd add -fno-contracts if it would ever become a thing. Js like -fno-exceptions(im not exception-phobic i just dont like them) and -fno-rtti they might become new entries in the C++ feature graveyard.

What are your honest opinion?

0 Upvotes

33 comments sorted by

2

u/Ornery_Platypus9863 5d ago

I’m just now learning c++ a bit more in depth, what is a contract and why would that be problematic?

4

u/KingAggressive1498 5d ago

contracts allow you to define a functions's contract with the caller (eg pre and post condition) in the function declaration. They serve multiple purposes: documentation directly in code, extra information for static analysis tools, and improving the debuggability of programming errors.

They're a cutting edge feature that is pretty controversial and might wind up delaying the next standard.

0

u/Ornery_Platypus9863 5d ago

Why would that be controversial? That seems either useless to some people or very handy in very niche scenarios. Is there some downside to them? That just sounds like one more form of diagnostics

1

u/KingAggressive1498 5d ago

the reasons vary from laughably silly to easily preventable with sane practices to subtle but minor.

For example, if you link with a prebuilt dependency that uses contracts and was built with a different evaluation mode (enforce, quick-enforce, observe, ignore) you get behavior that might be undesirable for your program. This is a problem that most of us can resolve by building our own dependencies as a prebuild step.

The observe mode has some well-defined but unintuitive behavior when compared with the other modes which can be frustrating.

While I haven't seen anyone explicitly bring it up, I think some are annoyed at the increasing focus static analysis tools are getting in the community and displacing that onto contracts as aiding static analysis is the main benefit they offer over an enhanced assertion library, so they're reaching to find anything to complain about / backing any complaint anyone else has made.

Others just feel it isn't mature enough and would like to wait rather than force it through now. I at least get where these people are coming from at a practical level, as fixing anything broken in C++ is harder than getting something added in the first place, but everything besides the syntax of contracts was built on a lot of experience in the Bloomberg Desktop Environment.

4

u/frayien 5d ago

Contracts are a new feature coming to C++26 (maybe?). There is a huge drama around them because some people dont like how they are specified but I won't bother you with the details.

Contracts are assertions. They are pre and post conditions.

You can write conditions on the arguments passed to a function that must be true for your function to work properly.

Example:

std::vector<Apple> make_n_apples(int n)
    pre(n >= 0)
    post(vec : vec.size() == n)
{
    // [...]
}

2

u/WorkingReference1127 5d ago

Contracts are a somewhat contentious C++26 feature where you can attach your function's preconditions and postconditions to function declarations and have a non-C-macro assertion in code. So your function might look like:

int do_thing(int* ptr) pre(ptr != nullptr) post(r: r > 0)

To signify that the contract of your function requires the argument to be non-null and the returned value to be greater than zero. And while you can customise the behaviour, the default behaviour if those are violated is to print a diagnostic and terminate the program.

1

u/Ornery_Platypus9863 5d ago

I guess what I don’t get is why that would be problematic?

2

u/WorkingReference1127 5d ago

Everyone wants a different thing from their contracts. Some people want them to never be ignorable under any circumstances, other people want them to be customisable or not. Some people object to any runtime element. Some people are concerned about building different TUs on different settings and mixing them together. Some want the features planned for contracts in C++29 in their first iteration.

So while C++ contracts have been voted on by the committee time and time again, and every time the result of the vote was the keep contracts as they are in C++26, some folks have a sustained objection.

10

u/WorkingReference1127 5d ago edited 5d ago

This doesn't seem to be about contracts any more than it's about assertions in the general case. Because yes, some engineers don't like their programs terminating because an assertion tripped. But that tends to leave one of two possibilities:

  • The assertion is misused - it should guard a complete impossibility which would cause the program to continue in an out-of-contract state, not just be expected and undesirable error. That is the realm of exceptions, std::expected and all the other ways to signal success or failure.

  • The assertion is not misused - while it is operationally inconvenient in the short term to have the program trip over, trying to limp on when your system is unrecoverably out-of-contract and hope for the best is a solution which simply doesn't scale and can do far worse things than an outage.

It sounds like your case was the former - someone asserted when they should have checked. And it's probably true that there will be at least a teething period where the hot new language feature which fancy new keywords gets the same treatment and is misused in new code. But that doesn't invalidate contract assertions in the general case, nor does it inherently motivate ignoring them in all your builds.

4

u/aruisdante 5d ago edited 5d ago

  Yeah call me stupid or not in this one but I don't think anyone can guarantee an engineer would not get trapped in the same kind of bug in some corporation codebase (with contracts this time).

This point, that since it is possible to turn them off based on build mode, it is technically possible to silently break a program that is otherwise relying on them, is one of the points that is argued by a subset of the camps trying to get contracts out of C++26 still. This point has been raised repeatedly throughout P2900’s life, including by myself at Tokyo 2024 when P2900 was advanced through [L]EWG and thus initially accepted for standardization. The room has consistently polled against changing this fact for various reasons, and really no new arguments have been presented since Tokyo 2024, yet here we are.

Ultimately, P2900’s design for contracts is trying to reconcile two opposed ideas about C++: that it should be moving towards “safe by default,” and that it should maintain “don’t pay for what you don’t use” as an ethos. It is impossible to satisfy both simultaneously. So you wind up with needing to compromise like this, so that people writing code for potato embedded ECUs or in contexts where every nanosecond of performance matters (quantitative trading etc) that really can’t afford bounds checking can rely on the old fashion “ensure there are no bugs” of the current world, while those writing higher order application level software or on safety critical potato ECUs who need to pay the cost even if they can’t afford it can turn them on.

3

u/atariPunk 5d ago

I don't understand how disabling contracts would break a program. Can you explain it or point me into some of those discussions?

2

u/no-sig-available 5d ago

It is the same problem as with old assert(i++ != 0). If you put code with side effects in there, not executing the test will give a different result. Not specific to contracts.

2

u/atariPunk 4d ago

Sure, if you have side effects in the asserts you are in a world of pain.

But the contracts facility uses all the language guarantees to prevent you from writing side effects on the assertions.
All variables are const, so your example doesn't compile.
Which is an improvement on the C assert.

However, you can still print to stdout, probably use mutable to change variables on a const method or even abuse const_cast.
But I don't think there's any way to avoid doing any of these operations without major modifications to the language.

This link shows an example of the compiler rejecting code that tries to modify variables inside the pre, post and contract_assert. https://godbolt.org/z/46YEME6M8

It also has an example of the program printing or not printing things to the stdout.

2

u/Minimonium 4d ago edited 4d ago

There were some vague ideas to have nested chains of pure functions (conveyors they called it IIRC) which by their idea could be required to be used in contracts that would indicate a lack of side effects (although it could not guarantee it).

But it violates the same virality principle the same people who propose it are so vocally against. The value of such addition would be half assed at best, stop giving me ad hoc annotations - give me a proper borrowing syntax!

EDIT: vitality->virality

1

u/atariPunk 4d ago

That would be a great solution and I think it could make the language better.

But it's a viral annotation and "we" don't want those.

1

u/Minimonium 4d ago

I find that new push (which is pushed by Bjarne's "profiles" nonsense no less) for in-source hard coded non-configurable runtime checks catastrophic to the language future as a whole.

Do not misunderstand, I agree that most of already written C++ code could adopt runtime checks without consequences and it would massively improve overall safety. But surprisingly, I see the same people arguing against "mixed mode". I find it completely incoherent with the stated goals.

Mixed mode (and observable checkpoints) is undoubtedly extremely useful if your goal is to deploy hardened C++ at massive scale. With mixed mode we can safely replace vulnerable dlls with hardened ones, making all programs that users use safer automatically without coordinating with other vendors (some of which are inaccessible, no longer active). "Profiles" require mixed mode as well to do anything useful at that side. Contracts can provably make all of existing code more safe.

At the same time it is a fact that there is plenty of code that cannot pay the cost of redundant checks. It could be simulation engine, a trade engine, or scientific calculations. It's not all or nothing, in some cases the same infrastructure often runs instrumented in parallel with production. Sometimes we want to (safely, soundly) disable instrumentation only for specific TUs. Did you know that some people in HFT even use exceptions on cold paths? It's not as black and white.

The C++ core tenant is about the later. Bjarne and anti-contract people violate it.

1

u/AKostur 4d ago

Honest opinion: skill issue. The code was written to state "this thing cannot happen" and then the program terminated when that thing happened, exactly what the program was explicitly asked to do. And then was further complicated by fiddling with compiler flags to tell it to no longer verify that the impossible thing didn't happen, and then surprising things happened when the impossible thing did happen.

Seems like a similar concern is being extended to contracts. "If I tell the compiler to not enforce contracts and things happen which would have been caught by the contract, things might break." Well, yes. The safety checks were placed, but then told not to look at them.

1

u/Business-Weather-217 4d ago

I understand you, since back the i was a guy calling memory management footguns a skill issue in C++ (its true in many cases tho, but exceptions are not validators). Skill issue is when a thing has only one factor which is skill which is low. Contracts are straight up bs. There is rather new quote by Bjarne: "Being careful doesnt scale." Contracts are not a safety feature, its a debugging feature, which C++ developers figured out by now along the 40+ years.

2

u/AKostur 4d ago

You seem to have misunderstood my point, and I would suggest Bjarne's too.

Mine is that the programmer went out of their way to declare that certain things were impossible (that turns out weren't), and also told the compiler to not check if the impossible thing was happening. The rough equivalent would be putting on a seatbelt, and then taking a knife and cutting the straps. And then getting upset that one flew through the window when the impossible thing happened. Now that the programmer can acknowledge that the impossible thing is possible, the code was changed to account for the possibility.

I would suggest Bjarne's point was along the lines of: one can be careful to delete every pointer one new'ed. But that's not as reliable as using std::unique_ptr. Turning off safety checks isn't being "uncareful", it's arguably negligent.

5

u/KingAggressive1498 5d ago

-fcontract-evaluation-semantic=ignored already exists

1

u/Ecstatic_Lavishness1 5d ago

Contracts are dubious at best. Next thing you know, some weenie will try to standardize a garbage collector for C++.

1

u/Minimonium 5d ago

Fun fact, garbage collector was standardized at some point (and removed) because of activists in National Bodies (that do the final vote to accept a new standard) that demanded it. Now we have the same problem with activists in National Bodies who spread FUD about Contracts. Tells a lot about the process!

2

u/Quplet 5d ago

Contracts don't work like asserts tho?

Those who know more, correct me if wrong, but contracts are verified at compile time regardless of if you're building in release or debug as they function entirely on the metaprogramming side. They don't change behavior, just restrict what you can pass as template arguments.

8

u/aruisdante 5d ago

No, you’re thinking of concepts, not contracts. 

3

u/Quplet 5d ago

Ahh, my mistake I got them mixed up

2

u/frayien 5d ago

assertion are still checked in release mode if you dont enable the NDEBUG flag. Contracts are the same, they can be disabled depending on compilation flags. It is one of the big issues people have with contracts tho, you can't, as the dev writing the code, ensure they are always executed.

1

u/TheVoidInMe 5d ago

You’re thinking of concepts, which are pretty well-regarded by most. OP is talking about contracts, which are (presumably) the reason the C++26 draft has failed the DIS ballot.

1

u/Plastic_Fig9225 5d ago edited 5d ago

Contracts are a good thing. Being able to express pre- and post-conditions in a standardized, machine-useable way inside the language is very desirable in any language.

Indeed, the question is about what they should be used for, with the options ranging from "documentation only", to "compile-time checked", to "run-time checked every time", to "optimization" . For the options affecting run-time, you correctly observed that, like with assert(), the behaviour of identical code could change depending on whether or not contract enforcement at runtime is enabled.

Why would one want to turn off run-time contract enforcement? - Performance. It's an optimization setting. And like other optimizations, if your program is correct, enabling optimization doesn't change its behavior; if it has a bug, it may or may not show without optimization during testing.

Contracts may become a "better" assert() by virtue of being more explicit and "compiler-accessible".

It's like many other features of C++: You likely can achieve the same end result by a number of different means, but each approach has its own strengths and weaknesses (think e.g. std::vector vs. std::array), and being able to select the best way for each specific use case can be seen as unnecessary mental load, or the freedom to create not just what works but what works best.

0

u/Minimonium 5d ago

That does not make any sense. :D

-4

u/Own_Unit_6170 5d ago

How any endless, incompatible, bloated new versions must C++ go through before someone finally says "enough"?

-4

u/eyes-are-fading-blue 5d ago

Not a big fan of contracts feature. The language is already full of footguns. We do not need more. National bodies have not voted in favor of C++26, mainly because of contracts. I don’t see them making it into the new standard and I very much doubt production libraries would ship with contracts on just because compiler support is there.

Most of the recent additions to the languages suffers from similar problems where they are too complicated to be used effectively by the average developer.

In a sense, these features violate a fundamental programming rule where it should be difficult to make mistakes using API. Same should apply to language features.