Trip report: Summer ISO C++ standards meeting (Rapperswil)

On Saturday June 9, the ISO C++ committee completed its summer meeting in beautiful Rapperswil, Switzerland, hosted with thanks by HSR Rapperswil, Zühlke, Netcetera, Bbv, SNV, Crealogix, Meeting C++, and BMW Car IT GmbH. We had some 140 people at the meeting, representing 11 national bodies. As usual, we met for six days Monday through Saturday, this time including all evenings.

Per our C++20 schedule, this was the second-last meeting for merging major language features into C++20. So we gave priority to the major proposals that might make C++20 or otherwise could make solid progress, and we deferred other proposals to be considered at a future meeting — not at all as a comment on the proposals, but just for lack of time. We expect to get to these soon once C++20 is well in hand.

Top news: Contracts adopted for C++20

Contracts (Gabriel Dos Reis, J. Daniel Garcia, John Lakos, Alisdair Meredith, Nathan Myers, Bjarne Stroustrup) was formally adopted for C++20.

Contracts allow preconditions, postconditions, and assertions to be expressed in code using a uniform syntax, with options to have different contract levels, custom violation handlers, and more.

Here are a few quick examples to get the flavor. Let’s start with perhaps the most familiar contract, assert:

void f() {
    int x = g();
    int y = h();
    [[assert: x+y > 0]]
}

“But wait,” someone might say, “C’s assert macro was good enough for my grandpappy, so shouldn’t it be good enough for me?” Never fear, you can still assert(x), but if you respell it as [[assert:x]] you get some benefits. For example:

  • You’re not relying on a macro (unlike C assert). Yes, this matters, because macros are outside the language and routinely cause problems when using language features. For example, this was demonstrated again just a few days ago on Twitter by Nico Josuttis (HT: Alisdair Meredith), who pointed out that assert(c==std::complex<float>{0,0}) does not compile; the reason is because macros don’t understand language commas, but [[assert: c==std::complex<float>{0,0}]] works just fine without any surprises.
  • You get to install your own violation handler and ship a release build with the option of turning on enforcement at run time.
  • You get to express audit to distinguish expensive checks to be run only when explicitly requested.
  • You get to express axiom contracts that are intended to never generate run-time code but are available to static analysis tools.
  • Finally, you will likely get better performance, because contracts should enable compilers to perform more optimizations, more easily, than expressing them using assertions.

But there’s more, because of course contracts are not just about assertions. They also include expects preconditions and ensures postconditions, which are part of the function declaration and so are visible at call sites:

double sqrt(double x) [[expects: x >= 0]];

void sort(vector<emp>& v) [[ensures audit: is_sorted(v)]];
    // could be expensive, check only when audit is requested

In addition to similar benefits as for assert, expects preconditions in particular deliver some enforcement benefits that are very desirable and difficult or impossible to get by hand:

  • Preconditions are usually enforced at the call site, which is what we want most of the time because a precondition violation always means a programming bug in the calling code.
  • But preconditions can also be enforced in the callee, which can sometimes be necessary for pragmatic reasons, such as when the function is invoked through an opaque function pointer.
  • Preconditions and postconditions that are known at the call site also give the optimizer more information to potentially make your code fast.

As a cheeky aside, if you noticed that I mentioned optimization several times, it’s for the usual reason: The simplest way to get C++ programmers to want a feature is to show that it can make their code faster, even if performance isn’t the only motivation or benefit.

Why contracts are a big deal, and related Rapperswil progress

In my opinion, contracts is the most impactful feature of C++20 so far, and arguably the most impactful feature we have added to C++ since C++11. That statement might surprise you, so let me elaborate why I think so.

Having first-class contracts support it is the first major “step 1” of reforming error handling in C++ and applying 30 years’ worth of learnings.

Step 2 is to (gradually) migrate std:: standard library precondition violations in particular from exceptions (or error codes) to contracts. The programming world now broadly recognizes that programming bugs (e.g., out-of-bounds access, null dereference, and in general all pre/post/assert-condition violations) cause a corrupted state that cannot be recovered from programmatically, and so they should never be reported to the calling code as exceptions or error codes that code could somehow handle. Over the past three meetings (Albuquerque through Rapperswil), the Library Evolution Working Group (LEWG) voted unanimously to pursue P0788R2 (Walter Brown) and relatedly in Rapperswil voted unanimously to pursue section 4.2 of P0709 (my paper), which includes pursuing a path of gradually migrating all standard library preconditions from exceptions toward contracts. (Note that in the near term, the idea is for implementations to be allowed, but not required, to use contracts. We are bringing the community forward gently here.)

Why is step 2 so important? Because it’s not just window dressing: Gradually switching precondition violations from exceptions to contracts promises to eventually remove a majority of all exceptions thrown by the standard library(!). This principle applies across programming languages; for examples, in Java and .NET some 90% of all exceptions are thrown for precondition violations (e.g., ArgumentNullException). Being able to eliminate a majority of all exceptions, which eventually enables many more functions to be noexcept, is a huge improvement for both correctness and performance:

  • Correctness: It eliminates a majority of the invisible control flow paths in our code. For example, over 20 years ago I wrote GotW #20 [Sutter 1997] that shows how today a 4-line function has 3 normal execution paths and 20 invisible exceptional execution paths; if we can eliminate a majority of the functions that can throw, we immediately remove a majority of the invisible possible execution paths in functions like this one, in all calling code.
  • Performance: More noexcept enables more optimization and faster code. (You knew that was coming, right?)

Once you change all preconditions (and postconditions and assertions) from exceptions to contracts, eliminating some of the largest categories of exceptions, one specific kind of exception dominates all others: bad_alloc. Which brings us to step 3…

Step 3 is to consider handling heap exhaustion (out-of-memory, OOM) differently from other errors. If in addition to not throwing on precondition violations we also do not throw on OOM, the vast majority of all C++ standard library functions can be noexcept. — Needless to say, this would be a huge change where we need to tread carefully and measure impact and high-fidelity compatibility in major real code bases, and we don’t want people to panic about it or think we’ve lost our minds: We are serious about bringing code forward gently after validating good adoptability and low impact long before this gets near an actual standard.

Nevertheless, we are also serious about improving this, and the fundamental change is simple and already fully supported in C++ today: In Rapperswil, LEWG also voted unanimously to pursue section 4.3 of P0709 (my paper) which proposes pursuing a path of gradually migrating all OOM from bad_alloc to new(nothrow)-like mechanisms. The initial contemplated step, definitely not for C++20, would be to change the default new_handler from throwing bad_alloc to calling terminate. That might sound like a big change, but it’s not, it’s already fully supported in C++ today because you can already change the new_handler to terminate today with a single line of code (std::set_new_handler([]{terminate();});), and this would just be changing the default and existing code that wants to keep the current behavior could still write simply the reverse single line of code (std::set_new_handler([]{throw std::bad_alloc();});) to get exactly the current behavior.

To repeat, this is a feature that will want a clear high-fidelity zero-breakage migration path, and we’re treating that compatibility seriously, even as we are also treating solving this problem seriously to modernize C++ error handling and move toward a mostly-noexcept world.

You can find a more detailed writeup in my new proposal P0709, particularly sections 1.1, 4.2, and 4.3. Again, P0709 is not for C++20, it is to illustrate a direction and potential path. The other parts of P0709 have not yet been reviewed by the full committee, so for now they should not be treated as anything more than a proposal, subject to much discussion and feedback over the coming several years.

Other new features approved for C++20

We adopted several other new features into the draft standard.

Feature test macros (Ville Voutilainen, Jonathan Wakely). This enables code to portably test whether a certain new C++ feature exists. “Why would I do that?” someone might ask. The primary and biggest benefit is they help your team to start to adopt new C++ features even before all your compilers support them; just write

#if /*new feature is present*/ // temporary
    nice clean modern code!    // <-- keep this long-term
#else                          // temporary
    do it the old way          // temporary
#endif                         // temporary

and eventually drop the #if test and the whole #else block as soon as all your compilers are up to date. This is so much better than today, where one of two things happens: (1) Teams often wait until all their compilers support a given new C++ feature before they start to use it, which slows down adopting the new features and getting their benefits at least on the compilers that do support it. Or, (2) teams roll their own nonstandard nonportable compiler-specific “approximate” feature tests (e.g., write their own macro for “I know this feature is available on version ## of MSVC and version ## of GCC” by hand).

We all agree we don’t like macros. However, we don’t yet have replacements for all uses of macros, including this one, and these standard macros are better than rolling your own nonstandard ones or, more likely, just not using new C++ features at all for a longer time.

Some experts still disagree, and we respect their views, but in my view these feature test macros are an important and pragmatic help to improve the speed of adoption of new standard C++ features.

Standard library concepts (Casey Carter, Eric Niebler). This is the first part of the Ranges TS to be merged into C++20 at an upcoming meeting, and contains the core concepts from the Ranges TS. It is also the first appearance of the concepts language feature in the standard library. Expect more to come for C++20 (see Ranges, below).

Class Types in Non-Type Template Parameters (Jeff Snyder, Louis Dionne). Yes, you can now have types other than int and char as non-type template parameters. For example, in addition to template<int Size>, you can now have things like template<fixed_string S> for a suitably defined class type. It turns out that this builds on the <=> spaceship comparison operator; and if you’re wondering why, it’s because we know the semantics of a defaulted <=> comparison, which is essential because the compiler has to perform comparisons to determine whether two template instantiations are the same.

Note: I did not have this benefit in mind at all when I proposed <=>. I think this is a great example where, when you let programmers express intent explicitly as with <=>, and provide useful defaults, you are inherently adding more reliable information to the source code and will reap additional benefits because you can now build on knowledge of that clear programmer intent.

explicit(bool) (Barry Revzin). This is conditional explicit, along the same lines as conditional noexcept. It lets library writers write explicit at a finer granularity, to turn off conversions where possible and feasible, without having to write two functions all the time.

Bit-casting object representations (JF Bastien). Header <bit> now lets you bit_cast. “But wait,” someone may be thinking, “don’t we already have reinterpret_cast?” Yes we do, and this is still a reinterpreting operation, but bit_cast has less unsafety and some additional flexibility: it ensures the sizes of the From and To types match, guarantees that they are both actually trivially copyable, and as a bonus makes the operation constexpr wherever possible.

Speaking of constexpr bit_cast, here are some more items in the “constexpr all the things!” department:

Other progress and decisions

Reflection TS is feature-complete: The Reflection TS was declared feature-complete and is being sent out for its main comment ballot over the summer. Note again that the TS’s current template metaprogramming-based syntax is just a placeholder; the feedback being requested is on the core “guts” of the design, and the committee already knows it intends to replace the surface syntax with a simpler programming model that uses ordinary compile-time code and not <>-style metaprogramming.

Parallelism TS2 is done: The second Parallelism TS is done! We completed ballot comment resolution, and approved the final TS for publication. This TS includes more execution policies, an exception_list type to communicate multiple parallel exceptions, and more parallel algorithms including wavefront, reductions, inductions, parallel for, and more. It also includes task_block support to enable writing custom parallel algorithms.

Graphics (io2d) is deferred: Many thanks to Michael McLaughlin especially, and to Guy Davidson, Michael Kazakov, and David Ludwig for their assistance with the design, specification, and implementation of this cross-platform library. This is a project that has been worked on for several years, and for the past two years has been primarily responding to committee tweak requests; unfortunately, although the committee requested a series of updates to the proposal that have been applied, at this meeting the committee decided that it does not have interest to pursue further work on graphics in any form at this time after all. However, the io2d project will continue, and be available on package managers (Conan, vcpkg), and we expect a renewed proposal in the medium term; in the meantime, watch Guy Davidson’s blog for news and updates.

Also, LEWG adopted P0921r2 as a statement of their direction for future C++ standard library evolution compatibility guarantees.

Updates on other major proposals

Here are other major items in progress. You’ll notice that the first six (6!) of them mention expectations for our next meeting this November in San Diego. Not all of those items will make C++20 in San Diego, but people are going to try. It’s not surprising that San Diego is going to be a busy meeting, though; that was expected, because it’s the last meeting to merge major features into C++20, and deadlines are famously motivational. — Just do not expect all of the following to make C++20, and I’m listing them in rough order starting with the most likely to make it in.

(very likely for C++20) Ranges: In my previous trip report I mentioned that the core concepts from the Ranges TS were expected to make C++20, but the rest of the Ranges TS would be “C++20 or C++23.” Since then we have made faster than expected progress, and it now looks like Ranges is “likely” to make C++20 in the next meeting or two. For those interested in details, in addition to all of the Ranges TS, also paper P0789 on Range Adaptors and Utilities have now progressed to detailed wording review and are targeting C++20. In sum, to quote Eric Niebler: “If you liked the Ranges TS, you’ll love C++20.”

(likely for C++20) Concepts: “convenience” notation for constrained templates: We already added the concepts core feature to C++20, and at this meeting we had further discussions on adding a convenience syntax to write constrained templates without resorting to the low-level “requires” keyword. The two major active proposals that received discussion were from Bjarne Stroustrup and from me. The good news is that the Evolution Working Group (EWG) liked both, which means that for the first time we have a proposal based on the TS syntax (Bjarne’s preference) that could get consensus to be approved!

The key people are continuing to work to come up with a merged proposal that might be adoptable for C++20 in November in San Diego, and I’m pleased to report that as of this post-meeting mailing we for the first time have a unified proposal that lists most of the previous authors of papers in this area as coauthors, you can find it here: P1141R0: “Yet another approach for constrained declarations.” I’m guardedly optimistic that we may have a winner here; we’ll know in San Diego. (I sometimes delay my trip report until the post-meeting mailing is available so that everyone can see the latest papers, and knowing that this new paper was coming was one reason I delayed this report.)

(maybe for C++20) Coroutines: EWG considered an alternative, then decided to go forward with the TS approach. That came up for full committee vote but fell just short and was not adopted for C++20; the proposers will continue to work on improving consensus over the summer by addressing remaining concerns and we expect coroutines to be proposed again for C++20 at our November meeting in San Diego.

Modules: For the first time, the committee saw a merged approach that both major proposers said satisfies their requirements; that announcement was met by applause in the room. The merged proposal aims to combine the “best of” the Modules TS and the Atom alternative proposal, and that direction was approved by EWG. EWG did not approve the poll to incorporate a subset of it into C++20 at this meeting; it is not yet clear whether part of the proposal can make C++20 but we are going to have an extra two-day meeting in September to help make progress and we expect it to be proposed again for C++20 at our November meeting in San Diego.

Executors: This is still not expected to be part of C++20, but key people have not given up and are trying to make it happen, and one never can tell. We are going to hold an extra two-day meeting in September to help make progress on Executors, and expect to have a lively discussion about options to merge all or parts of it into C++20 in November in San Diego.

Networking: This is pretty much ready except that it depends on Executors. Soon after Executors are ready for C++20, Networking is ready to slide in right behind it.

Clearly San Diego is going to be a busy meeting. But before then we have two extra design meetings on modules and executors to help improve their chances of progress; those will be co-located with CppCon in September, to take place near the CppCon site on the days just before the conference begins. On top of that, there will also be an extra library wording issues meeting in the Chicago area in August… all in all, it’ll be a full summer and fall before we even get to San Diego.

Additionally, SG12 had productive discussions about undefined behavior (including with participation from our sister ISO working group WG23, Programming Language Vulnerabilities), and SG15 had a second exploratory evening session focusing on package managers for C++.

What’s next

Here is a cheat-sheet summary of our current expectations for some of the major pieces of work. Note that, as always, this is an estimate only. The bolded parts are the major changes from last time, including that Ranges as a whole is looking very likely for C++20.

wg21-schedule-2018-06

And here is an updated snapshot of where we are on the timeline for C++20 and the TSes that are completed, in flight, or expected to begin:

wg21-timeline-2018-06

Thank you again to the approximately 140 experts who attended this meeting, and the many more who participate in standardization through their national bodies! Have a good spring… we look forward now to our next “extra” meetings in September (Bellevue, WA, USA) and the next regular WG21 meeting in November (San Diego, CA, USA).

25 thoughts on “Trip report: Summer ISO C++ standards meeting (Rapperswil)

  1. (I just realized my comment below got messed up, so I repeat it here)

    As a small comment to yaafcd (p1141r0):

    It says “We are willing to tolerate this small potential for ambiguity.”
    Wouldn’t it make slightly more sense to have constrained type template-parameters have the regular syntax template<Contrain typename T> foo() or template<Constrain class T> foo() and then additionally add the relaxation that template<Constrain T> foo() is allowed? That would make the rules symmetric to Constain auto x and again allows for a style that has no ambiguities. Therefore it would not need the admission above.

    Still I’m really glad to see that there’s finally a good proposal which most people are willing to support. :)

    And I’m really excited that range-adaptors could actually make their way into C++20 :)

  2. What about adding bound checking for STL containers like [[expects audit: x < size()]]? I find it a pity that they do not provide an effective way for bound checking without exceptions or wierd .at() idiom.

  3. As a small comment to yaafcd (p1141r0):

    You wrote “We are willing to tolerate this small potential for ambiguity.”
    Wouldn’t it make slightly more sense to have constrained type template-parameters have the regular syntax “template foo()” or “template foo()” and then additionally add the relaxation that “template foo()” is allowed? That would make the rules symmetric to “Constain auto x” and again allows for a style that has no ambiguities. Therefore it would not need the admission above.

    Still I’m really glad to see that there’s finally a good proposal which most people are willing to support. :)

    And I’m really excited that range-adaptors could actually make their way into C++20 :)

  4. The terminate-on-OOM mindset really worries me. As an owner of a system component DLL, we can’t just go terminating a whole process because some bad font had a table that was too large, and I certainly do not want my own application being terminated because the user tried to load some huge JPEG (I have some images that are hundreds of MB’s when loaded).

  5. @J

    “I think terminating on a OOM makes much more sense than throwing exceptions.”

    Terminate what? The whole process or the thread in which it occurred?
    If my process can keep running in the face of 1 thread having an OOM, then I would much rather that, than kill the whole thing.

    I’d argue that would make for more reliable software, as an OOM (due to memory pressure or fragmentation) can sometimes be a transitory condition that passes, meaning multi-threaded long-running processes can potentially keep going regardless.

  6. Does this mean that with contracts we will finally have

    T& pop_back() noexcept;

    ?

  7. @Adrian

    I’ve been thinking about fragmentation. The only reason you cannot defragment C++ memory today is someone might send a pointer to someplace not trackable as a pointer. I’ve seen two cases of this: you cast it to “an int” and do some math that you latter undo and cast back; or you send the pointer elsewhere (ie the network) and latter get it back (these are probably undefined behavior). For all pointers they point to nullptr, someplace within the object in question, or one beyond the object. Thus if you ignore those cases it is possible for the compiler to track/find all the pointers, and when compaction is needed move/relocated the objects wherever and change the pointers.

    If someone wants to run with this, feel free. I think it can work but there are many tricky things that I can’t possibly think of much less put into a proposal. I do not require credit for my tiny idea.

  8. It always concerns me when people think and talk about OOM (out of memory) when it comes to allocation failure. An allocation failure does not necessarily mean that you’re out of memory. In my experience, the most frequent cause of allocation failure is address space fragmentation, not memory exhaustion. You don’t even need a memory leak for that to happen.

    Thinking about all allocation failures as OOM limits the thinking about the causes, solutions, and error reporting strategies that should be used.

  9. @Florian Rudolf: “Concerning contracts, are there plans to support overloading with contracts?”

    I hope not. Contracts aren’t concepts, and having two functions with two different contracts ought to mean having two different algorithms behind them. And a different algorithm should be spelled with a different name.

    That is, if there’s a version of `my_algorithm` that takes a sorted list and one that doesn’t, then these are two separate algorithms and users should pick which one to call based on that data. If you have a vector, then you know that it is sorted, either because you [[requires]] that it was sorted, or some function [[ensures]] that it is sorted. Either way, you the caller of `my_algorithm` is perfectly aware of the sorting status of the vector. So just call the right algorithm.

    Overload resolution is complex enough; let’s not give people the ability to add arbitrarily many layers of complexity to them.

  10. Concerning contracts, are there plans to support overloading with contracts? Something like:

    my_algorithm(std::vector<float> const & foo);
    my_algorithm(std::vector<float> const & foo) [[expects: is_sorted(foo)]];
    
    std::vector<float> my_sort(std::vector<float> const & foo) [[ensures: is_sorted(result)]];
    
    // somethere else
    std::vector<float> tmp = randomVector();
    
    my_algorithm(tmp);  // calls first overload
    my_algorithm(my_sort(tmp)); // calls second overload
    
  11. The OOM paradigm switch idea is interesting, but relying in global state for disabling retroactive changes to how it works sounds really concerning. Maybe this could be opted in by using tools in std2 namespace or something? Is std2 becoming a reality anyway?

    I sort of disagree with P0709r1 1.1 (2). Consider a program that runs some sort of not really well trusted submodule, for instance an IDE which interprets an in-progress, possibly buggy program, or an X server which runs a session for a possibly buggy client. Bugs in these are “kinda-recoverable”, you can’t recover the module, but it’s likely you can recover the state of whole program. Maybe I don’t know what I’m talking about? The rest of the document appears to be great. Have you considered reporting no error by using a hidden error domain of no error on the ABI side, as much as that must be ludicrous while the code side of error structure is explicitly designed to not indicate success?

  12. Thank you for the amazing work you all are putting in to make C++ an even more amazing language.

  13. @avjewe Remember that metaclasses are a multi-year project in early prototyping and that’s also responding to committee feedback. I’ll have more news this summer though, stay tuned…

  14. std::bad_alloc

    I think terminating on a OOM makes much more sense than throwing exceptions.

    As opposed to C and its heap allocation functions (e.g. realloc() and friends) C++’s new is auto-magically getting memory from the free store. It is much more analogous to an automatic variable getting its memory from the stack. Ideally I think a new failure should trigger UB like a stack overflow. I guess terminate() is the next best thing.

    As for the multi language objection, adding an extra API in the foreign language to call std::set_new_handler([]{throw std::bad_alloc();}); cannot be that hard unless I am missing something?

    J.

  15. Thanks to all the experts!
    Totally agree with Giuseppe about Modules.
    Modules has to be the most important of all…
    May be, prioritizing Modules over other stuff a bit is required?
    Good luck ahead!

  16. Two things… Three taking into account a big thanks for all the committee members.
    1- for the backwards compatibility it will be after C++20 so you’re not forced to use later version of the language with battle tested 25years old code.

    2- The train model is working good, but, considering the incredible usefulness of the modules would it be incredibile to delay.1 meeting or 2 in order to let modules go into IS?

  17. “Just add set_new_handler” isn’t a solution for libraries. Today I have libraries written in C++ that are driven by main application written in Swift or ObjectiveC or Java. Code in those languages generally cannot call set_new_handler. The C++ libraries rely on bad_alloc in a few places. If you change the default I’d have to change the code. Not to mention how new and old C++ libraries are supposed to coexist in such world?
    In general, global solutions to local problems are evil. If you want std library not to throw bad_alloc then solve it in the library (this is what allocators are supposed to be for, no?). Don’t change the globals for code you don’t own or control.

  18. More details on the prospective August libraries meeting in/near Chicago? I could likely help out on that.

  19. ‘new_handler ‘

    Changing ‘new_handler from throwing bad_alloc to calling terminate’ is messing with backwards compatibility.

    We already have the screw up with the removal of auto_ptr to satisfy a zealot attitude, auto_ptr is fine if used correctly in old code. The only change should have been a compiler warning with perhaps a flashing blue light for good measure!

    This is not as bad, requiring perhaps just one change to old code to get it to be backward compatible.

    Unfortunately the ISO C++ committee doesn’t appear to appreciate one of the great features of C and C++ is backwards compatibility with old code – You know the stuff that is 100% battle proven possibly over 25+ years of use and anyone modifying just to comply with a dictated new feature takes on a real risk in terms of messing it up and their job security!

    After being (justifiably!) negative above, very please to see the ‘Feature test macros’ , something that should have been in C++11

    Although hard to believe anyone would ask “Why would I do that?” – Ok perhaps someone on the C++ISO committee…….

  20. where can I found an explanation with examples for Contracts in C++20? if you have a lecture or article that can easily be understood it will be great.

Comments are closed.