timur.audio

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

Category: C++

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 ↑