GCC 16.1 recently came out and landed some big features, including early support for C++26 contracts. In doing so, it is the first major compiler to support this new language feature, which is pretty exciting. So it is finally time to take C++26 contracts for a spin.
I really like the idea of contracts facilitating a more defensive approach to programming.
They are a great way of explicitly stating the constraints on the inputs and outputs of functions and enforcing these constraints at runtime.
Violating a contract constitutes an incorrect use of our API and is a programmer error.
This is in contrast with something unexpectedly going wrong at runtime which should be handled either with exceptions or classic error handling/std::expected/std::optional.
The syntax is pretty straightforward:
| |
In this example we are given a vector of numbers and want to compute the average of their square roots
It is obvious some preconditions on the function arguments have to be fulfilled for this computation to be valid:
- Each number must be non-negative, otherwise the square root is not defined (at least not in the domain of real numbers).
- The vector must not be empty, otherwise dividing by the length of the vector would be a division by 0.
It is clear that the return value of the function must be non-negative, so we can encode this in a postcondition ensuring that our function computes a valid output.
The name result used in the postcondition is arbitrary, we have chosen it ourselves by specifying it before the colon.
We could have just as well written post(average: average >= 0.0) or something else.
Getting Stuff to Compile
If you want to try this feature yourself, make sure you have GCC 16.1 or later installed and enable the C++26 standard -std=c++26 as well as the contract feature via -fcontracts.
When invoking g++ directly to compile and link a small program in one go, that is all you need: the driver knows that -fcontracts is in use and takes care of pulling in the necessary symbols when it invokes the linker for you.
The situation is a bit more nuanced in build systems like CMake, where compilation and linking happen as separate stages1.
There you will most likely encounter a linker error about missing symbols, since there is currently no default contract violation handler included in the usual stdc++ standard library.
There are two ways to fix this:
- Writing your own custom contract violation handler by implementing the global function
void handle_contract_violation(const std::contracts::contract_violation&);which optionally can be markednoexcept(see Contracts for C++). - Using the default contract violation handler that currently ships with the experimental
stdc++explibrary2. The proper and more future-proof way to do this is to also pass-fcontractsas a linker flag and let the compiler driver figure out which library to pull in. You could instead link againststdc++expexplicitly via-lstdc++exp, but this is more of an implementation detail that may change in future GCC versions.
It is also worth noting that, similar to how -std=c++20 implies -fconcepts today, future GCC versions will likely imply -fcontracts from -std=c++26 – at which point most of this dance should disappear on its own.
For now I opted to just use the default contract violation handler, but I might explore writing my own in a future post.
Why contracts as a language feature?
C++26 contracts extend the already overwhelmingly complex C++ syntax even further, something that often sparks a lot of criticism. So one might wonder if contracts warrant such a change when there exist libraries that can achieve similar functionality already. In this case I am actually quite happy that contracts extend the language:
- When using libraries, it can prevent us from being able to declare functions as
constexprandnoexcept. The libraries often handle violations as exceptions and rely on some allocations for helper objects, rendering them incompatible withconstexprandnoexceptqualifiers. - Especially postconditions and preconditions for constructors felt a bit annoying in libraries.
- On a more subjective note, I think the syntax is pretty nice and intuitive. It allows writing the constraints in a concise and readable manner, hopefully encouraging developers to actually use them.
To elaborate on the second and third point, let’s consider some examples using Boost.Contract:
Boost.Contract Function Example
Let’s consider a simple function that computes a number based on some inputs:
| |
So we have to make sure to declare the result variable before the contract such that we can capture it as a reference in the postcondition lambda.
We can’t even do the calculation of the result in the same line as the preconditions would then be checked afterwards, defeating the whole purpose.
Since Boost.Contract relies on exceptions to handle contract violations and we have the non-trivial boost::contract::check object, we can’t declare the function as constexpr or noexcept even though the computation itself might allow for it.
In C++26 we can just write:
| |
This is much more readable and concise.
Furthermore, we can declare the function as constexpr and noexcept without any issues (given that the computation itself does allow for it).
Boost.Contract Constructor Example
Using Boost.Contract for constructor preconditions is especially awkward, since we have to inherit from the boost::contract::constructor_precondition class to allow us to check preconditions in the constructor at the very beginning of the initialization list:
| |
In C++26 this can simply become:
| |
When reading the documentation for Boost.Contract one finds plenty of more tricky scenarios beyond constructors: destructors, subcontracting (function overrides during inheritance, multiple inheritance), etc.
While I have not played with all possible scenarios (e.g. with inheritance, conditions on destructors), it seems that in C++26 they pretty much boil down to adding pre() and post() which is pretty nice and intuitive.
Contract Assertion Evaluation Semantics
Previously, when using libraries like Boost.Contract or microsoft/gsl for contracts, I always liked being able to control the evaluation of contract assertions.
Any runtime overhead introduced by contract assertions can be eliminated by specifying a compiler flag which essentially makes them go away completely.
The Contracts for C++ paper talks about four different levels of contract assertion evaluation.
When a contract violation occurs the following happens depending on the active evaluation semantic:
- ignore: The contract violation handler will not be invoked. The draft still requires that contract assertions must be parsable and whether they are evaluated is implementation-defined.
- observe: The contract violation handler will be invoked. If it returns normally, the program continues its execution. Otherwise the program will be terminated.
- enforce: The contract violation handler will be invoked. The program gets terminated in any case.
- quick-enforce: The contract violation handler will not be invoked. Instead the program gets terminated immediately.
Which of these semantics is active and how to control it is implementation-defined as well.
In GCC we can switch between the different evaluation semantics via the -fcontract-evaluation-semantic=[ignore|observe|enforce|quick_enforce] flag.
Unfortunately, the documentation does not state which one is the default at the moment, but based on my testing it seems to be enforce as the program terminates on contract violations by default.
Especially the description for ignore semantic is interesting: contract checks will be elided, with no checking at either compile or runtime.
I did a quick test on Compiler Explorer and confirmed that the assembly a function with pre-/postconditions but using -fcontract-evaluation-semantic=ignore is exactly the as when not using contracts at all.
This is exactly the behavior I was hoping for, allowing us to have optimized builds with contract checks completely removed.
I hope other implementations follow this approach as well, but the contract proposal does not require it: even with the ignore semantics, contract assertions must be parsable and might be evaluated which is different from the assert() macro which can be completely removed by defining NDEBUG.
It might also be interesting to look into the many other compiler flags listed in the GCC documentation, which provide fine control over contracts, starting with -fcontracts.
Summary
Even at this early stage, C++26 contracts are a really nice addition to the language and are already usable if the compiler supports it.
The only rough edges I encountered were related to the fact that this is a very new feature and the very first implementation of it.
Let’s hope that other compilers as well as tools like clang-format, clang-tidy, and clangd will follow soon.
There was a fruitful discussion on LinkedIn about this topic. ↩︎
This can easily be verified by running something like
nm /usr/lib/gcc/x86_64-redhat-linux/16/libstdc++exp.a | grep handle_contract_violation(path most likely different on your system). ↩︎