Assertions (run-time checks of a Boolean condition) have been around since the beginnings of the C language, and are of course available to C++ from the <cassert> header. Starting with the latest Standard, C++ supports a more flexible and featured approach to assertions, with the new language keywords pre, post and contract_assert. This article is intended to provide a walkthrough and quick-start to using these keywords in Modern C++ code, and hopefully explains some of the aims and rationale behind their names and functionality.
In a previous article we looked at how to write a more flexible to assert() in C++, but this approach is rendered obsolete by the newly available syntax. To upgrade existing code to use the new keywords, all that is necessary is to somehow replace assert() with contract_assert(). (The new name is a little long for my liking, however it does provide symmetry with the existing compile-time static_assert()). Some code will demonstrate this:
int main(int argc, const char *argv[]) {
contract_assert(argc > 1 && argc < 10);
return argc - 1;
}
This program simply returns the number of command-line arguments passed as its return code, or fails if there are none or too many. Unlike for existing assertions, no header is needed and the assertions semantics, from a choice of four, are set with a compiler flag (there is no longer a need to use a macro):
| Semantic | Logic | Typical Use Case | Expected Compiler Flag (e.g., GCC/Clang) |
| Ignore | Contracts are not checked at all. | Production / High Performance | -fcontracts-mode=ignore |
| Observe | Handler is called; program continues. | Logging bugs without crashing. | -fcontracts-mode=observe |
| Enforce | Handler is called; program terminates. | Standard Development / Testing | -fcontracts-mode=enforce |
| Quick-enforce | Immediate crash; no handler called. | High-security environments. | -fcontracts-mode=fast |
This table may look intimidating but in reality it’s just the options of log and/or terminate, with Ignore being neither. It is expected that a project-wide assertion semantics setting will become part of the build process in the style of existing Debug and Release builds.
That’s not the full story, as functions can now be written with precondition (constraints on function parameters) and postcondition (constraints on return value) checks which are switchable on the assertion semantics as described above. Here is a simple function to demonstrate this:
int add(int a, int b)
pre(a > 0)
pre(b <= a)
post(r : r >= 0 && r < 1000)
{
contract_assert((2 * a + b) != 42);
return a + b;
}
This function specifies two separate preconditions with the pre() blocks, separated only by whitespace in the style of requires() when using Modern C++ concepts. An arbitrary number of preconditions can be specified, and this provides better readability and debugging compared to using pre(a > 0 && b <= a).
There is also a postcondition block where the result value has been given the name r and checked against falling within the range [0, 1000). Note that is doesn’t have to be a real variable, as the return expression is a temporary in this case.
Calling this function produces the following results:
add(1, 1) | returns 2 |
add(20, 2) | contract_assert() fails (during function execution) |
add(0, 1) | first pre() condition fails (before function entry) |
add(1, 2) | second pre() condition fails (before function entry) |
add(500, 500) | post() condition fails (at function exit) |
add(0, -1) | post() condition fails (at function exit) |
Where a handler is called, as for modes Observe and Enforce, it would be expected for output to be logged, with failing condition, source filename, line number etc. In some deployment cases this (possibly confidential) information should not be shown, so it is expected that a custom handler can be specified in which case this data can be consumed differently. The exact syntax is not yet finalized but is likely to use a std::contracts::contract_violation type, which can be queried in a similar manner to std::source_location. (Unlike the language features outlined above, this will require the new <contracts> header.) This function could be made to clean up critical resources, but using std::set_terminate() may be a better fit for this, as cleaning up may not even be necessary if the modes Ignore or Observe are specified.
That’s about it for this article. It should be noted that at the time of writing there appears to be no compiler support for this feature, so the code above is only assumed correct and representative. Look out for a future P2900 branch in GCC or Clang (including its availability on Compiler Explorer) in order to be able to test out these features and syntax.