Trip Report: C++ Standards Meeting in Oulu, June 2016

Summary / TL;DR

Project What’s in it? Status
C++17 See below Committee Draft published; final publication on track for 2017
Filesystems TS Standard filesystem interface Published! Part of C++17
Library Fundamentals TS I optional, any, string_view and more Published! Part of C++17
Library Fundamentals TS II source code information capture and various utilities Resolution of comments from national standards bodies in progress
Concepts (“Lite”) TS Constrained templates Published! Not part of C++17
Parallelism TS vI Parallel versions of STL algorithms Published! Part of C++17
Parallelism TS v2 TBD. Exploring task blocks, progress guarantees, SIMD. Under active development
Transactional Memory TS Transaction support Published! Not part of C++17
Concurrency TS v1 future.then(), latches and barriers, atomic smart pointers Published! Not part of C++17
Concurrency TS v2 TBD. Exploring executors, synchronic types, atomic views, concurrent data structures Under active development
Networking TS Sockets library based on Boost.ASIO Wording review of the spec in progress
Ranges TS Range-based algorithms and views Wording review of the spec in progress
Numerics TS Various numerical facilities Under active development
Array Extensions TS Stack arrays whose size is not known at compile time Withdrawn; any future proposals will target a different vehicle
Modules TS A component system to supersede the textual header file inclusion model Initial TS wording reflects Microsoft’s design; changes proposed by Clang implementers expected. Not part of C++17.
Graphics TS 2D drawing API Design review in progress
Coroutines TS Resumable functions Initial TS wording reflects Microsoft’s await design; changes proposed by others expected. Not part of C++17.
Reflection Code introspection and (later) reification mechanisms Introspection proposal undergoing design review; likely to target a future TS
Contracts Preconditions, postconditions, and assertions Design review in progress. Not part of C++17.

Introduction

Last week I attended a meeting of the ISO C++ Standards Committee (also known as WG21) in Oulu, Finland. This was the second committee meeting in 2016; you can find my reports on previous meetings here (October 2015, Kona) and here (February 2016, Jacksonville), and earlier ones linked from those. These reports, particularly the Jacksonville one, provide useful context for this post.

This meeting was sharply focused on C++17. The goal of the meeting was to publish a Committee Draft (CD) of the C++17 spec. The CD is the first official draft that’s sent out for comment from national standards bodies; that’s followed up by one or more rounds of addressing comments and putting out a new draft, and then eventual publication.

The procedure WG21 follows is that the spec needs to be feature-complete by the time the CD is published, so that national standards bodies have adequate opportunities to comment on the features. That means this was the last meeting to vote new features into C++17, and consequently, all committee subgroups were focused on finishing work on C++17-bound features. (The Evolution and Library Evolution groups did have a chance of review some post-C++17 proposals as well, which I’ll talk about below.)

Work on Technical Specifications (TS’s) continued as well, but was noticeably muted due to the focus on C++17.

C++17

As I mentioned, this meeting was the “feature complete” deadline for C++17, so by now we know what features C++17 will contain. Let’s see what made it:

Features already in C++17 coming into the meeting

See my Jacksonville report for features that were already voted into C++17 coming into the Oulu meeting.

Features voted into C++17 at this meeting

Features that didn’t make C++17

In my Jacksonville report, I talked about several features that were proposed for C++17 at that meeting, but didn’t make the cut: Concepts (available instead as a published Technical Specification), Coroutines (targeting instead a work-in-progress Technical Specification), and unified function call syntax (abandoned altogether, at least for now).

At this meeting, a few additional features originally targeting C++17 were axed.

Default Comparisons

I predicted in my last report that default comparisons might turn out to be controversial, and it turned out to be quite so.

Concerns raised about this fell into several broad categories:

  • Some felt that automatically generating operator== and operator!= made conceptual sense, but generating operator< and its counterparts (operator>, operator<=, and operator>=) did not.
  • Some disliked the opt-out nature of the proposal (that you get the operators by default, and have to opt out if you don’t want them), and expressed a preference for an opt-in proposal (that you don’t get the operators by default, but can opt into them if you’d like). It was pointed out that the last time this preference was polled (at the November 2014 meeting), opt-in had a stronger consensus. (Why did the committee subsequently pursue an opt-out approach, then? I believe it was simply because the opt-out crowd continued to present updated versions of their proposal, while the opt-in crowd let their proposal languish. EWG then expressed consensus for the only proposal on the table – the opt-out one – over nothing at all.)
  • Several people in the Library groups expressed concern that they did not have an opportunity to assess the impact on the standard library, and make changes to standard library classes (such as adding opt-out declarations for relevant types) that might be necessary.
  • There were concerns that automatic generation of operator< meant that subsequent changes to the order of declaration of class members would change the semantics of the comparison.
  • There were concerns that the semantics of the generated operator< and its counterparts, were not a good fit for the low-level model of the language. The details here are a bit sublime (see the proposal wording if you’re interested, particularly around operator<=), but the gist is that the generator operators don’t behave exactly the same as a hand-written function would have in some respects.

As a result of the above concerns, the proposal did not gain consensus to go into C++17.

Most of the objections were to operator< and its counterparts; operator== and operator!= were, by comparison (no pun intended), relatively uncontroversial. The question of just moving forward with the operator== and != parts thus naturally came up. However, this met opposition from some who felt strongly that the two parts of the feature belonged together, and in any case there was no time to formulate a subsetted proposal and assess its implications. The feature thus missed the C++17 train altogether.

I fully expect default comparisons to come back for C++20, though perhaps with aspects of the design space (such as opt-in vs. opt-out) revisited.

Operator Dot

Operator dot also attracted a fair amount of controversy. Among other things, a late-stage alternative proposal which presented some concerns with the original design and offered an alternative, was brought forward.

In the end, though, it wasn’t these concerns that defeated the original proposal. Rather, wording review of the proposal revealed some design issues that the proposal authors hadn’t considered. The issues were related to how operator dot invocations fit into conversion sequences – when are they considered, relative to base-to-derived conversions, other standard conversions, and user-defined conversions. It was too late to hash out a consistent design that answered these questions “on the spot”, so the proposal was withdrawn from consideration for C++17.

I expect this, too, will come back for C++20, with the alternative proposal as possible competition. [Update: I’ve since seen Herb Sutter’s trip report, where he hints that operator dot may in fact come back for C++17, by means of a national body comment on the C++17 Committee Draft asking that it be included. This way of sneakily adding features into a C++ release after the official feature-complete deadline sometimes works, but the bar is high and of course there are no guarantees.]

(It’s worth keeping a mind that there is yet another proposal for operator dot, which was handed to the Reflection Study Group, because it was sufficiently powerful to serve as the basis for a full-blown code reification mechanism. There remains a minority who believe that all other operator dot proposals should be put on hold while we wait for the Reflection Study Group to get to developing that.)

Other Languages Features
  • Removing exception specifications. This was not controversial, the Library Working Group just ran out of time before it could finish reviewing the library parts of the wording for this.
Library Features
  • joining_thread, a proposed wrapper around std::thread whose destructor joins the thread if it’s still running, rather than terminating the program. A concern was brought forward that use of such a wrapper without sufficient care could lead to deadlocks and other more-subtle-than-termination misbehaviour. As a result, the proposal lost consensus to move forward. I personally don’t view this as a great loss; it’s straightforward for users to write such a wrapper themselves, and its absence in the standard library might prompt a prospective user to do enough research to become conscious of things like the deadlock issue that they otherwise may have overlooked.
  • Global swap() accepts unequal allocators. This proved to be controversial during discussions in the library working groups, and was postponed for further study post-C++17.
  • Generalize some sorting algorithms to more iterator types. This was postponed as well due to implementers preferring to have a chance to gain more experience with it.

Towards Publishing C++17

I mentioned that the primary goal of the meeting was to publish the Committee Draft or CD of C++17 for comment by national standards bodies. This goal was accomplished: the Committee voted to publish the C++17 Working Draft, with the approved proposals listed above merged into it, as the CD. (An updated Working Draft that includes the merges will be forthcoming within a few weeks of the meeting.)

This means C++17 is on track to be published in 2017! The “train” (referring to the 3-year release train model adopted by the Committee after C++11) is on schedule 🙂

What’s the status of other big-ticket features?

The features below weren’t in the limelight at this meeting, since they were previously decided not to target C++17, but as they’re very popular, I’ll give a status update on them all the same.

Concepts

The Concepts TS was published last year, but was not merged into C++17. What does that mean going forward? It means it has a Working Draft, available to accept fixes (and in fact one such fix was voted in at this meeting) and larger changes (one such larger change was already proposed, and more are expected in the near future).

This Working Draft can eventually be published in a form of the Committee’s choosing – could be as a Concepts TS v2, or merged into a future version of the International Standard (such as C++20).

Coroutines

Coroutines are targeting a Technical Specification which is still a work in progress. The initial contents of the TS are set to be the latest draft of Microsoft’s co_await proposal, but there are also efforts to generalize that into a form that can also admit other execution models, such as “stackful” coroutines. EWG looked briefly at this latter proposal, but otherwise no notable progress was made; everyone was busy with C++17.

Modules

Modules is in a similar state to Coroutines: there’s a proposal by Microsoft which is slated to become the initial content of a Modules TS, and there are proposed design changes pending resolution. As with Coroutines, no notable progress was made this week, the focus being on C++17 features.

Reflection

Reflection continues to be actively worked on by the Reflection Study Group, which met this meet to review the design of its chosen static introspection proposal; I talk about this meeting below.

Evolution Working Group

As has become my habit, I sat in the Evolution Working Group (EWG) for the duration of the meeting. This group spends its time evaluating and reviewing the design of proposed language features.

As with all groups, EWG’s priority this meeting was C++17, but, being “ahead in the queue” of other groups (language proposals typically start in EWG and then progress to the Core Working Group (CWG) for wording review, and if appropriate, the Library groups for review of any library changes in them), it also had time to review a respectable number of C++20 features.

I’ll talk about the C++17-track features first. (Being listed here doesn’t mean they made C++17, just that they were considered for it; see above for what made it.) In most cases, these were features previously approved by EWG, which were revisited for specific reasons.

  • Default comparisons. EWG looked at this proposal on three different occasions.

    First, the authors proposed removing the anti-slicing provisions added last time around (which would have made it ill-formed to slice an object during comparison and initialization), because implementation experience revealed that this broke too much code; this change was approved.

    Second, during CWG review of this feature, implementers expressed a preference for not requiring a diagnostic for the rule that says it’s ill-formed for a comparison of the same two types to resolve to a default-generated comparison operator at one point in a translation unit, and to a user-provided comparison at a later point; EWG rejected tis request, expressing a strong preference for requiring a diagnostic. (For what it’s worth, I fall strongly on the “require a diagnostic” side in this and similar situations. I’ve seen it happen too many times where a C++ programmer is bitten by implementation divergence regarding the diagnosis of a rule that the standard proclaims is “ill-formed; no diagnostic required”. I think we should vigorously limit this style of rule to situations where basically all implementations agree that the rule is prohibitively expensive to diagnose, and thus will not in practice diagnose it.)

    Finally, EWG looked at a proposal to restrict the default generation to operator== and != only, which became a general discussion of the feature, during which many of the concerns mentioned above were articulated. EWG considered several changes in direction, including dropping the default generation of operator< and counterparts as suggested, and moving to an opt-in approach, but neither of these gained consensus, and the unmodified design was submitted for a vote in plenary (which then failed, as I related above).
  • Operator dot came up in EWG twice.

    First, the group briefly clarified the name lookup semantics associated with types that have an operator dot. It was stressed that the guiding model for such types is that of smart references, and so reference semantics are emulated when possible. It was also clarified that operator dot does not capture sizeof (that is, sizeof(my_smart_reference) still returns the size of the reference wrapper type, and not of the referred-to type).

    Second, CWG sent the feature back to EWG to clarify the intended interaction with other types of conversions. During the resulting discussion, it was discovered that there are entire aspects of the design space that haven’t been nailed down yet in this area (specifically, in where applications of operator dot fit into conversion sequences), and that it’s too broad of an area to nail down on the spot. The proposal was therefore withdrawn for revision until the next meeting (and therefore withdrawn from C++17).

    The alternative proposal for operator dot that was newly submitted for consideration at his meeting wasn’t looked at. It wouldn’t have made C++17 even if chosen, so looking at it wasn’t a priority. The authors plan to refine it and hopefully present it at the next meeting.
  • EWG briefly looked at an interaction between if constexpr and generic lambdas. The source of the problem is the semantics of default captures for generic lambdas. C++ says that if a lambda has a default capture, then a variable named inside the lambda is covered by the capture only if the the occurrence of the name is an odr-use (this is a term of art that roughly means “a use requiring the variable to have allocated storage and an address”); however, for a generic lambda, whether a use of a name is an odr-use can depend on the arguments you’re calling the lambda with. This leads to an interaction with if constexpr where determining captures can require instantiating a branch of code whose instantiation would otherwise have been disabled by an if constexpr, leading to an undesirable hard error. EWG’s suggestion was to revise the default capture rules for generic lambdas, perhaps to always require named variables to be captured. (Meanwhile, the if constexpr proposal itself isn’t held up over this or anything.)
  • Two aspects of the inline variables proposal were revisited.

    First, in Jacksonville, a restriction was added that namespace-scope variables could only be inline if they were const; this was meant to discourage mutable global state. It was pointed out that this can be worked around in several ways (for example, by making the variable a reference to a mutable object, or by making it a variable template, which has no such restrictions). Rather than trying to stymie these workaround approaches, EWG decided to just lift the restriction, making the feature simpler.

    Second, EWG previously asked that constexpr imply inline. CWG pointed out that for namespace-scope variables, this would change the linkage of existing constexpr variables, breaking existing code. As a result, for namespace-scope variables, constexpr no longer implies inline (for static data members of classes, it still does, because there is no linkage issue there).

    A minority continued to oppose this proposal altogether, but they were overruled.
  • Template argument deduction for class templates came back to EWG because CWG pointed out a snag. The proposal allowed for “partial deduction”, which meant that you could provide a partial template argument list to a class template when declaring a variable, and have the remaining template arguments be deduced from the constructor arguments. For example:

    template <int N, typename T> struct small_vector { small_vector(T fill); };
    small_vector<3> v(42); // N is specified, T is deduced

    However, it was pointed out that in the presence of variadic templates, the “partial” argument list could also form a valid type in and of itself:

    tuple<string> t("waldo", 42, 2.0f);

    EWG felt it would be surprising for this to deduce tuple<string, int, float> given that tuple<string> is also a valid type.

    A suggestion was raised to force the caller to provide all template arguments, but use auto for the ones that should be deduced:

    tuple<string, auto, auto> t("waldo", 42, 2.0f);

    however the details remained to be pinned down, and EWG felt uncomfortable standardizing this approach on the spot, instead preferring to disallow partial deduction. Therefore, the feature is now restricted to the case where no template argument list is provided:

    tuple t(std::string("waldo"), 42, 2.0f); // all arguments deduced

  • template <auto N> was briefly revisited to resolve an issue where certain code examples involving deduction of a non-type template parameter from an array bound would become ambiguous. EWG requested that the deduction rules be tweaked to avoid introducing the ambiguity, at the expense of making the already complicated deduction rules even more so; see the paper for details.
  • A few design points about structured bindings came up for discussion.

    The most prominent issue was the choice of customization points for a non-aggregate user-defined type to opt into the mechanism. The current specification requires using the customization point for “tuple-like” types – that is, specializing std::tuple_size to return the number of elements, std::tuple_element to return the type of each element, and std::get to return a reference to each element. Several people, including the author of this paper, expressed a desire to have a customization point that’s more specific to this feature, and/or one that operated by ADL rather than by specialization. EWG was sympathetic to this, but believed that “tuple-like” should be at least one of the customization points (for consistency with operations like std::tuple_cat()), and that it’s good enough as the only customization point in C++17; the Library Evolution Working Group can then design a nicer customization point in the C++20 timeframe.

    The handling of bitfield elements also came up. The proposal specifies that the bound variables act like references to the elements of the structure, but you cannot form a reference to a bitfield. However, the customization point mechanism allows for std::get to return a proxy type that assigns through to the bitfield element; this was deemed a good enough solution until the problem of not being able to form a reference to a bitfield is addressed more comprehensively by the core language.
  • A new proposal for if statements with an initializer.

    The background to this proposal is that C++ already allows you to declare a variable in the condition of an if statement if the variable’s type is contextually convertible to bool. For example, you can do this:

    if (Type* foo = GetFoo()) {
    ...
    }

    Here, the body of the if statement will execute if foo is not nullptr.
    The advantage of doing this over declaring foo above the if statement is that the scope of foo is limited to the if statement (including any else block).

    However, often you have a type which is not contextually convertible to bool, but you still want to check something about it, and only use it if the check passes. This proposal allows you to do this while keeping the variable’s scope local to the if, by combining a declaration with an expression to test:

    if (Type foo = func(); foo.isOk()) {
    ...
    }


    EWG really liked this, so much that they made an exception to the previously-agreed rule that new features first proposed at this meeting wouldn’t be considered for C++17. Two small modifications were requested: that the feature work for if constexpr and switch as well.

  • Modernizing using-declarations, which allows a pack expansion to occur in a using-declaration. This was another brand new proposal that EWG made an exception for and approved for C++17, but unfortunately CWG did not have time to review the wording, and it thus did not make it in.

Now let’s move on to the C++20-track features. I’ll categorize these into my usual “accepted”, “further work encouraged”, and “rejected” categories:

Accepted proposals:

  • Comma omission and comma elision, a proposal to solve a long-standing issue with the preprocessor where it was very difficult to tell apart zero argument from one argument in a variadic macro (because macro arguments are delimited by commas, and zero arguments and one argument both have the property that they have no commas). Importantly, the author has run this by WG14 (the C Standards Committee), and they like the direction and would be open to adopting it in tandem with C++ – an important consideration because WG21 tries to avoid preprocessor divergence between C and C++ where possible.
  • A proposal to clean up an obscure corner of the type system, dubbed abominable function types. EWG left the specific cleanup approach up the discretion of the authors and CWG.
  • A proposal to define the semantics for certain new parallel execution policies, vector and wavefront policies. EWG sent this to CWG, with the caveat that compiler backend implementers should be present during the CWG review (as they would be involved in the implementation of such policies).
  • Designated initializers. This is a C feature that allows you to initialize a structure with a braced-init-list whose elements (“designators”) name the fields, like so:

    struct Point { int x; int y; };
    void foo(Point);
    ...
    foo({ .x = 2, .y = 3});

    The proposal was to adopt this feature into C++, with several important modifications, the most important one being that the order of the designators must match the declaration order of the fields.

    This solves, at least partially, one of the longest-standing incompatibilities between C and C++.

Proposals for which further work is encouraged:

  • Regular void, a proposal to remove most instances of special-case treatment of void in the language, making it behave like any other type. The general idea enjoyed an increased level of support since its initial presentation two meetings ago, but some details were still contentious, most notably the ability to delete pointers of type void*. The author was encouraged to come back with a revised proposal, and perhaps an implementation to help rule out unexpected complications.
  • Return type deduction and SFINAE, which aims to solve the problem that if a function uses C++14 return type deduction, the return expression is no longer in the immediate context of the call, and so errors in the return expression become hard errors rather than substitution failures. (By contrast, in C++11, the return expression would typically appear in the signature as decltype(return-expr), bringing it into the immediate context of the call.) The proposed solution is to extend SFINAE to function bodies in certain contexts, with some important limitations to avoid some of the problems with extending SFINAE to function bodies in general (such as the need to mangle the function’s body into the name).

    As expected, implementers were quite wary to extend SFINAE; they were cautiously open to some form of limited extension, but the precise limitations remain to be nailed down. The author was encouraged to continue discussing this with a wide variety of compiler implementers, to arrive at a solution that everyone was on board with.
  • A [[visible]] attribute, intended to be a standard version of things like __declspec(dllexport) and __attribute__((visibility("default"))). EWG recognized that there’s a widespread desire for something like this, but also noted that interactions with Modules should be considered, and suggested further exploration of the problem space.
  • Product type access. This is a proposed extension to structured bindings that would expose the mechanism by which an aggregate structure type is decomposed into its fields to user code, by having such structures “automagically” implement the customization point used to opt non-aggregate user-defined types into structured binding. EWG agreed that this is desirable, but preferred to wait until the design of that customization point is nailed down in the C++20 timeframe (see above for more discussion of this).
  • Overload sets as function arguments, which allows passing an overloaded name (that is, a name that resolves to a function template, or to a set of overloaded functions and/or function templates) as argument for a function parameter whose type is a type template parameter; the compiler “packages” the name into a function object with a call operator whose body dispatches the call to the overload set. You can write such a function object today (along the lines of [](auto... args){ f(args...); }), but this proposal would allow you to just write f instead.

    EWG liked the proposal, but recognized an important problem. In the case where f resolves to a single, non-template function, you can pass it as an argument today, and it becomes a pointer to the target function. Changing this (to “package” f into a function object as in the case where it’s an overloaded name) would break existing call sites, so the proposal restricts the “packaging” to the case where it is in fact an overloaded name. But this means that, for a given call site that passes f, whether or not it gets “packaged” depends on how many declarations of f are in scope at the call site. As argued in this paper, this can depend on what headers are included prior to the call site, which can vary from one translation unit to another, making it very easy to get into ODR violations. EWG agreed that to avoid this, a new syntax is necessary: []f will always package the name f into a function object, no matter how many declarations of f are in scope (a plain f, meanwhile, will retain its existing meaning of “function pointer if f is a single non-template function, ill-formed otherwise”).
  • Generalized member pointers, which proposes three extensions to C++’s pointers to members: upcasting the member’s owning type (assuming the target of the cast is a non-virtual base); forming pointers to members of members; and forming pointers to bases. EWG liked these extensions, and requested that the author confirm implementability on major compilers, and come back with wording. A fourth extension, to recover a pointer to the object from a pointer to a member of the object, was deferred, to be pursued as a separate proposal. It was pointed out that the first three features could conceivably be extended to pointers to member functions (albeit non-virtual ones) as well.
  • Bitfield default member initializers, which proposes a syntax for giving bitfield members a C++11-style default member initializer (something that’s currently forbidden). The challenge here is to find a syntax that combines the bitfield width specifier and the default member initializer in a way that’s not ambiguous. This specific proposal tried to avoid introducing new syntax, instead just introducing disambiguation rules, but EWG felt the proposed rules were unintuitive, and suggested the author try inventing new syntax instead.
  • Comparing virtual methods. This is not a proposal, just a statement of a problem, namely that sometimes you want to efficiently check whether a class instance has a particular function in its vtable, but the language doesn’t currently provide a way to do this without incurring the full overhead of a virtual function call. EWG encouraged the author to pursue a solution.
  • Lambdas in unevaluated context, which proposes to lift the restriction that lambdas cannot appear inside a decltype(). EWG approved of the intent, but the exact rules need nailing down to ensure that we don’t allow situations that would require mangling a lambda expression into a symbol name.
  • Language support for heterogeneous computing. The language changes for which feedback was specifically solicited mostly concerned the use case of a host device sending code in the form of a lambda to be run on a special-purpose device such as a GPU or accelerator. To this end, the standardization of various aspects of the representation of such lambdas was proposed, so that code on the two devices, potentially compiled by different compilers, can interoperate. EWG’s feedback was to leave these representational aspects up to the implementation; the implication is that, if the host code and the device code are in fact compiled by different compilers, those compilers need to agree amongst themselves (much like how linking code from different compilers together requires that they agree on an ABI).

    The broader proposal to support heterogeneous computing in standard C++ is based on SYCL; it was suggested that the authors also look at CUDA.
  • Pattern matching and language variants is an ambitious proposal to add a first-class pattern matching facility to the language, which can, among other things, deconstruct sum and product types; customization points to opt user-defined sum and product types into pattern matching; and a variant type built into the language (which models a sum type).

    EWG had two main pieces of feedback: first, that you should be able to write patterns that match specific derived types when the input is a base type (relevant prior work on that can be found here); and second, that use of the pattern matching facility should not come with a performance penalty.

    It’s important to note that the proposed built-in variant type would not supersede std::variant, which was standardized at this meeting. It would have named alternatives, which means it would be to std::variant as structs are to std::tuple.
  • Contracts, which provides a way to express preconditions, postconditions, and assertions in the language. The general direction of the proposal was approved at the last meeting; the discussion today mostly concerned details. For example, it was pointed out that specifying the different contract checking modes will be tricky, as there is no precedent in the standard for specifying things that are in practice controlled by compiler flags. Some argued that requiring every declaration of a function to state the pre- and post-conditions is too strict, and having them on the first declaration should be sufficient; in particular, EWG didn’t want contracts to become a de facto part of the type system the way exception specifications have. Finally, some details, such as when postconditions are checked relative to local destructors running, remain to be nailed down.
  • An updated version of the proposal to unify stackful and stackless coroutines under a common model and common syntax was presented. The idea is that the new syntax can be lowered to the same low-level “stackless” representation as the co_await syntax, and thus can have the same performance characteristics (for example, avoiding dynamic memory allocation in many cases), but can also be used in a stackful way when the flexibility offered by that (for example, the ability to suspend from inside code that the compiler hasn’t transformed into a coroutine representation) is desired. The machinery that would enable this to work is that the compiler, guided by “suspendable” annotations, would compile a function either normally, or into a coroutine representation (or both), much as how in the Transactional Memory TS, the compiler can create transaction-safe and transaction-unsafe versions of a function. The usual questions of whether such “suspendable” annotations are part of a function’s type, and what the impact of this bifurcation of the set of generated functions on binary size and linking time is, came up. The conclusion was that the area needs more exploration, and implementation experience would be particuarly helpful.
  • Parameter packs outside of templates, an early-stage proposal to expand the functionality of parameter packs to act as a general type list facility at compile-time, and as a building block for a tuple of values at runtime. EWG liked the goals and use cases, but expressed a desire for a cleaner syntax (the syntax proposed in the paper has ... tokens sprinkled around fairly liberally). It was also pointed out that the proposal has the potential to bring significant compile-time gains by reducing the amount of metaprogramming required to achieve common tasks, and care should be taken to ensure this potential is realized to its fullest extent.
  • File literals, a new type of literal that would name a file, and evaluate to the contents of the file. The main motivating use case is embeddeding external resources (files) into an executable and making it available to the program code in a portable way. Discussion revealed that this feature would be quite a bit more powerful than necessary to achieve this use case, as the contents of the file would be made available for compile-time processing. EWG preferred to avoid this without compelling motivation, and suggested the author formulate the feature as a new type of declaration whose initialization loads the file content at runtime / program load time.

Rejected proposals:

  • Comparison operators in fold-expressions, which proposed to disallow fold-expressions of the form a <... b, because their expansion (a_1 < a_2 < ... < a_n < b if a is the pack) is almost certainly not what the user wants (in C++, a < b < c means “compare a and b, and then take the boolean result of that comparison and compare it to c“, not (a < b) && (b < c)). EWG’s view was that we don’t gain much by disallowing them; what we’d really want to do is specify them to do the sensible thing, but we can’t do that without also changing what a < b < c means (because consistency), and it’s too late to change that (because backwards compatibility).
  • Proposals to make aggregate types hashable and swappable by default. These were withdrawn by the author, in favour of library solutions building upon the extension mechanism provided by structured bindings.
  • An [[exhaustive]] attribute for enumerations, which tells the compiler this is an enumeration whose instances should only take on the value of one of the specified enumerators (as opposed to an enumeration that functions as a bit vector, or an enumeration whose set of values is meant to be extended by the user), and thus guides compiler diagnostics. EWG pointed out that most enumerations are exhaustive, and we want to be annotating the exception, not the norm. However, there wasn’t much appetite for a “non-exhaustive” annotation, either; implementers were of the opinion that current heuristics for diagnostics for switch statements on enumerations are sufficient.
  • Making pointers to members callable. This would have allowed a pointer-to-member-function pmem to be called as pmem(obj, args) in addition to the current obj.*pmem(args). There was no consensus for making this change; the opposition was mainly over the fact that this looked like a step towards unified function call syntax, which was rejected at the last meeting (even though it was pointed out that this proposal is rather unlike unified call syntax, as it does not change any lookup ryules).
  • Allowing any unsigned integral type as parameter type for literal operators, instead of just unsigned long long. This was rejected, mostly because no one could remember what the original reason was for the restriction, and people were hesitant to move forward without knowing that.
  • A new char8_t character type to represent UTF-8 data. EWG was of the opinion that this would be doable, but it would come with some cost (for example, it would be an ABI-breaking change for code that currently typedefs char8_t to be (unsigned) char, similar to how the introduction of the C++11 char16_t and char32_t types was; there is also code in the wild that uses char8_t as a completely different kind of type (e.g. an empty “tag” structure), which would break more severely). The suggested way forward was for the Library Evolution group to decide on a strategy for handling UTF-8 data (perhaps the answer is “just use std::string“, in which case no language changes are needed), and then see what the language can do to help accommodate that design.
  • const inheritance, a new form of inheritance that would allow the derived class to access the base class only through a const reference. EWG did not find this change to be sufficiently motivated.
  • Partial classes, an attempt to solve the problem of a header file containing a class declaration needing to include declarations of types used in private data members and functions, by splitting out a public interface from a class declaration, and allowing most uses of the class with just the interface declaration being included. EWG found several problems with the mechanics of the proposal, and pointed out that Modules, while not solving this problem directly, should address the ultimate goal (reducing compile times) sufficiently.
  • Timing barriers, which attempted to address an interesting problem. In the following code:

    auto start = std::chrono::high_resolution_clock::now(); // #1
    operation_to_be_timed(); // #2
    auto end = std::chrono::high_resolution_clock::now(); // #3

    there is nothing that currently prevents the implementation from re-ordering the statements such that e.g. #1 happens after #2, rendering the code useless. The proposal attempted to introduce a mechanism that would prevent such a re-order, but it was pointed out that such a mechanism is basically unimplementable without completely tying the optimizer’s hands. The reason is explained very nicely in this StackOverflow answer, but to summarize: conceptually, clock::now() observes the current time; to make sure that a particular operation X is not re-ordered with clock::now(), that operation needs to be annotated as modifying the current time ; but every operation “modifies the current time” by taking time to run, so every operation would need to be so annotated. That same StackOverflow answer points to some practical ways to solve this problem.
  • A different take on unified call syntax, where the unified call semantics is opted into on a per-callsite basis, by changing the syntax of the call site to prefix the function name with a dot (.). EWG felt that having to modify call sites defeated the purpose.

There were a few C++20-track proposals that weren’t looked at because no one was present to champion them, or because there was no time. They’ll be picked up in due course.

Library / Library Evolution Working Groups

As I’ve been sitting in EWG for the week, I didn’t have a chance to follow the library side of things very closely, but I’ll mention some highlights.

First, obviously the Library Working Group (LWG) has been quite busy reviewing the wording for the variety of library proposals voted into C++17 at the end of the meeting. A particularly notable success story is variant being standardized in time for C++17; that proposal has come a long way since two meetings ago, when no one could agree on how to approach certain design issues. (There are still some who are unhappy with the current “valueless by exception” state, and are proposing inventive approaches for avoiding it; however, the consensus was to move forward with the current design.)

A notable library proposal that was pulled from C++17 is joining_thread, for reasons explained above.

The Library Evolution Working Group (LEWG) mostly looked at post-C++17 material (see here for a list of proposals). LEWG’s view on ship vehicles has shifted a bit; until recently, most proposals ended up targetting a Technical Specification of some sort (often one in the Library Fundamentals series), but going forward LEWG plans to target the International Standard directly more often. As a result, a lot of the proposals reviewed at this meeting will likely target C++20.

There are also various library Technical Specifications in flight.

The Library Fundamentals TS v2 has had a draft sent to national standards bodies for comment, and now needs these comments to be addressed prior to final publication. It was originally intended for this process to be completed at this meeting, but with the heavy focus on C++17, it slipped to the next meeting.

LEWG spent a significant amount of time reviewing the Graphics TS, which will provide 2D graphics primitives to C++ programmers. The proposal is progressing towards being sent onwards to LWG, but isn’t quite there yet.

Not much progress has been made on the Ranges TS and the Networking TS, again due to the focus on C++17. LEWG did review a proposal for minor design changes to the Ranges TS, and approved some of them.

Study Groups

Due to the meeting’s emphasis on C++17, most study groups did not meet, but I’ll talk about the few that did.

SG 1 (Concurrency)

SG 1 spent some time reviewing concurrency-related proposals slated for C++17, such as the handling of exceptions in parallel algorithms, as well as advancing its post-C++17 work: the second revisions of the Concurrency and Parallelism Technical Specifications.

The Concurrency TS v2 is slated to contain, among other things, support for floating-point atomics, atomic_view, a synchronized value abstraction, and emplace() for promise and future.

The Parallelism TS v2 is expected to contain a low-level library vector facility (datapar), as well as more forward-looking things (see proposals tagged “Concurrency” here).

SG 7 (Reflection)

SG 7 met for an evening session to provide design feedback on an updated version of the proposal that was chosen as the preferred approach for static code introspection at the last meeting.

The discussion focused most heavily on a particular design choice made by the proposal: the ability to reflect typedefs as separate from their underlying types (so for example, the ability for the reflection facility to distinguish between a use of std::size_t and a use of unsigned long, even if the former is a typedef for the latter). The issue is that the language does not treat typedefs as semantic entities in their own right, and a reflection facility pretending that it does can result in a variety of inconsistencies.

A prominent example is the treatment of template instantiations: if size_t is a typedef for unsigned long, then vector<size_t> and vector<unsigned long> are the same instantiation. Consider an implementation which has already instantiated vector<unsigned long>, and now encounters vector<size_t>; it resolves the size_t, realizes that it already has the instantation being referred to, and uses that instantiation. In this process, the information that this particular use of the instantiation originated from a place where the type argument was spelt size_t, can be lost.

The conclusion that the group tended towards was that desired uses of typedef reflection fall into two categories:

  • Cases where you want typedefs to be preserved in a type name because e.g. you’re presenting it to the user. For these cases, the reflection facility can provide a method like get_sugared_name() which makes a best-effort attempt to return a readable type name that has typedefs preserved (but makes no guarantees). This would be similar to how type names are printed in compiler diagnostics.
  • Cases where you truly want the typedef to be a distinct semantic entity. For these cases, the language needs a proper “strong typedef” facility (many have been proposed over time, most recently this one), and reflection can expose that if and when such a facility is added.

It was established at previous meetings that the proposal should built upon a type-list library facility; the beginnings of one are now in the works (being developed outside of SG 7).

Most interestingly, the proposal author hinted at what a future extension of the proposal to cover the other aspect of static reflection, reification (also called interface synthesis) could potentially look like:


// Current facility: call "reflexpr" on an entity to introspect it, yielding a
// compile-time meta-object (i.e. a type) that encodes information about the entity:
typedef reflexpr(int) MetaInt;

// Future extension: call "reflexpr" again on a meta-object to reify it:
typedef reflexpr(MetaInt) ReifiedInt; // ReifiedInt = int

In the above example, we just reify an unmodified meta-object, getting back the same entity that was originally introspected, but you can imagine performing modifications to the meta-object (such as adding methods to a type), and reifying the result, producing a new entity that doesn’t exist elsewhere in the code.

A ship vehicle for Reflection has not been chosen yet, but the general expectation is that it will be a Technical Specification.

SG 14 (Game Development & Low-Latency Applications)

SG 14 aims to engage with the game development and low-latency application developemnt (including finance/trading) communities – communities which have historically eschewed aspects of standard C++, such as exception handling, due to their unique performance constraints – to try to bring them into the standard C++ fold.

The group is very active, meeting during C++ standards meetings (including an evening session at this meeting), as well as at other events such as GDC, CppCon, and Meeting C++; it also holds regular teleconferences between in-person meetings.

Lately, members of the embedded computing community have expressed interest in the group’s work as well, as they share many of the same goals.

One of the foremost problems the group has been tackling is making exception handling performant enough to be palatable to these developer communities, or coming up with an alternative error handling mechanism that is. This report is a good summary of the latest efforts in this area, listing no fewer than 11 possible proposal ideas.

One thought that keeps coming up, in SG 14 but also in other contexts (such as the variant issue), is that the reason almost any operation in C++ can throw is that C++ tries to treat memory allocation as a recoverable failure, throwing std::bad_alloc on memory exhaustion. However, this rarely ends up being useful; for example, due to the way OSes overcommit memory, often memory exhaustion results in a crash before the memory allocator would nominally run out of memory and throw bad_alloc. If memory exhaustion weren’t treated as a recoverable error, the remainder of exceptional situations could much more easily be contained to a small portion of interfaces.

I haven’t had a chance to follow the rest of SG 14’s work in detail; if you’re interested, I encourage you to look at this overall status report, and to follow the group’s mailing list.

Next Meeting

The next meeting of the Committee will be in Issaquah, Washington (an hour outside Seattle), the week of November 7th, 2016.

Conclusion

This meeting saw the feature-completion of C++17. While some big-ticket items like modules did not make the train, I think the feature set we ended up with is pretty respectable, and the new language version will improve C++ programmers’ lives in significant ways.

I’m also very excited about what’s in the pipeline for post-C++17: modules, coroutines, ranges, contracts, reflection, and more facilities for concurrency and parallelism, among other things. Stay tuned for further reports!

Other Trip Reports

Herb Sutter’s trip report makes a good read as well, as does Michael Wong’s (in particular, these slides are a great overview of the current status of C++ standardization).

11 thoughts on “Trip Report: C++ Standards Meeting in Oulu, June 2016

  1. Hi,
    Is the link target correct in this item?
    “Making it easier to use attributes that an implementation doesn’t support”
    It points to the “Return type deduction and SFINAE” proposal.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s