WG21 C++ Standards Committee St. Louis 2024 Trip Report

Andrei Zissu
11 min readJust now

By Andrei Zissu, Morphisec

I recently travelled a third of the way around the world for my first face to face meeting since joining the committee a couple of years ago (I did attend a few of them remotely). And there is nothing more befitting than having my first F2F C++ standardization meeting in a place known for short as STL - I wonder why WG21 took so long to get there…

Between discovering there are hotter and more humid areas than Israel’s coastal plains, and getting the chance to admire wonderful places like first and foremost of course the St. Louis Arch, and also the Cathedral Basilica of St. Louis, and the Aquarium and the Maze both of which are part of the Union Station complex at which the meeting took place, I also got to take part in a fascinating week of back to back C++ with the creme dela creme of the C++ world. I mean, what can be better than a whole week of seeing people like Herb Sutter and Andrei Alexandrescu all around, having Bjarne show me what I was doing wrong trying to open some hotel entrance, or having lunch and dinner with the biggest names in the industry…

Back to C++ though. Currently my main focus is on contracts, so this is what most of this report will be about. I attended all of the contracts (SG21) sessions including shared sessions with EWG, except for Tuesday when I chose instead to attend an SG23 (safety and security) full day session much of which was also about contracts - while SG7 (compile time and reflection) was also in session, creating quite a dilemma for me. What a week…

SG21 Contracts

Joint Sessions - SG21 + EWG

The last WG21 meeting back in March in Tokyo ended up with multiple issues being sent back by EWG to SG21 for further consideration. In the intervening months SG21 conducted multiple weekly telecons in which those issues were reconsidered. In most cases the final decision was to stick by the original SG21 position as expressed in the contracts MVP (P2900), and this was presented to EWG in St. Louis via EWG feedback on contracts (P3197).

EWG in turn reached consensus on accepting SG21’s response over all but one serious sticking point. Here’s the detailed breakdown:

Mode of termination

P2900 has 4 contract semantics, 2 of which are terminating semantics:

Enforce semantic terminates upon contract violation when the violation handler returns normally (it can be circumvented by the violation handler not returning normally, e.g. by throwing an exception or performing a longjmp).

● Quick_enforce semantic terminates then and there, without bothering to invoke the violation handler. This is a new semantic which was only introduced following the Tokyo meeting, and is intended mainly for resource-constrained embedded systems.

Some concerns were raised by EWG members in St. Louis over P2900 defining the manner of termination for these two semantics as implementation defined. The trade-off here is between the amount of freedom we want to give compiler vendors vs. developers’ expectations in terms of cross-platform compatibility. Ultimately implementation-defined termination won the day in the EWG poll, with consensus against changing the status quo.

Multiple evaluations

The P2900 status quo puts no lower or upper bounds on the permitted number of times a CCA (contract check annotation) may be evaluated. This has been an issue of much contention, involving mainly discussion around CCAs which have side effects. There are those who would like such side effects to be prohibited altogether (see also the constification discussion later on) while others would want us to continue supporting state-changing assertions which are relatively common in present day cassert + #ifdef NDEBUG code. On the other hand P2900 principles mandate that contract semantics should be implementation-defined, and that goes for both caller and callee sides in any function invocation and implementation. In particular we may definitely get checking semantics (any semantic other than ignore) on both sides, resulting in two evaluations. Thus the need for up to two evaluations to be allowed seems to have a pretty strong basis to stand on (as is also the need for zero evaluations, in addition to exactly one evaluation). As for more than two evaluations, some motivation has been brought forward as to that being useful in writing tests meant to flash out unintended side effects, but that seems less compelling.

The polls taken by EWG in St. Louis reflect a strong consensus on maintaining the current multiple evaluations mandate (as in allowing more than one evaluation), and a somewhat weaker consensus on allowing more than two evaluations.

Throwing violation handlers

This has been one of the main contention points, both in SG21 itself and in EWG. Throwing violation handlers are (somewhat paradoxically) seen as a get out of jail free card for avoiding termination upon contract violation in systems which are not allowed to crash. They are also viewed as essential for negative testing, whereby a test intentionally injects faulty input and expects the resulting contract violation to be communicated to it by means of an exception. On the other hand they create multiple issues, such as how to handle CCAs placed in noexcept functions, how might a prospective noexcept(auto) construct behave in the presence of CCAs (lie or tell the truth — and what exactly is the truth?), and down to whether noexcept(contract_assert(false)) should return true or false.

In Tokyo the EWG poll on this matter was evenly split. This time EWG in St. Louis achieved consensus on keeping throwing violation handlers in the contracts MVP

Leaving the door open for assume semantics

Assumptions are a new addition in C++23, where they have been introduced via an attribute (https://en.cppreference.com/w/cpp/language/attributes/assume). Any compiler choosing to do so (as attributes are optional as far as compilers are concerned) may optimize based on the injected certainty that the predicate being assumed will always hold. After such an optimization, any violation of the assumed predicate at run time is very likely to lead to undefined behavior (UB). Which is why assumptions are considered one of the more dangerous features ever introduced into C++, and must be used sparingly and with the utmost care.

An assume semantic is not currently being proposed neither in the contracts MVP nor post-MVP, but there is a reasonable likelihood of it being proposed at some point. EWG at St. Louis voted against leaving the door open for such a likelihood, as in making sure any syntax proposed for the MVP doesn’t block any likely future syntax for assume semantics. Due to the outcome of this vote no details of what such design space reservation would entail were actually discussed.

Constification

This is the one remaining serious hurdle still standing in the way of EWG making a clearer path for the contracts MVP into C++26. P2900 stipulates that local variables and function parameters referred to in CCAs are implicitly const, similar in some ways to the treatment of non-static class members by const member functions. The motivation for this is mainly out of the decades-long pain spot of assignments erroneously replacing comparisons inside assertions (how many times have you misspelt = instead of ==?). Also, side effects in CCAs may not be guaranteed due to the 0-N number of evaluations, so in particular you may not get them at all or you may get them multiple times. Therefore limiting the possibility of side effects may also be seen as a partial mitigation of this issue.

Opponents of constification see multiple issues with it, such as:

● It’s hard to teach that the same code may mean different things in different contexts.

● It may be hard to reason about overload resolution resulting in different outcomes for the same code copied into a different context.

● Problematic inconsistencies. For example, references referred to in a CCA are to be handled as referring to a const object (as in deep constness) while pointers referred to the same way would only exhibit shallow constness - CCAs would be able to mutate objects pointed to by such pointers, just not the pointers themselves.

● In some cases mutating operations may need to be carried out after all, be that for logging from CCAs or for invoking utilities for which applying const correctness is impossible or impractical. In some cases the official advice would be to use const_cast, which is seriously problematic on several fronts:

Const_cast is generally frowned upon, and for good reasons. The main one being that applying const_cast to an object originally declared as const constitutes UB. Therefore the programmer would need to make sure said object was not originally const and is only viewed as such inside the CCA due to constification.

○ Many of us teach that const_cast is a bad code smell, and here all of a sudden it becomes an official get out of jail free card.

Constification will probably remain a tough nut to crack for a while longer. Let’s hope we find a way forward in time for the Wroclaw meeting in November!

[P3097R0] SG21 Contracts: Contracts for C++: Support for virtual functions

A newer version (not yet published) of this paper was discussed by SG21 and later by EWG. The basic functionality described in this paper proposes 2 sets of pre and post CCAs which will be evaluated at each virtual function invocation:

● “Interface” contracts (now renamed “indirect”) - pre/post CCAs attached to the function in the class representing the static type, which is to say the type of the pointer or reference via which the virtual call is performed. These will be evaluated by the caller.

● “Implementation” contracts - pre/post CCAs attached to the function actually being invoked, i.e. either the same as indirect or an overridden function in a class derived from the class representing the static type. These will be evaluated by the callee.

The call sequence would be: pre indirect, pre implementation, post implementation, pre indirect. Assertion statements (contract_assert) are unrelated to this, and will continue to be evaluated whenever control flow reaches them (and contract semantic permitting).

This basic functionality was voted into the MVP by EWG, which is indeed great progress as there were many voices in EWG back in Tokyo insisting on virtual function support being essential for the contracts MVP.

The extended proposal included in the same paper was not voted into the MVP, and its approval post-MVP will require more hard and creative work. That part refers to supporting explicit annotations via which pre/post CCAs may be marked indirect-only or implementation-only, allowing various inheritance models prevalent in the real world.

Other SG21 Discussions

SG21 made good progress on a few other topics as well.

[P3328R0] SG21 Contracts: Observable Checkpoints During Contract Evaluation

This paper leverages [P1494R3] Partial program correctness (should it be approved, as it looks quite likely to be, for C++26) to mitigate to large extent the issues around newly introduced UB in CCAs. There are concerns around UB outside a CCA performing time travel all the way back into the CCA and optimizing it out (only in observe semantics) as well as UB inside a CCA triggering similar elimination of code leading up to said CCA. An observable checkpoint at those boundaries will serve as an impenetrable barrier against such optimizations, and is likely to alleviate much of EWG’s UB-related concerns.

Contracts on Function Pointers

At first glance this seems similar to supporting virtual functions, however contracts on function pointers raise much more difficult issues for which no consensus seems forthcoming at the moment. I won’t go into those details at the moment (unless there is interest) since none of the existing directions seems to be close to gaining a foothold. What does look very promising is Lisa Lippincott’s function usage types (P3271R0). Her proposal creates a kind of novel type aliasing system, whereby “function usages” map to existing types instead of constituting new types in the type system, and can have CCAs added on top. They may also be seen as a sort of fancy function pointer type, but the important thing is they don’t become part of the type system, thus seemingly allowing backwards compatible contractualization of existing code.

There was strong consensus on encouraging continued work on this post-MVP, however there is also wide agreement on this being too big a language change to be safely considered for adoption so close to C++26, therefore it should be pursued for C++29.

P3290R0 "Integrating Existing Assertions With Contracts"

● There will be added functionality allowing manual invocation of the violation handler with a properly populated std::contract_violation object, in enforce and observe semantics. Similarly, it will also be possible to programmatically trigger a quick_enforce violation. All this will allow writing customized assert macros hooked into the new contracts facilities.

● An addendum to the above, which was also approved, adds an overloaded function which triggers termination should an exception escape the violation handler. This is also in order to provide behavior in accordance with the current cassert.

● The last part which was approved allows compiler vendors to implement the assert macro in terms of contracts, and invoke the violation handler in case of assertion failure. This would be implementation-defined, and vendors would presumably provide a way for developers to opt into this new behavior.

The one part which was rejected proposed adding a new kind of contract assertion which would behave similarly to cassert. This was broadly viewed as a bad idea, as we would be teaching 2 new contract assertion types, one of which basically preserves the existing cassert which we want to move away from.

Other Topics

Here are a few topics from other groups I attended besides contracts.

[P3166R0] Static Exception Specifications

Lewis Baker has brought a well thought novel solution to the decades-old saga of C++ exception handling. His proposal would have the exception set be optionally deduced automatically at compile time, which would allow bringing back explicit throw specifications but this time in a manageable manner. This could also be performant, as with a known exception set the compiler may optimize throwing into goto flows. Oh yes, and (albeit in a repurpossed manner) we would regain throws()

This paper still has a long way to go, but it’s off to a promising start with an encouraging keep-working poll from EWGI in St. Louis.

[P2825R2] Overload Resolution hook: declcall(unevaluated-postfix-expression)

The proposed declcall operator triggers the overload resolution mechanism and resolves to a pointer to the actual function that would be called for the given expression, out of the available overload set. This solves an annoying issue which so far has required workarounds (such as using a lambda) and is yet another of those little life improvement proposals that have progressively made C++ better over the years.

EWG polled in favor of further work on this proposal.

Beman Project

An informal evening session was devoted to a presentation of the Beman Project, a kind of new generation Boost intended only to temporarily host library additions on their way into the C++ standard and provide the much needed implementation experience. This is an interesting initiative born out of the latest CppNow conference, and it’s definitely worth following and contributing to.

SG23 (Safety and Security)

● Profiles — Bjarne’s framework for profiles development (P3274R0) is, along with contracts, one of the lead efforts towards enhanced safety and security in C++. This paper was discussed in SG23 (the safety and security subgroup) and consensus was reached on adopting the attribute-based syntax, one of two syntaxes proposed in the paper. Much work remains, and volunteers will be welcome!

● User-defined erroneous behaviour (P3232R0) would have programmatic explicit EB added to the standard. SG23 voted to forward it to EWG and LEWG.

● SG23 also had strong consensus in favor of officially encouraging contracts in C++26. This is a non-binding poll, yet it adds further motivation to getting the contracts MVP in on time.

Other Trip Reports

The official WG21 trip report

WG21 convener's trip report (Herb Sutter)

Think-Cell's trip report (Jonathan Müller) - I find the last paragraph particularly interesting. We’ve been familiar for years with higher level languages such as Python and now Rust relying on C and C++ for writing performant and/or “unsafe” code, but there’s an insightful suggested reversal of fortunes here: Perhaps C++ should also rely on Rust - for writing safe code! The two languages forming a kind of ecosystem instead of out-competing each other would definitely be a fascinating turn of events, although the more idioms we can bring into C++ itself relatively pain-free, the better.

Last Word

This was a fantastic experience. I’m now holding my fingers crossed for contracts, hoping with 2-months-worth of telecons between September and early November we will be able to solve the remaining issues (especially constification) before the Wroclaw deadline, when the committee meets again.

--

--