A few weeks ago, the C++ committee descended upon Cologne, Germany, to finalise the C++20 Committee Draft (or in other words, our “release candidate” for C++20).
We will now have two more meetings, in Belfast and Prague, to iron out any last-minute bugs and address national body comments, and to then actually ship the new C++20 standard.
As usual, my trip report does not aim for completeness at all. Instead, it focuses on a few particular areas that I was involved in personally and/or things that might be relevant for the audio community.
If you want a complete and thorough report, please head to the Reddit report or the one by Herb Sutter. There’s also one by Botond Ballo and Guy Davidson.
No contracts in C++20
Let’s address the big news first: We removed Contracts from C++20. It was already obvious at the last meeting in Kona that there were significant issues with Contracts as they were in the C++20 draft (for example, that unchecked contracts are always assumed, potentially injecting unwanted undefined behaviour with no possibility to opt out of that; and issues with the term “axiom”). It was further obvious that there was no consensus among the original authors of contracts how those issues should be fixed for C++20.
In Cologne, we then had over a dozen papers on the table in EWG, pointing out those and other issues, and suggesting different, mutually incompatible solutions. Some of them were minor fixes; others were more significant redesigns of Contracts. One of those, P1607 (“Minimizing contracts”), was a proposal to do away with contract levels and build modes and replace them with literal semantics, i.e. specifying the desired behaviour of every contract check directly there in the code (assume
, ignore
, etc.). This proposal eventually got a narrow consensus in EWG. I was in favour of this design, because it would give you the elementary building blocks for specifying what a contract checking statement should do, while leaving the door open to add higher-level features like contract levels later, after having gained some user experience with this facility.
However, it is arguably a significant redesign, and the committee ultimately agreed that it was too late in the C++20 release cycle to approve such a redesign now. An evening session with the authors of contract-related papers was convened to figure out a way forward. I was in the room. In this meeting, we talked in great detail about different use cases of contracts, and learned that we don’t actually yet understand each other’s use cases well enough to be able to make any decision confidently. This realisation then led to the only possible decision: We unanimously agreed to remove contracts from C++20.
Instead of shipping contracts in C++20, we formed SG21, a new Contracts Study Group chaired by John Spicer, with the aim to come up with a better contracts facility for C++23. I expect that we will first meet at the next committee meeting in Belfast this November. The first task of SG21 will be to gather different use cases for contracts, make sure we understand them, and get consensus on which of those we actually want to support. Hopefully this will then lead to a clearer understanding of the feature, and ultimately a better design that can win a greater consensus. I think this is a very positive outcome of Cologne. Work on collecting, describing, and categorising those usecases is already underway. Expect a paper in the pre-Belfast mailing.
I will participate in SG21 to help with that effort, not least because I have my own domain-specific interest in contracts. I would like to have a standardised C++ facility to replace non-portable compiler built-ins like __builtin_assume
. Having such portable optimisation hints would be great for optimising DSP algorithms and other low-latency applications, and unlike the other contract semantics, this “assume” semantic is something that you cannot do portably in today’s C++ at all. I have a proposal in the race, P1774 – proposing std::assume
– and I hope to get it into C++23. It could either be a standalone feature, or could be covered by a future contracts facility. It is currently unclear yet which one it should be, but I am sure SG21 can help figure it out. There’s also a larger background story here: we could probably have had std::assume
in C++20, if things had been different with the way contracts went. (You can read more about that in P1773 and P1774 if you’re interested.)
Concepts
The other big language features in the C++20 working draft: Concepts, Coroutines, Modules, and operator<=>
, are doing well. The latter three needed only minor fixes this time around, and are good to go. Concepts received a larger change: we voted in Cologne to rename all C++20 concepts from PascalCase
to snake_case
, to be consistent with all other names in the C++ standard library. LEWG then did another pass over those names, making them more consistent and avoiding name clashes. They ended up with a very nice and coherent result in my opinion. We now have an interesting situation where many concepts have a corresponding type trait (metafunction) starting with is_
, for example std::copy_constructible
is now the (new) concept, and std::is_copy_constructible
is the corresponding type trait (which has been around since C++11).
Overall, we are now in a really good shape for all those language features, and I am really excited about being able to use them soon. Concepts in particular are badly needed for C++ library design, and my own work on audio software would look a lot better if I could already portably use them today.
Formatting
We managed to get std::format
library into C++20, which I am really excited about. Finally there is a sane, typesafe library for text formatting in C++, and we never again have to use things like sprintf
and iomanip
. The new library also supports positional arguments (great for localisations), is optimised for performance, and has a low binary footprint. Here’s what the syntax looks like:
std::format("The answer is {}.", 42);
Cool, huh? You can read more on Victor Zverovich’s homepage.
Multithreading and synchronisation
The other really large library that we landed in Cologne is the C++20 Synchronisation Library. You get semaphores, latches, barriers, and lots of other useful synchronisation and thread coordination facilities. This library is pure gold for people who write multithreaded, high-performance software. And if that wasn’t enough, you also get a stop token and jthread, which let you asynchronously request that a thread stops execution, and automatically join threads (instead of crashing when they go out of scope).
A lot of stuff didn’t make it into C++20
This was a really busy committee meeting. In fact, the busiest I attended so far (and this is my 8th meeting). And it was the last meeting to finalise wording for the C++20 committee draft. As a result, many features didn’t make it into C++20 simply because we ran out of time and didn’t get around to review and finalise the wording. Unfortunately, they will now all have to wait for C++23. The list is long and includes many things particularly interesting for low-level and low-latency work, like what we do in the audio world. In particular, for C++20 we won’t have std::flat_set
and std::flat_map
(cache-friendly associative containers based on a sorted vector), we won’t have std::any_invocable
(useful alternative to std::function
), we won’t have the compile-time for...
“loop”, and we won’t have Richard’s fixes to the C++ object model which would have provided a legal way to achieve some popular type punning-style tricks that today have undefined behaviour. This last one is particularly painful, and means that I will have to significantly re-arrange my upcoming CppCon talk on that particular topic…
CTAD
Another paper I was involved in, P1021 (Filling holes in CTAD), finally made it into C++20, but not to 100%. The way I had presented it at C++Now 2019 (“Better CTAD for C++20“), we were supposed to get CTAD for aggregates, CTAD for alias templates, and CTAD from inherited constructors in C++20. In the end, after having spent a considerable amount of time in Cologne getting all that wording right, we did get the first two into C++20, however the third (inherited constructors) will have to wait until C++23. I am quite happy about that, because out of the three, that last one is by far the least common use case anyway. I will be talking again about CTAD at the ACCU Autumn Conference in Belfast, and I’ll make sure to update my slides accordingly.
Audio
Last but not least, here’s an update on the std::audio
proposal. I poured quite a lot of work into this over the last few months, and even though this is yet a long way away from getting into the C++ standard, I am happy to report we are making great progress.
Here in Cologne, we discussed revision P1386R2 in SG13 (the study group for graphics, audio, and other forms of I/O). There was again a lot of useful feedback on our API. We made some tweaks to our audio_buffer
class, which I now believe is really awesome: it now elegantly handles all common memory layouts for multi-channel audio buffers (interleaved vs. de-interleaved, contiguous vs. pointer-to-pointer, etc). We will now also be looking into unifying the way we do callbacks with the efforts of the Executors folks, who were kind enough to show up in SG13 and talk us through the way they do it.
There was also very interesting feedback from Apple audio engineers (P1746). We addressed many of the points they raised. In the next revision, we will have to clarify further that this proposal is for a low-level hardware abstraction API, and not a higher-level “audio experience” API like the one you would find on a mobile platform, designed around audio sessions (rather than devices).
We are still missing some other features. Over the next months, we will need to add things like handling various multichannel layouts, a proper error handling mechanism (we will use std::error_code
, following what std::filesystem
does), and a proper device settings negotiation API to replace our current somewhat crude std::audio_device::get/set
functions. Some things we don’t know yet how to solve, such as how to support 24-bit integers and reversed-endianness integers as the sample format (which is a thing in some native audio APIs, but unfortunately not something the C++ standard really supports).
Our optimistic plan at the moment is to address all of the above and to produce a feature-complete revision R3 for the Belfast meeting. If that goes well, we will then hopefully be able to move the proposal forward to LEWG, In parallel, we will propose our implementation of P1386 for inclusion into Boost as Boost.Audio, so y’all can get your hands on a proper, reviewed and test implementation of std::audio
as soon as possible. Watch this space.
That’s all, folks! The next update about committee work will come after Belfast in November 2019.
Leave a Reply