Featured image for: Report from the virtual ISO C++ meetings in 2020 (core language).

The March C++ ISO Standard meeting this year was back in Jacksonville, Florida.  As usual, Red Hat sent three of us to the meeting: Torvald Riegel, Thomas Rodgers, and myself.  Jonathan Wakely attended via speakerphone.  There were 121 people attending the plenary meeting at the beginning of the week.

This meeting was mostly about new features for C++20, particularly when and how to merge Technical Specifications into the draft standard.  In the core language, the ones trying to make C++20 are Concepts (already partially merged), Coroutines, and Modules.  There was a lot of discussion around all three.

Concepts

Much of the Concepts discussion took place at an evening session Monday night.  Several papers were discussed there, largely trying to address the concerns that prevented the "natural" or "terse" syntax from the Concepts TS from being merged with the rest of the TS.

Two proposals dealt with the visual ambiguity when declaring a constrained template parameter: when you see template <MyConcept X>, the kind of template parameter X depends on the definition of MyConcept.  The proposals suggested that we treat concepts as "adjectives" that prefix the C++14 template parameter syntax, e.g. MyTypeConcept typename X or MyNumericConcept int Y.  There was not much enthusiasm for this direction.

Another paper proposed an "in-place" syntax to replace the constrained-type-specifier of the TS: When a concept-name in a declaration followed by braces containing one or more names, those names are declared as template type parameters.  So,

Integral{T} fn(T, Integral{U});

would be equivalent to:

template <typename T, typename U>
 requires Integral<T> && Integral<U>
 T fn(T,U);

This syntax is derived from the template-introduction syntax in the TS, but to be used anywhere, not just at the beginning of a declaration.  There was a lot of enthusiasm for this proposal.

There was also significant support for just adopting the syntax from the TS, with two adjustments. The first adjustment was to change the meaning of a concept-name used multiple times in a declaration.  For example:

Integral fn(Integral, Integral)

In the TS, all the uses of Integral name the same type.  The consensus at this meeting was that they should be different types, like auto parameters in generic lambdas.

The other adjustment was a yet-to-be-specified syntax that indicates that a declaration is a template, which many people continue to feel strongly about.  This group found the "in-place" syntax above to be sufficiently clear about that, but disapproved of making the braces optional.

Coroutines

There was a significant push to merge the Coroutines TS into the draft standard at this meeting, but there were also some concerns raised in one paper about various ways the design in the TS is problematic for the way they'd like to use the feature.  After significant discussion, the main sticking point seemed to be a question of how often heap allocation can be optimized away in coroutine usage.  The general agreement was that the authors of this paper would propose a change for the next meeting.  A proposal was also adopted to make it easier for a coroutine promise class constructor to access the arguments to the coroutine itself.

Modules

Many adjustments to the TS were proposed at this meeting.  Some of the changes continued with the clarification of what semantic properties of certain entities are exported as a consequence of exporting another entity.  Others involved the handling of macros and legacy headers, which have been a migration concern with the TS as published.  A proposal is expected at the next meeting to merge some of these adjustments into the Modules TS working paper.

New features

Several smaller new features were added to the draft standard at this meeting:

* P0840, the [[no_unique_address]] attribute, allowing users to get the empty base optimization using non-static data members instead of bases.
* P0780, allowing variadic lambda init-capture:

template<class... Args> void f(Args... args) {
  [...xs=std::move(args)] { return g(xs...); }();
}

* P0634, requiring the 'typename' keyword in fewer places, for example:

template<class T> T::R f(); // OK, return type of a function declaration at global scope

* P0479, adding [[likely]] and [[unlikely]] attributes to guide optimization.

And several more feature proposals went through some Core review:

* P0722, destroying operator delete, for better handling of classes allocated along with a fixed buffer.

* P0194, static (compile-time) reflection.  Reflection has been an area of study on the committee for some time, and several competing proposals are still being considered, but this was the first time one made it as far as CWG.

* P0542, contract-based programming.  Contracts have also been an area of study for a very long time, but it looks like they are finally getting close to going into the language.  This will allow code to formally specify the preconditions and postconditions of functions in a way that can be optionally checked at runtime:

void push(int x, queue & q)
  [[expects: !q.full()]]
  [[ensures: !q.empty()]]
{
  //...
  [[assert: q.is_valid()]];
  //...
}

* P0892, explicit(bool).  This proposal adds an optional expression to explicit, to allow libraries to more easily make a member function explicit or not based on the template arguments.

* P0482 proposes adding char8_t to go along with char16_t and char32_t, to distinguish UTF-8 characters from the existing char type.

* P0784 proposes extensions to constexpr to allow some uses of standard containers in constant expressions, including allowing matching new and delete expressions within a constant expression evaluation if they could be omitted under the C++14 rules.

Core issues

We also considered a number of issues raised against the standard. Some of the more widely interesting ones:

* Issue 2256: the lifetime of objects of trivial type should follow the same rules as non-trivial, rather than being tied to their storage duration.  GCC optimization has already moved in this direction.

* Issue 2219: The optimizer sees that unwinding to reach the catch handler will always call exit, so it removes the catch handler, so the EH runtime doesn't see a handler and calls terminate rather than unwind, so we don't actually call exit after all.  We discussed this for a while but didn't settle on an answer.  Now I wonder if it would work to turn such a handler into a catch(...).

The next meeting will be in June in Rapperswil-Jona, Switzerland.

Last updated: March 23, 2023