timur.audio

A blog about C++, music software, tech community, and life.

Trip report: July 2019 ISO C++ committee meeting, Cologne, Germany

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.

Initialisation in C++17 – the matrix

Recently, I gave a talk titled “Initialisation in modern C++”, which was apparently quite well received. (A video recording is available here).

One of the slides in this talk was a matrix tabulating all the possible initialisation syntaxes and what they do for different kinds of types (such as: built-in types, aggregates, etc.)

Ever since then, I keep getting requests for a high-resolution version of that table. So finally I got around to uploading one. Here it is:

You can freely reuse this table – I hope it will be useful. And please let me know if you spot any mistakes 🙂

Keep in mind that the above table is for C++17, but things will change in C++20, in particular for aggregate types: we will get designated initialisers as well as direct initialisation for aggregates.

Trip report: February 2019 ISO C++ committee meeting, Kona, Hawai’i

What better way to start my new blog than to publish a trip report from the most recent C++ committee meeting on the wonderful Big Island of Hawai’i? 

If you are looking for an incredibly detailed report of everything that happened, please instead head to this report by Bryce and others, and also see Herb Sutter’s and cor3ntin’s reports. I won’t try to provide this breadth of coverage, and instead focus on a few areas that are particularly relevant for me and the community that I am proxying here:

  • Making C++ simpler, more uniform, and easier to teach;
  • Providing developers with better tools;
  • Improving support for low-latency and real-time(-ish) programming,
  • 2D Graphics, Audio, and other forms of I/O and human-machine interaction.

That being said, let’s start with the big news: we voted both Coroutines and Modules into C++20!

Coroutines

Coroutines are now finally part of C++20. There has been strong opposition to merging coroutines into C++20 at the last couple of meetings; this time, however, there was overwhelming consensus. Congratulations to Gor Nishanov and everyone else who worked so hard on this! Having coroutines in the language is a true game changer. In audio, we do a lot of low-level processing based on callbacks, generators, etc. Such things can be written much more concisely and elegantly using coroutines. My mind was blown when a fellow committee member took my own code example from P1386R0 (the one producing a sine wave) and rewrote it in terms of coroutines, making the code shorter by 50%. Expect more stuff on this topic! I’ll do my best to make a talk about Coroutines happen at the Audio Developer Conference 2019. (Anyone interested in delivering one?)

I was myself opposed to “Gor-routines” for a long time, because they are not really first-class C++ objects: you do not know their type or their sizeof. Instead, they are weird new type-erased entities that will allocate the coroutine stack frame on the heap when created. For audio and other real-time-ish usecases, memory allocations are a non-starter (even if the optimiser might optimise away those allocations in some cases). Gor’s paper P1365R0 confused me even further, proposing a make_on_stack helper that would 1) require an obscure C++ wording change to work at all and 2) potentially render your program conditionally ill-formed depending on what the optimiser does. Not willing to entertain such hacks, it took me a while (and several conversations with Gor and other experts) to realise that there is a much simpler solution: Create your coroutine on the main thread, immediately suspend it, and then you’re free to keep using it on the real-time processing thread afterwards, with no allocations. It’s quite similar to other type-erased, potentially-heap-allocating things like std::function: just don’t create or copy any of these on the processing thread, but calling them is fine!

Much of the opposition to Gor-routines so far has been due to the alternative proposal, Core Coroutines, promising a different, more low-level model not suffering from such problems. However, one crucial thing happened in Kona. We heard a report from compiler vendors on the implementability of the different approaches, revealing that Core Coroutines might be very hard to implement. Stack-allocating a Core Coroutine requires an even more weird new kind of type: a type with deferred layout. It means that the compiler won’t yet know the sizeof of such a type during semantic analysis, only later during optimisation, making working with such types very difficult. This property is contagious: any type with a deferred-layout member would itself be a deferred-layout type. It might take vendors until C++26 to make this machinery work. At this point, I am convinced that we serve the community better to give them a tried and tested version of coroutines right now in C++20. Even if it’s not perfect, I convinced myself that it is good enough.

At this point, the only real downer is that C++20 will ship without any standard library utilities for coroutines – it’ll just give you the bare-bones core language facility, which is not that simple to figure out. So If you want, say, an easy-to-use generator type, you’ll have to write it yourself, or use a third-party library such as cppcoro.

Modules

Modules is the other big feature that was promised and that we finally delivered on in Kona. What we are getting in C++20 is the “merged modules” proposal that evolved from the combined efforts of the authors of the original Modules TS (mostly driven by Microsoft) and the competing Another Take on Modules, a.k.a. the “Atom proposal” (mostly driven by Google). As the committee voted to merge “merged modules” into C++20, Gabriel Dos Reis and Richard Smith exchanged a historic handshake. This moment will forever change the way people write C++ code, and have a profound impact on the C++ ecosystem, perhaps more than any other language feature introduced in the last two decades.

And this is where I am somewhat worried: the version of modules that we now have in C++20 is something that no-one has implementation experience with, let alone usage experience. It creates a huge burden on tool vendors. The wording leaves many things undefined (like, how do modules even map to actual files?), and compilers will implement it each in their own way. Anything that has to parse a C++ project, but isn’t a compiler (think IDEs, build systems, static analysis tools, etc.) will then likely have a really hard time to catch up. These issues have been known for a while, but the committee was reluctant to act on it, because many people thought that it wasn’t the committee’s job to concern themselves with such things (after all, the C++ standard doesn’t define how header search paths work, either). For the last two years, I found this stance to be a very frustrating obstacle.

However, just like with Coroutines, something happened in Kona that changed my mind to voting in favour of modules: SG15 (the Tooling study group of the C++ committee) decided that we will publish an official Technical Report about the C++ ecosystem in a modularised world. This should happen in the timeframe of publishing C++20 itself, and will give guidance to compilers and tool vendors alike. My hope is that this will lead to vendors coalescing around a sane way of implementing C++ modules across the ecosystem.

It is unclear though how long the transition to modules will take. Unfortunately, the C++ standard library itself will not be modularised yet for C++20 (there was simply no time), and I imagine it will take quite a while before third-party C++ libraries start shipping with module support and bring the feature to the end user.

Contracts

Contracts are another one of those really big new C++20 features I am really excited about. They have so many use cases. The one use case which I believe audio developers (and DSP engineers in particular) should be really excited about is not even the main intended use case for contracts (essentially, a better and more flexible assert), but rather, the possibility to use contracts as a portable replacement for __builtin_assume to tell the compiler about conditions that you know will always be true in your code, so that the compiler can optimise based on those assumptions and thus generate faster code than before (by eliminating checks and other redundant instructions).

Now, contracts have been in the C++20 working draft for some time now, so why do I write about them here? Well, it turned out in Kona that what we currently have in the draft is not quite perfect yet, and improvements should be made. One issue is that the “assume” technique described above makes a violation of the contract undefined behaviour, which is a really sharp knife to give to developers. So we could perhaps have different modes for contracts that can be assumed even if they’re not checked (that would be the sharp knife), but also contracts that cannot. More broadly, it feels like the current defaults for the behaviour of the various different contract modes are not the defaults we want. Another question relates to whether (and how) to explicitly allow continuing execution after a contract check has failed.

In Kona, we have seen that there are currently substantial disagreements on what the right solutions for these issues are. Despite a rather heated debate, no consensus was reached this time. We debated several competing proposals: P1290R1, P1429R0, and P1426R0, the last one actually proposing to remove contracts from C++20 altogether. But of course, there was no consensus on that proposal, either.

Instead, EWG agreed to rename expects and ensures to pre and post, respectively. Another controversial move – it doesn’t feel yet like this would be the final decision. So I expect the show to continue in Cologne this July. Bring popcorn!

Initialisation, Aggregates, and CTAD

If you have followed my recent C++ talks (such as Initialisation in modern C++ and CTAD in C++17), you might have noticed that I am particularly interested in these interconnected areas. That’s because they are a major source of confusion for C++ developers, and I’d like to see them being simpler and more uniform.

After lots of hard work to get my first paper on simplifying initialisation (P1008) into C++20 in Rapperswil, this time I had a much smoother ride with a smaller fix, allowing array-size deduction in new-expressions (P1009), which was approved here in Kona. A larger paper that I am also involved in, “Filling holes in CTAD” (P1021), is not quite there yet. It was discussed in CWG and now requires more work on the wording, which, as it turned out, is quite hard to get right. I am hopeful that we can finish this work in time and have this proposal moved into C++20 in Cologne.

However, the most important (and controversial) initialisation-related paper in Kona was P0960. In fact, this was the most controversial paper of the whole meeting – drawing more “against” votes in plenary than either Modules or Coroutines (but still sailing through with a comfortable 80% majority). As a result, we now have a new way of initialising aggregates (including arrays) in C++20 – we can now use parentheses instead of curly braces:

int arr[3] (0, 1, 2);

struct MyAggregate {
int i;
int j;
};

MyAggregate agg (2, 3);

This offers several benefits. In C++20, functions that forward constructor arguments (such as std::make_unique or std::vector::emplace_back) will finally work with aggregate types. It will also be easier to initialise dependent types in generic, templated code (curly braces are usually a real pain to use in such contexts). Further, you will be able to initialise aggregates inside macros, such as assert, which doesn’t work with curly braces:

assert (MyAggregate {2, 3});   // Error

The preprocessor treats the comma as a separator between macro arguments, and as a result, the parser breaks. There is a special parsing rule for commas inside parentheses – but not inside curly braces. 

More broadly, in C++20 we will live in a world where initialisation is, in fact, more uniform. Think of it as uniform initialisation 2.0! Both parentheses and curly braces will just “always work”, with only a couple of simple-to-understand semantic differences:

  • Curly braces will call a std::initializer_list constructor if possible, parentheses will not;
  • Parentheses allow narrowing conversions, curly braces do not. 

I think that this is a nice world to live in. However, as always in C++, there are a few subtle gotchas, notably the order in which the arguments are evaluated (in-order for curly braces, indeterminate for parentheses), and lifetime extension of temporaries, which is allowed for curly braces but not for this new case. We did this to keep parentheses as close as possible to “calling a constructor” – even though no constructor exists in this case. This means:

struct MyAggregate {
int i;
int&& j;
};

int f() { return 2; }

Aggregate a1 {1, f()}; // ok, a1.j is now 2

Aggregate a2 (1, f()); // compiles, but a2.j is now a dangling reference!

Expect a future blog post from me, dealing with this stuff in more detail.

Curiously, there is no name for this new type of initialisation. We briefly considered “direct aggregate initialisation” in CWG, but there was no consensus to call it that. So I am not sure yet how exactly I’ll teach this. I am doubtful that the standardese description, “an aggregate initialized from a parenthesized expression-list as described in [dcl.init]/9.3″, will sound very palatable to most people.

Audio

The Kona meeting was a special one for the audio community: for the first time, a formal proposal to add audio I/O to C++ was presented. This new proposal is a work in progress by Guy Somberg, Guy Davidson, and myself, and is currently still in its early phase. It started with an informal exchange of ideas, then later a talk at ADC 2018, and now an actual C++ proposal, which was seen by the LEWGI and SG13 study groups in Kona. Since there was no update on the 2D graphics proposal for Kona, SG13 (the recently resurrected Study Group for human-machine interaction) spent their entire 4-hour meeting on audio!

I am happy to report that we received a large amount of encouragement and helpful feedback from both study groups. The Guys and I also had the honour to host a 2-hour evening session about “Audio basics”, explaining to the C++ committee how digital audio works. I was positively surprised by how well-attended it was, and pleased with the many interesting and challenging questions we received. I also had very insightful discussions with some of the authors of the mdspan proposal, as well as people from SG1 who are working on a uniform callback interface for C++. This is all very relevant to what we are trying to do here.

As a result of Kona, we will add significant improvements to the current design, and will publish the result in an R1 revision of the paper.

The most notable change will be to adopt mdspan (P0009) as the underlying type for an audio buffer. This makes the ill-designed buffer_view and strided_span classes from P1386R0 go away entirely. They are not implementable correctly: when trying to come up with an iterator interface for a multidimensional array, you inevitably run into the “view-of-views” problem, which makes your view not really a view, and your iterators not really iterators. I discovered this the hard way; I was happy to learn that some more experienced people have already thought all this through. In Kona, we also solved the problems of how to introduce the driver type (for example, you can have both WASAPI and ASIO devices on windows, and might want to enumerate and use them independently), and how to correctly account for different sample types (such as float vs. int) and different buffer orders (interleaved vs. deinterleaved).

Expect a more in-depth discussion of all this in the upcoming P1386R1. I will also talk about this work at the upcoming Audio Developer meetup as well as next month at the ACCU 2019 conference

span and mdspan

Speaking of mdspan: unfortunately it did not make the cut for C++20. I would say that the proposal is in a very good shape, but not quite finished. But there is good news: proposal P1161 is in flight, targeting C++20, which proposes to deprecate the usage of the comma operator inside operator[], with the goal to remove this usage entirely in C++23. Which means that mdspan, if and when it lands in C++23, will be able to use the really nice syntax m[i, j, k] for multidimensional element access.

On the other hand, the single-dimensional std::span has been in the C++20 draft for a while. My only grief with std::span was that unlike containers, it used signed integers as its size_type, meaning that when I started using it, my code started to throw conversion warnings at me. There is something worse than applying a bad rule: applying a bad rule inconsistently. Fortunately, as of Kona, size() is now always unsigned, including for std::span, which also received other usability enhancements (see P1227 and P1024).

I am now consistently using span everywhere in my code where I would previously use a pointer and a size, and I recommend everyone else to do the same.

flat_set and flat_map

This is another goodie for everyone writing low-latency programs. It’s basically a sorted vector with the interface of a set/map. While element insertion and removal is algorithmically more expensive than for  a set/map – O (N) instead of O (log N) – the other operations have significantly better performance characteristics for many cases, because the contiguous memory layout means that they are much more CPU-friendly and have much better cache locality. I’ve seen boost::flat_set and boost::flat_map used in production code to great effect, and having both data structures in the standard is definitely a huge win. The relevant proposals, P0429 and P1222, were not quite ready in Kona for merging into C++20, but good progress was made, and I am hopeful that they will both land in C++20 in Cologne.

Now, if only there was a lock-free queue in the standard, I would be a truly happy fan of the C++ standard library! (But let’s see, perhaps we can do something about that…)

Linear interpolation

Sometimes, the small and simple utilities are the ones that are most useful – and their absence most painful. C++17 finally gave us std::clamp, which I now use on a daily basis (it’s a very common function in audio code). In Kona, we voted two others into C++20: std::lerp, performing linear interpolation between two floating-point values, and std::midpoint, which, well, computes the midpoint between two values, but is remarkable in that it does the right thing for both floating point and integer numbers (see P0811). Oh, and of course, both are constexpr, so if you combine them with the constexpr std::vector and the compile-time for... loop that we will probably also both get in C++20 – let’s wait for Cologne – then you will have some really elegant tools for building compile-time interpolation tables!

C++20 feature freeze

There are lots of other things that happened in Kona which I did not cover here (if you are curious, check out the other trip reports). But the most important outcome is that C++20 is now feature complete. And it’s going to be a great release: Concepts! Modules! Coroutines! Contracts! Ranges! We basically delivered on all big features that were promised, except for Networking and Executors. It looks like C++20 will be the biggest update ever for the C++ language.

We will use the next meeting (Cologne, July 2019) to merge in a few last-minute features for which the wording is not yet finalised – some of which I’ve mentioned above – but there will be no more design work. Then, there are two more meetings (Belfast and Prague) reserved for bugfixes. Following that, C++20 will be released, and the committee’s efforts will shift towards C++23 – with a focus on reflection as the next big feature.

See you in Cologne!

© 2019 timur.audio

Theme by Anders NorénUp ↑