Trip Report: ISO C++ Meeting Cologne (2019)

This is my trip report of the ISO C++ Meeting in Cologne – my first ISO C++ Meeting.

I will not so much focus on the overall features that were approved for C++20/C++23 (there are better reports, e.g. here and here), but more describe my personal experience. I try hard to accurately represent what happened, but I might have misunderstood (inevitably) or misremembered things. I am happy for any feedback.

The meeting lasts for six days, starting with technical discussion from Monday to Friday, and then ending with a plenary on Saturday, where official members of the national bodies vote on the motions of the week. As I’m not a member of the German national body (or any other), I participated as an “observer”. Which mainly means that I’m not allowed to vote in the plenary on Saturday, but otherwise can participate in any discussion and straw poll during the week. All meetings are open to the public and there is no attendance fee. As a courtesy to the host, one should let them know before attending, so they can plan accordingly.



On Monday, the meeting started with a plenary session at 9 am to kick off the meeting. I arrived a bit earlier, received my name badge and table tent, took a seat in the plenary room, and watched the many well-known faces around me. The room was very crowded and, in fact, there was another “overflow” room, which could follow the plenary via video broadcast.

The organizer, Nicolai Josuttis, mentioned that he had originally planned with 100-120 attendees (similar to the meetings in 2017), but this meeting marked a new record with well beyond 200 attendees.

The first-time attendees were asked to briefly introduce themselves – I would guess there were over 40 of them.

Then we split into (9!) parallel meeting tracks. An addition to the standard (in the form of a paper) usually starts its life in one of the study groups. At the Cologne meeting, 13 study groups were meeting (but usually for only one or two days of the week).

A paper is usually first discussed in one of those study groups, and then either rejected, sent back to the author with suggestions for improvement, or forwarded to either the Evolution Working Group (EWG) or the Library Evolution Working Group (LEWG). EWG and LEWG complete the design of changes of the language itself or the C++ standard library, respectively. When those subgroups have consensus on a design, they forward the paper to the Library Working Group (LWG) or Core Working Group (CWG). Those subgroups improve the wording of the changes (without modifying the design) to make the intent unambiguous – this is really hard for a complex language like C++.

LWG – Rangify New Algorithms

I started my journey at the end of the pipeline in the Library Working Group (LWG). We started to work on the paper P1243R2 “Rangify New Algorithms”, which adds overloads to the algorithms for_each_n, clamp, sample, shift_left and shift_right which operate on ranges instead of iterators. The atmosphere was welcoming to first-time attendees. LWG also seems to be the only group that has “LWG Reviewing Guidelines” in the wiki, which together with the Specification Style Guidelines help to get started into the world of standard library wording. Issues discussed on that particular paper ranged (no pun intended) from “A note must not contain the word ‘may’” to “the for_each_n function has no Complexity specified”.

LEWG – Deprecate std::aligned_storage

After having lunch with some people I met at LWG, I joined the Library Evolution Working Group, i.e. the group that designs standard library features before forwarding them to LWG.

When I entered the room, the discussion was on P1413, Deprecate std::aligned_storage and std::aligned_union. I have the feeling that we are deprecating more often in recent times and that makes me happy. I’d like to see even more deprecation in the future to remove sharp edges and to make it easier to teach C++.

EWG – Contracts

I joined the Evolution Working Group (EWG) meeting in the afternoon. Recall, this is the group that designs language features.

The theme for the afternoon were Contracts, i.e. annotations of the form

EWG – Contracts-iso_cpp_meeting_2019

that allow to express preconditions, postconditions and asserts.

Contracts were already voted into the C++ working draft three meetings ago, but from what I understood, with the expectation that the remaining disagreement over parts of contracts could be resolved until by Cologne meeting. After the Cologne meeting, the Working Draft of the C/C++ standard is turned into the committee draft, which is then send to all national bodies for comments. After Cologne, there are two more meetings planned to fix those comments, and then release C++20.

There were seven papers that proposed changing major parts of contracts, and two smaller “bug-fix” papers. The session started with Ville Voutilainen, the chair, taking polls on which papers to discuss (some had overlap). During the discussion it became clear that people felt strongly about contracts, and pulling them into different directions so late in the C++20 cycle was probably not a good sign.

At the end of the session, the paper P1607R0 Minimizing Contracts got consensus. The voting happened in two parts: First whether build levels and continuation should be taken away (consensus), and second, whether to add four levels to specify in each contract, ignore, assume, check_never_continue, and check_maybe_continue, which are called “literal semantics”. (see the paper for more details).

I could understand the consensus for removing build levels and continuation, because they seemed controversial, and I could imagine that they can be added later (e.g. in C++23) if needed. I was surprised to see that adding “literal semantics” got consensus because this appeared to be a (major) design change with no way to go back after shipping C++20.

Anyway, after those polls were taken, I thought that contracts were settled for C++20. I would be proven wrong in a few days.

After this session ended at 5:30 pm, everybody headed out for dinner.

Evening session – Just-in-time Compilation for C++

In the evening, I attended a session on just-in-time (JIT) Compilation for C++. Evening sessions seem to be mostly used to introduce a topic to the committee and to get some initial discussion. No official votes are taken in them.

This session started with a fantastic overview over the history of just-in-time compilation, which started in the 1960s.

Just-in-time compilation was defined as “any translation performed dynamically, after a program has started execution”. Three advantages of JIT were mentioned:

  • Interpreted programs (the source of JIT compilation) tend to be smaller due to higher level of abstraction
  • Interpreted programs tend to be more portable; only the interpreter needs to be ported
  • Interpreters (such as JIT systems) have access to runtime information, such as input parameters, control flow and target machine specifics

The boundaries between ahead-of-time compilation (what C++ developers do), interpretation, and just-in-time compilation are blurry. Is the transformation of source code to mere bytecode ahead-of-time compilation? Is the execution of native code in a virtual machine just-in-time compilation?

Then, the authors presented their paper P1609 C++ Should Support Just-in-Time Compilation, which looks intriguing. Their first example is:


Note that in current C++, arguments to templates (here: a) need to be constant expressions, because the C++ compiler needs to generate the instantiation at compile-time. With this proposal, the [[jit]] attribute make a template eligible for runtime instantiation. The compilation of the template body is deferred until runtime (i.e. just-in-time compilation). This allows to generate code that is optimized for the actual value of the parameter.

It was reported that this provides huge benefits e.g. in linear algebra applications, where templates can be optimized at runtime to the actual matrix size and properties (like symmetry). Without JIT in C++, the alternative is to generate all specializations at compile-time, conceptually like

Cheap JIT-iso_cpp_meeting_2019

which results in huge binary sizes.

This paper was discussed at the next day in SG 17 “EWG Incubator”. I did not attend that session, but I hear that SG 17 decided to spend more time on this proposal in the future.


I started the day at 8:30 in the Library Evolution Working Group (LEWG).

LWEG – Add wait/notify to atomic_ref and atomic<shared_ptr<T>>

The first two papers on the agenda were P1643R0 Add wait/notify to atomic_ref and P1644R0 Add wait/notify to atomic<shared_ptr<T>>.

Both papers were follow-ups for P1135 The C++20 Synchronization Library paper, which added std::atomic<T>::wait(), std::atomic<T>::notify_one and std::atomic<T>::all (and much more!). Those allow to use blocking synchronisation on atomics in a more efficient way than just polling the atomic for a change of its value. For example


can be replaced by


provided that the thread that updates b calls std::atomic_notify_one(&b); afterwards.

P1643 and P1644 had been discussed by SG 1 “Concurrency and parallelism” on Monday, and were then forwarded to LEWG. LEWG itself had no concerns about the design – the  proposed changes were adding the same methods to std::atomic_ref and

std::atomic<shared_ptr<T>> for consistency. LEWG voted to forward both papers to LWG for wording. At the end of the week, all three papers got merged into C++20.

LEWG – Bikeshedding a namespace for numeric constants

Earlier, the two papers P0437R1 Numeric traits for the standard library and P0631R1 Math Constants had been approved by the committee. The first one adds numeric type traits like num_min<T>, num_epsilon<T>, etc (to replace std::numeric_limits with a better design) and the second one adds mathematical constants such as pi and Euler’s number.

The authors had been tasked to agree on a common namespace for both, but after many discussions now asked LEWG to decide the issue.

So there we are now, bikeshedding. Proposed names were std::math, std::number, std::numbers, std::num and std::constants.

There were concerns that number could conflict with a concept of the same name. Eventually, it took us only 6 polls to get consensus on std::numbers. Naming is both hard and important.

LEWG – Issues with current flat_map proposal

Next, P1727R0 Issues with current flat_map proposal was presented. The author wants to redirect the proposed std::flat_map from C++20 into a technical specification to get more experience with it.

A flat_map is similar to a map, but the values are stored in a sorted vector instead of a tree to allow for a more cache-friendly lookup (especially for small container sizes). If you already know boost::flat_map, then the proposed std::flat_map is similar. But there is one major difference. boost::flat_map uses a vector<pair<key, value>> as storage (so iterators give access to references to pair<key, value>) while std::flat_map uses two vectors, one for keys and one for values, as storage. This allows for faster lookups of a key (because values do not pollute the cache lines), but has a major impact on the interface. Dereferencing an iterator of std::flat_map yields a pair<key&, value&> (instead of a pair<key,value>&), which has the following surprising effects described by the paper:


We went to to live explore the reference implementation. The room was amazed when we saw that allows to #include via an URL (example)!

In essence, dereferencing an std::flat_map iterator yields a proxy object (similar to vector<bool>::iterator and various ranges). There were discussions whether that was surprising enough to be a concern or if the advent of ranges already introduces enough precedent for it. The room was split, and so there was no consensus to stop std::flat_map from proceeding to LWG. Later I found out that even though LEWG did not stop flat_map at this point, LWG did not have enough time to perform its wording review during the week, so flat_map did not make it into C++20.

SG 1 – Not All Agents Have TLS

I spent the rest of the day in SG 1 “Concurrency and parallelism”. There were roughly 25 persons in the room. The first paper I saw there was P1367R1 Not All Agents Have TLS. In the current standard, thread-local variables, i.e.


are instantiated with every thread, but it’s unclear if that refers to every thread of execution (including GPU threads, vector lanes, executors, etc.) or just std::thread. The paper advocates to make thread_local refer to the thread of execution, and make support of thread_local optional for threads of execution that are not std::thread (or main()). This seems important to support GPUs, who would otherwise need to instantiate all thread-local variables on every GPU thread, and that would quickly exhaust GPU memory (even with just the operating system and standard library variables (think errno etc)). Imagine that a coroutine uses thread-local storage, but the executor (more below) uses different std::threads (of a thread pool) each time the coroutine is resumed. In that case the executor should be able to advertise that it does not support thread local storage.

SG 1 – Executors

The main executors proposal is P0443r10 A Unified Executors Proposal for C++, and there were various paper in SG1 to tweak it. I had not followed earlier work on executors (which started years ago), so I had some difficulties entering the discussion. Talking to some committee members gave me the gist: Executors provide a unified interface to execute a callable function object on different execution resources, such as std::thread(s), a GPU, etc. The executors proposal seems to be quite mature. The major open point was to get consensus on error handling. In P0443, to schedule the execution of a callable on an executor, one would use


which would return immediately and the execution itself would happen asynchronously. If the callable threw an error, if the executor failed to execute the callable for internal reasons (e.g. could not connect to server, device memory exhausted, etc.), or if the callable was canceled because the executor was asked to shutdown, no feedback would be available to the code that scheduled the callable. The authors of P1525R0 – One-Way execute is a poor basis operation made the case that this prevented to write generic algorithms that took any executor. On example was a “retry” algorithm, which would submit a callable up to N times until it succeeded, or a “fallback” algorithm, which would submit the callable to the first given executor, and if that failed to the second given executor.

Concerns were raised that this would complicate the implementation of executors, and that the support for an error channel like this should be optional. Code could statically query whether an executor supports that error channel. This counter proposal is P1791r0 Evolution of the P0443 Unified Executors Proposal to accommodate new requirements.

The discussion was more heated then the ones I had seen before (except Contracts), but it was well organized with people raising their hand and waiting for their turn to speak. At times, the chair needed to write down the order in which people had raised their hands to keep track.

It came down to fundamental questions like “Is the existence of failure universally applicable to all domains/use cases?”, which are really hard to reason about. In the end, we achieved slight consensus to require error channel support in executors, as proposed in P1660r0 A Compromise Executor Design Sketch. There, in addition to calling execute with a normal callable (like in the example above), one could provide an instance of a class with done() and error() methods:


done() will be called when the executor abandons the callable before executing it (the name of this function might be changed) and error() will be called if the executor encounters an error while handling the callable, or an exception escapes from the callable.

SG 1 – Simplifying and generalising Sender/Receiver for asynchronous operations

The paper P1792R0 Simplifying and generalising Sender/Receiver for asynchronous operations is concerned with how the standard library (and other libraries) can provide asynchronous operations in a way that they can be used with all different asynchronous mechanism are already part of the standard or that will be part of a future standard, including std::future, coroutines, fibers, etc. In this proposal there is a single way to write, e.g., an asynchronous accumulate operation “async_accumulate” with the signature


After writing it once, we can invoke it synchronously, by passing a lambda as a receiver that will be invoked immediately, or we can invoke it asynchronously by using one of the predefined receivers. For example, when passing “use_future”, async_accumulate returns a future. When passing “use_await”, async_accumulate() returns an awaitable.

Examples (from the paper):


I don’t quite understand the ins and outs of this particular proposal, and there was quite some discussion about it, but I would be happy if asynchronous operations become that unified. Unfortunately, I could not attend the meeting on Wednesday and Thursday, so we jump directly to Friday.


SG 15 – Modules

Study group 15 “Tooling” is concerned with tools in the C++ ecosystem, where big topics are C++ Modules and package management.  The session started with the chair, Bryce Adelstein Lelbach, reporting on the “C++ Tooling Ecosystem” Technical Report. It is meant to help C++ modules adoption (which are coming with C++20) by

  • explaining requirements and usage of modules across the C++ ecosystem,
  • presenting field experience on topics like module naming, mapping, performance,
  • and offering a set of guidelines.

Earlier during the week, EWG had approved to begin work on the technical report. SG 15 is now looking for contributions to it.

Next, three implementers, Nathan Sidwell for GCC, Gabriel Dos Reis for MSVC, and Richard Smith for Clang, reported on the support of C++ Modules in their respective compilers. All of the implementations support basic use cases (like importing <iostream>) but none of them has complete support for C++ modules. On a simple helloworld (import <iostream> and print a line), clang compiled 4x faster with modules than using traditional includes. Other compilers reported speed-ups in the same region.

SG 15 – Format for describing dependencies of source files

For build systems, C++ Modules provide new challenges, because modules must be built before the source files that import them. In general, only compilers (or similar tools like clang-scan-dep) can figure out which modules are imported by a source file. So there needs to be a way how compilers and build systems can exchange that information, and it would help if all build systems and compilers agree to a common way. P1689 Format for describing dependencies of source files provides a proposal how this exchange format would look like. It was well received. Having implementers of compilers and build systems in the same room tackling this topic makes me hopeful that Modules will become a valuable part of C++.

SG 1 – std::process

I switched rooms to attend SG 1’s discussion of P1750 A Proposal to Add Process Management to the C++ Standard Library after I could not attend the discussion of that paper in LEWG.

The fact that LEWG had already reviewed it and provided comments, meant that SG 1 could and should focus on concurrency-related issues only, and not re-discuss other design aspects.

The paper had three main aspects, which are

  • a class to read and modify environment variables
  • a class to launch and control processes
  • classes to abstract OS pipes for communication between processes

The paper might be split into three separate papers to move those things forward independently. The first topic we discussed was the destructor of the proposed std::process. In the proposal, invoking the destructor on a std::process without a prior call to std::process::wait() would call std::terminate, which was in line with how std::thread behaves when join() or detach() weren’t called before destruction). On the other hand, C++ 20 will get std::jthread, which is almost like std:thread, but e.g. joins on destruction instead of terminating the program. We suggested to the paper author to change the default behavior of the destructor to either terminate the child process or detach it. And maybe a way to change the default behavior if that seemed necessary.

Then we discussed whether the C++ standard would need to say something about the child process’s progress, similar to the forward progress guarantees for std::thread. It seems that we cannot. If the child process is not written in C++, then it would anyway be outside of the standard to make any guarantees. We would not even be able to say that the child process “completes” after join() returns, because it might have forked surviving child processes itself. The process paper should say the same thing about forward progress of the child process as what the networking TS says about the forward progress of the “remote server”, which is probably nothing.

We continued the discussion about the way that std::process finds the executable. In the proposal, one would call std::process with an absolute path to the executable. One might use the helper std::this_process::environment::find_executable() to find the absolute path of a command according to shell rules (using PATH etc.). This split was necessary due to security concerns. An interesting question was brought up: The paper specified that std::process takes an absolute path, but it did not specify what would happen if a relative path is provided. The intention that an implementation should refuse those, e.g. by throwing an exception, should be explicitly specified.

Then we tackled another SG 1 core concern: Should objects of std::process be considered thread-safe, i.e. all const-methods are allowed to be called concurrently from different threads, or should we say that std::process is not thread-safe at all (which might be easiest). This discussion started on the std::process::running() method, which was specified as const, but at the same time had a comment saying that it might store the exit code of the child internally.

There will be more iterations on this proposal, both in SG 1 and LEWG. I hope it will eventually become part of C++.

LEWG – Integrating SIMD with parallel algorithms

The paper D0350R3 Integrating  SIMD with parallel algorithms does not target C++20 or C++23, but instead the Parallelism 2 TS. A Technical Specification (TS) is a way for the C++ committee to get implementation experience (compilers are more likely to implement a TS than “just” a paper), while still being able to make changes before moving features from a TS to the C++ standard. There is an overview of the current Technical specifications here.

The Parallelism 2 TS contains, among other things, support to exploit SIMD (Single instruction, multiple data) capabilities of modern processors. With the TS, one can write


which computes the scalar product of a multiple vectors at the same time. native_simd<float> contains as many floating point numbers as the native machine’s SIMD instructions support (e.g. 4 for SSE and 8 for AVX). Operations on this data type are directly mapped to the corresponding SIMD instructions. The paper D0350R3 want to add support for SIMD into the standard algorithms, like transform. One could then write code like


The std::execution::simd execution policy instructs the standard library to use SIMD for parallelization. It will divide the input array into multiple chunks of simd<float, ABI> (with different sizes) and call the lambda on each chunk. Imagine that the array has size 13, and the CPU supports SSE and AVX. Then transform will first call the lambda with an argument of type simd<float, AVX> (the native_simd, 8 elements), then with an argument of type simd<float, SSE> (next 4 elements) and then once with an argument of type float for the remaining one element.

For many algorithms, this change is straight-forward. For other algorithms, like generate (which fills a container with N values), it’s less obvious. How would the algorithm tell the callable how many values it needs to generate (i.e. which type of simd<T, ABI> it should return) – the callable of generate does not take arguments. Options were return a fixed size and discard unused elements, pass a dummy argument to provide the expected type, or require a templated lambda.

EWG – Zero-overhead deterministic exceptions: Throwing values

I shortly left LEWG to see Herb Sutter presenting P0709R3 Zero-overhead deterministic exceptions: Throwing values in EWG. This is an amazing approach to “fix” C++ exceptions, which is one of the two features of C++ that every compiler allows to disable – and many people do. In essence, the paper proposes an opt-in approach to throw statically typed exceptions, and propagate them through the return channel without the time and space overhead of traditional exceptions. I suggest to read the paper itself, it’s easy to read and contains a detailed motivation. Some polls were taken on directions for the paper, and it will be discussed again in future meetings.

LEWG – Support C atomics in C++

Back in LEWG, we discussed P0943 Support C atomics in C++.

The paper wants to allow C++ programs to include C language headers containing _Atomic variables. One can manually achieve that compatibility by manually declaring


but this is quite ugly. The proposal is to hide this ugliness in a new stdatomic.h header, which is currently not part of the C++ standard, but only part of the C standard.

At a previous meeting, there was a concern that _Atomic is an implementation defined name (underscore followed by an uppercase letter), and putting it in the standard would encourage developers to use those names. The paper was then updated to use atomic_generic_type(T), which would need to be coordinated with WG14 (the working group for the C language) to add the same name to C. During the Cologne meeting, SG1 asked the author to change the name back to _Atomic, and the discussion continued in LEWG.

Then we discussed if the header should be moved to Annex D “Deprecated features”. There were two main arguments: Move it to annex D to discourage the use in C++ code (it should only be used in C headers consumed by C++ code), or not move it to annex D because that would mean that we might remove the feature in a future standard and we are not planning to. To complicate matters, currently all C compatibility headers are in Annex D, and there is a paper that wants to undeprecate them. The room was split on this question.

We voted to forward the paper to LEWG.

LEWG – Integer Width Literals

The paper P1280R1 Integer Width Literals showed me how complex simple features can be in a language like C++. The paper proposes to introduce literals like i32 and u64 so one could easily obtain integer literals of a certain size and signedness:


When I first read the paper, I was slightly in favor of adding this feature. I could see its utility and there seemed to be no downsides to it. The discussion that followed changed my mind.

The first argument was that the “u” and especially the “i” could be hard to spot when the numbers did not only contain zeros and ones, for example 31i32. We explored some variations like 31i_32 and 31int32, but they had the same issue. The alternative of using uint32_t{31} is already in the language.

Then, somebody brought up that -1u8 will be of type int, which baffled me. It turns out that the precedence rules for literal suffixes are so that -1u8 is evaluated as -(1u8), and the negation would promote the type to int. This is in contrast to -1u which yields an unsigned int. One might argue that there is no need for negative unsigned constant (I would disagree), but what is even more surprising is that -1i8 also has the type int instead of uint8_t due to the same integer promotion. Eventually we voted against forwarding the paper to LWG for C++23.

LEWG – Printing volatile pointers

The next paper P1147 printing volatile pointers was a bug fix and not a feature addition. In short, the code


prints 0xdeadbeef and, surprisingly, 0. This happens because there is an overload of operator<< for void * and one for bool. But volatile void * matches the latter because it is not allowed to drop the volatile qualifier. We had consensus on adding an overloading taking const volatile void *, and possibly applying this fix retroactively to earlier standards.

LEWG – std::string::contains

The last paper of the day was P1679r0 String Contains function, which proposes to add std::string::contains() to replace




It would supplement the recently added std::string::starts_with and std::string::ends_with calls and provide some more convenience to C++.

We discussed having a member function versus having a free function, i.e. a range algorithm, contains(). An argument for the member function were readability (Is std::contains(“foobar”, “bar”) true or false? The room was split.). Arguments against the member functions were: It would enlarge the string interface, which needs to be reproduced on string_view and, in the future, on std::text. It might increase compile times. We already have std::search. It behaves differently than map::contains, which looks for a single element and not a subsequence.

The paper proposes the following set of overloads:


We clarified that we don’t require an overload taking a std::string because that implicitly converts into a basic_string_view. We have the overload taking a const char*, even though it also would implicitly convert into a basic_string_view, because this saves the string length computation in the basic_string_view constructor.

Eventually we had consensus on forwarding this paper to LWG for C++23 as is.

Closing note

Attending my first face-to-face ISO C++ meeting was an amazing experience. I learned many things, not only about C++ itself, but also how it is currently used and how it will be used in the future by some of the top C++ experts. Each discussion with them illuminated facets of a topic that I had not thought about before. I also learned that reaching consensus is hard work, but well worth it.

Attending my first face-to-face meeting also allowed me to join the internal email Lists (a.k.a. “Reflectors”), where committee members discuss in between meetings. If you haven’t attended yet, you can still join the public lists.

I’m now very much looking forward to CppCon. I would love to meet you there at our SLX FPGA poster or at my talk “Lifetime analysis for everyone” together with Gábor Horváth.


Matthias Gehre is a Senior Software Architect at Silexica GmbH. He completed his PhD in industrial mathematics from the University of Bremen, Germany in 2013; his interests include embedded systems and compilers. He is also involved in the LLVM and Clang open source communities.