Summary / TL;DR
Project | What’s in it? | Status |
C++17 | See below | Publication imminent |
Library Fundamentals TS v2 | source code information capture and various utilities | Published! |
Concepts TS | Constrained templates | Merged into C++20 with some modifications |
Parallelism TS v2 | Task blocks, library vector types and algorithms and more | Nearing feature-completion; expect PDTS ballot at next meeting |
Transactional Memory TS | Transaction support | Published! Not headed towards C++20 |
Concurrency TS v1 | future.then() , latches and barriers, atomic smart pointers |
Published! Parts of it headed for C++20 |
Concurrency TS v2 | See below | Under active development |
Networking TS | Sockets library based on Boost.ASIO | Publication imminent |
Ranges TS | Range-based algorithms and views | Publication imminent |
Coroutines TS | Resumable functions, based on Microsoft’s await design |
Publication imminent |
Modules TS | A component system to supersede the textual header file inclusion model | Resolution of comments on Proposed Draft in progress |
Numerics TS | Various numerical facilities | Under active development; no new progress |
Graphics TS | 2D drawing API | Under active design review; no new progress |
Reflection | Code introspection and (later) reification mechanisms | Introspection proposal awaiting wording review. Targeting a Reflection TS. |
Contracts | Preconditions, postconditions, and assertions | Proposal under wording review |
Some of the links in this blog post may not resolve until the committee’s post-meeting mailing is published (expected within a few days of November 27, 2017). If you encounter such a link, please check back in a few days.
Introduction
A couple of weeks ago I attended a meeting of the ISO C++ Standards Committee (also known as WG21) in Albuquerque, New Mexico. This was the third committee meeting in 2017; you can find my reports on previous meetings here (February 2017, Kona) and here (July 2017, Toronto). These reports, particularly the Toronto one, provide useful context for this post.
With the final C++17 International Standard (IS) having been voted for publication, this meeting was focused on C++20, and the various Technical Specifications (TS) we have in flight, most notably Modules.
What’s the status of C++17?
The final C++17 International Standard (IS) has been sent off for publication in September. The final document is based on the Draft International Standard (DIS), with only minor editorial changes (nothing normative) to address comments on the DIS ballot; it is now in ISO’s hands, and official publication is imminent.
In terms of implementation status, the latest versions of GCC and Clang both have complete support for C++17, modulo bugs. MSVC is said to be on track to be C++17 feature-complete by March 2018; if that ends up being the case, C++17 will be quickest standard version to date to be supported by these three major compilers.
C++20
This is the second meeting that the C++20 Working Draft has been open for changes. (To use a development analogy, think of the current Working Draft as “trunk”; it was opened for changes as soon as C++17 “branched” earlier this year). Here, I list the changes that have been voted into the Working Draft at this meeting. For a list of changes voted in at the previous meeting, see my Toronto report.
- Language:
- The most significant new feature voted in was
operator<=>
, known colloquially as the “spaceship operator”. This proposal modernizes the way comparison operators (relational and equality) work in the language; it allows developers to define efficient comparison operators for their user-defined types without unnecessary verbosity, while also surfacing some details, such as the distinction between total vs. partial order, that have previously been swept under the rug. The “spaceship” refers to the new three-way comparison operator,<=>
, that the proposal adds, which the usual two-way comparison operators now delegate to. Note that, unlike the “default comparisons” proposal that failed to gain consensus for C++17, this is an opt-in proposal; you can opt your user-defined type into all comparison operators with a single line of code, but you do need to write that line – it doesn’t happen by default. - Range-based
for
statements with initializer. This makes the ranged-basedfor
loop consistent with other control flow statements likeif
andwhile
which gained the ability to contain a variable initialization before their condition in C++17. - Lambdas is unevaluated contexts. This removes the long-standing restriction against having a lambda-expression inside a
decltype
in many contexts. - Default constructible and assignable stateless lambdas. This lifts another unnecessary restriction on lambdas.
- Simplifying implicit lambda capture. This is a language-lawyer-y change that’s probably not of much interest to the average user, but it does fix the so-called “lambda capture paradox” where the type of generic lambda could depend on what variables it captures, but whether or not it captures a particular variable could depend on its type.
- Fixing small functionality gaps in constraints. When Concepts was merged into C++20 at the last meeting, some parts of it (abbreviated function templates (AFTs) and other features that relied on constrained-type-specifiers) were held back due to being controversial. The “cut” was made in some haste, resulting in some minor non-controversial features being excised as well; this proposal resolves that. Note that one such feature listed in the original proposal – the ability to write generic functions using
auto
, like in lambdas – was axed because people felt it’s sufficiently related to AFTs that they should be discussed together. (Were AFTs discussed, you ask? Not at this meeting; see below.) - Deprecating the notion of “plain old data” (POD). It has been replaced with two more nuanced categories of types, “trivial” and “standard-layout”. “POD” is equivalent to “trivial and standard layout”, but for many code patterns, a narrower restriction to just “trivial” or just “standard layout” is appropriate; to encourage such precision, the notion of “POD” was therefore deprecated. The library trait
is_pod
has also been deprecated correspondingly. - Access checking on specializations. This is a minor tweak related to generic programming.
const
mismatch with defaulted copy constructor. This fixes a minor wart involving the composability of types with defaulted special member functions. I co-authored this proposal, having run into the issue trying to store annsAutoPtr
in astd::tuple
; it is my first proposal to make it into the C++ Working Paper!- ADL and function templates that are not visible. This is another minor fix concerning calls to function templates with explicit template parameters where the function template’s name is found via argument-dependnet lookup.
- Core issue 1581: when are
constexpr
member functions defined? This tweak unblocks the standard library making more member functionsconstexpr
.
- The most significant new feature voted in was
- Library:
- Transformation trait
remove_cvref
- Treating unnecessary
decay
- Using
nodiscard
in the standard library - Make
std::memory_order
a scoped enumeration - Synchronized buffered
ostream
- A utility to convert pointer-like objects to raw pointers
- Add
constexpr
modifiers to functions in<algorithm>
and<utility>
headers. (Naturally, algorithms that can allocate dynamic memory, likestd::stable_sort
, are not included, since you can’t do dynamic allocation during constant expression evaluation (yet)). constexpr
forstd::complex
- Atomic
shared_ptr
- Floating-point atomics
- De-pessimize legacy
<numeric>
algorithms withstd::move
- String prefix and suffix checking, i.e.
starts_with()
andends_with()
- Transformation trait
Technical Specifications
In addition to the C++ International Standard, the committee publishes Technical Specifications (TS) which can be thought of “feature branches” (to continue the development analogy from above), where provisional specifications for new language or library features are published and the C++ community is invited to try them out and provide feedback before final standardization.
At the last meeting, we published three TSes: Coroutines, Ranges, and Networking. The next steps for these features is to wait for a while (usually at least a year) to give users and implementers a chance to try them out and provide feedback. Once we’re confident the features are ripe for final standardization, they will be merged into a future version of the International Standard (possibly C++20).
Modules TS
The Modules TS made significant progress at the last meeting: its Proposed Draft (PDTS) was published and circulated for balloting, a process where national standards bodies evaluate, vote on, and submit comments on a proposed document. The ballot passed, but numerous technical comments were submitted that the committee intends to address before final publication.
A lot of time at this meeting was spent working through those comments. Significant progress was made, but not enough to vote out the final published TS at the end of the meeting. The Core Working Group (CWG) intends to hold a teleconference in the coming months to continue reviewing comment resolutions. If they get through them all, a publication vote may happen shortly thereafter (also by teleconference); otherwise, the work will be finished, and the publication vote held, at the next meeting in Jacksonville.
I summarize some of the technical discussion about Modules that took place at this meeting below.
The state of Modules implementation is also progressing: in addition to Clang and MSVC, Facebook has been contributing to a GCC implementation.
Parallelism TS v2
The Parallelism TS v2 is feature-complete, with one final feature, a template library for parallel for
loops voted in at this meeting. A vote to send it out for its PDTS ballot is expected at the next meeting.
Concurrency TS v2
The Concurrency TS v2 (no working draft yet) continues to be under active development. Three new features targeting it have received design approval at this meeting: std::cell
, a facility for deferred reclamation; apply()
for synchronized_value
; and atomic_ref
. An initial working draft that consolidates the various features slated for the TS into a single document is expected at the next meeting.
Executors, slated for a separate TS, are making progress: the Concurrency Study Group approved the design of the unified executors proposal, thereby breaking the lockdown that has been holding the feature up for a number of years.
Stackful coroutines continue to be a unique beast of their own. I’ve previously reported them to be slated for the Concurrency TS v2; I’m not sure whether that’s still the case. They change the semantics of code in ways that impacts the core language, and thus need to be reviewed by the Evolution Working Group; one potential concern is that the proposal may not be implementable on all platforms (iOS came up as a concrete example during informal discussion). For the time being, the proposal is still being looked at by the Concurrency Working Group, where there continues to be strong interest in standardizing them in some form, but the details remain to be nailed down; I believe the latest development is that an older API proposal may end up being preferred over the latest call/cc
one.
Future Technical Specifications
There are some planned future Technical Specifications that don’t have an official project or working draft yet:
Reflection
The static introspection / “reflexpr
” proposal (see its summary, design, and specification for details), headed for a Reflection TS, has been approved by the Evolution and Library Evolution Working Groups, and is awaiting wording review. The Reflection Study Group (recently renamed to “Compile-Time Programming Study Group”) approved an extension to it, concerning reflection over functions, at this meeting.
There are more reflection features to come beyond what will be in the static introspection TS. One proposal that has been drawing a lot of attention is metaclasses, an updated version of which was reviewed at this meeting (details below).
Graphics
I’m not aware of much new progress on the planned Graphics TS (containing 2D graphics primitives inspired by cairo) since the last meeting. The latest draft spec can be found here, and is still on the Library Evolution Working Group’s plate.
Numerics
Nothing particularly new to report here either; the Numerics Study Group did not meet this week. The high-level plan for the TS remains as outlined previously. There are concrete proposals for several of the listed topics, but not working draft for the TS yet.
Other major features
Concepts
As I related in my previous report, Concepts was merged into C++20, minus abbreviated function templates (AFTs) and related features which remain controversial.
I also mentioned that there will likely be future proposals to get back AFTs in some modified form, that address the main objection to them (that knowing whether a function is a template or not requires knowing whether the identifiers in its signature name types or concepts). Two such proposals were submitted in advance of this meeting; interestingly, both of them proposed a very similar design: an adjective syntax where in an AFT, a concept name would act as an adjective tacked onto the thing it’s constraining – most commonly, for a type concept, typename
or auto
. So instead of void sort(Sortable& s);
, you’d have void sort(Sortable& auto s);
, and that makes it clear that a template is being defined.
These proposals were not discussed at this meeting, because some of the authors of the original Concepts design could not make it to the meeting. I expect a lively discussion in Jacksonville.
Now that Concepts are in the language, the question of whether new library proposals should make use of them naturally arose. The Library Evolution Working Group’s initial guidance is “not yet”. The reason is that most libraries require some foundational concepts to build their more specific concepts on top of, and we don’t want different library proposals to duplicate each other / reinvent the wheel in that respect. Rather, we should start by adding a well-designed set of foundational concepts, and libraries can then start building on top of those. The Ranges TS is considered a leading candidate for providing that initial set of foundational concepts.
Operator Dot
I last talked about overloading operator dot a year ago, when I mentioned that there are two proposals for this: the original one, and an alternative approach that achieves a similar effect via inheritance-like semantics.
There hasn’t been much activity on those proposals since then. I think that’s for two reasons. First, the relevant people have been occupied with Concepts. Second, as the reflection proposals develop, people are increasingly starting to see them as a more general mechanism to satisfy operator dot’s use cases. The downside, of course, is that reflection will take longer to arrive in C++, while one of the above two proposals could plausibly have been in C++20.
Evolution Working Group
I’ll now write in a bit more detail about the technical discussions that took place in the Evolution Working Group, the subgroup that I sat in for the duration of the week.
All proposals discussed in EWG at this meeting were targeting C++20 (except for Modules, where we discussed some changes targeting the Modules TS). I’ve categorized them into the usual “accepted”, “further work encouraged”, and “rejected” categories:
Accepted proposals:
- Standardizing feature test macros (and another paper effectively asking for the same thing). Feature test macros are macros like
__cpp_lambdas
that tell you whether your compiler or standard library supports a particular feature without having to resort to the more indirect approach of having a version check for each of your supported compilers. The committee maintains a list of them, but they’re not an official part of the standard, and this has led some implementations to refuse to support them, thus significantly undermining their usefulness. To rectify this, it was proposed that they are made part of the official standard. This was first proposed at the last meeting, but failed to gain consensus at that time. It appears that people have since been convinced (possibly by the arguments laid out in the linked papers), as this time around EWG approved the proposal. - Bit-casting object representations. This is a library proposal, but EWG was asked for guidance regarding making this function
constexpr
, which requires compiler support. EWG decided that it could be madeconstexpr
for all types except a few categories – unions, pointers, pointers-to-members, and references – for which that would have been tricky to implement.- As a humorous side-note about this proposal, since it could only apply to “plain old data” types (more precisely, trivially copyable types; as mentioned above, “plain old data” was deprecated as a term of art), one of the potential names the authors proposed for the library function was
pod_cast
. Sadly, this was voted down in favour ofbit_cast
.
- As a humorous side-note about this proposal, since it could only apply to “plain old data” types (more precisely, trivially copyable types; as mentioned above, “plain old data” was deprecated as a term of art), one of the potential names the authors proposed for the library function was
- Language support for empty objects. This addresses some of the limitations of the empty base optimization (such as not being able to employ it with types that are
final
or otherwise cannot be derived from) by allowing data members to opt out of the rule that requires them to occupy at least 1 byte using an attribute,[[no_unique_address]]
. The resulting technique is called the “empty member optimization”. - Efficient sized
delete
for variable-sized classes. I gave some background on this in my previous post. The authors returned with sign-off from all relevant implementers, and a clearer syntax (the “destroying delete” operator is now identified by a tag type, as inoperator delete(Type*, std::destroying_delete_t
), and the proposal was approved. - Attributes for likely and unlikely statements. This proposal has been updated as per previous EWG feedback to allow placing the attribute on all statements. It was approved with one modification: placing the attribute on a declaration statement was forbidden, because other attributes on declaration statements consistently apply to the entity being declared, not the statement itself.
- Deprecate implicit capture of
*this
. Only the implicit capture of*this
via[=]
was deprecated; EWG felt that disallowing implicit capture via[&]
would break too much idiomatic code. - Allow pack expansions in lambda init-capture. There was no compelling reason to disallow this, and the workaround of constructing a
tuple
to store the arguments and then unpacking it is inefficient. - String literals as template parameters. This fixes a longstanding limitation in C++ where there was previously no way to do compile-time processing of strings in such a way that the value of the string could affect the type of the result (as an example, think of a compile-time regex parsing library where the resulting type defines an efficient matcher (DFA) for the regex). The syntax is very simple:
template <auto& String>
; theauto
then gets deduced asconst char[N]
(orconst char16_t[N]
etc. depending on the type of the string literal passed as argument) whereN
is the length of the string. (You can also writetemplate <const char (&String)[N]>
if you knowN
, but you can’t writetemplate <size_t N, const char (&String)[N]>
and have bothN
andString
deduced from a single string literal template argument, because EWG did not want to create a precedent for a single template argument matching two template parameters. That’s not a big deal, though: using theauto
form, you can easily recoverN
via traits, and even constrain the length or the character type using a requires-clause.) - A tweak to the Contracts proposal. An issue came up during CWG review of the proposal regarding inline functions with assertion checks inside them: what should happen if the function is called from two translation units, one of which is compiled with assertion checks enabled and one of them not? EWG’s answer was that, as with
NDEBUG
today, this is technically an ODR (one definition rule) violation. The behaviour in practice is fairly well understood: the linker will pick one version or the other, and that version will be used by both translation units. (There are some potential issues with this: what if, while compiling a caller in one of the translation units, the optimizer assumed that the assertion was checked, but the linker picks the version where the assertion isn’t checked? That can result in miscompilation. The topic remains under discussion.)
There were also a few that, after being accepted by EWG, were reviewed by CWG and merged into the C++20 working draft the same week, and thus I already mentioned them in the C++20 section above:
- requires-clauses in lambdas. This was accepted.
- requires-clauses in template template parameters. Also accepted.
auto
as a parameter type in regular (non-lambda) functions. This was mildly controversial due to the similarity to AFTs, whose design is still under discussion, so it was deferred to be dealt with together with AFTs.
Proposals for which further work is encouraged:
- Standard containers and
constexpr
. This is the latest version of an ongoing effort by compiler implementers and others to get dynamic memory allocation working in aconstexpr
context. The current proposal allows most forms of dynamic allocation and related constructs during constant evaluation: non-trivial destructors,new
anddelete
expressions, placementnew
, and use ofstd::allocator
; this allows reusing a lot of regular code, including code that usesstd::vector
, in aconstexpr
context. Direct use ofoperator new
is not allowed, because that returnsvoid*
, and constant evaluation needs to track the type of dynamically allocated objects. There is also a provision to allow memory that is dynamically allocated during constant evaluation to survive to runtime, at which point it’s treated as static storage. EWG liked the direction (and particularly the fact that compiler writers were on the same page regarding its implementability) and encouraged development of a more concrete proposal along these lines. - Supporting
offsetof
for stable-layout classes. “Stable-layout” is a new proposed category of types, broader than “standard-layout”, for whichoffsetof
could be implemented. EWG observed that the definition of “standard-layout” itself could be broadened a bit to include most of the desired use cases, and expressed a preference for doing that instead of introducing a new category. There was also talk of potentially supportingoffsetof
for all types, which may be proposed separately as a follow-up. short float
. This proposal for a 16-bit floating-point type was approved by EWG earlier this year, but came back for some reason. There was some re-hashing of previous discussions about whether the standard should mandate the size (16 bits) and IEEE behaviour.- Adding alias declarations to concepts. This paper proposed three potential enhancements to concept declarations to make writing concepts easier. EWG was not particularly convinced about the need for this, but believed at least the first proposal could be entertained given stronger motivation.
[[uninitialized]]
attribute. This attribute is intended to suppress compiler warnings about variables that are declared but not initialized in cases where this is done intentionally, thus facilitating the use of such warnings in a codebase to catch unintentional cases. EWG pointed out that most compiler these days warn not about uninitialized declarations, but uninitialized uses. There was also a desire to address the broader use case of allocating dynamic memory that is purposely uninitialized (e.g.std::vector<char> buffer(N)
currently zero-initializes the allocated memory).- Relaxed incomplete multidimensional array type declaration. This is a companion proposal to the
std::mdspan
library proposal, which is a multi-dimensional array view. It would allow writing things likestd::mdspan<double[][][]>
to denote a three-dimensional array where the size in each dimension is determined at runtime. Note that you still would not be able to create an object of typedouble[][][]
; you could only use it in contexts that do not require creating an object, like a template argument. Basically,mdspan
is trying to (ab)use array types as a mini-DSL to describe its dimensions, similar to howstd::function
uses function types as a mini-DSL to describe its signature. This proposal was presented before, whenmdspan
was earlier in its design stage, and EWG did not find it sufficiently motivating. Now that themdspan
is going forward, the authors tried again. EWG was open to entertaining the idea, but only if technical issues such as the interaction with template argument deduction are ironed out. - Class types in non-type template parameters. This has been proposed before, but EWG was stuck on the question of how to determine equivalence (something you need to be able to do for template arguments) for values of class types. Now,
operator<=>
has given us a way to move forward on this question, basically by requiring that class types used in non-type template parameters have a defaultedoperator<=>
. It was observed that there is some overlap with the proposal to allow string literals as template parameters (since one way to pass a character array as a template parameter would be to wrap it in a struct), but it seemed like they also each have their own use cases and there may be room for both in the language. - Dynamic library loading. The C++ standard does not talk about dynamic libraries, but some people would find it useful to have a standardized library interface for dealing with them anyways. EWG was asked for input on whether it would be acceptable to standardize a library interface without saying too much about its semantics (since specifying the semantics would require that the C++ standard start talking about dynamic libraries, and specifying their behaviour in relation to exceptions, thread-local storage, the One Definition Rule, and so on). EWG was open to this direction, but suggested that the library interface be made much more general, as in its current incarnation it seemed to be geared towards certain platforms and unimplementable on others.
- Various proposed extensions to the Modules TS, which I talk about below.
There was also a proposal for recursive lambdas that wasn’t discussed because its author realized it needed some more work first.
Rejected proposals:
- A proposed trait
has_padding_bits
, the need for which came up during review of an atomics-related proposal by the Concurrency Study Group. EWG expressed a preference for an alternative approach that removed the need for the trait by putting the burden on compiler implementers to make things work correctly. - Attributes for structured bindings. This was proposed previously and rejected on the basis of insufficient motivation. The author came back with additional motivation: thread-safety attributes such as
[[guarded_by]]
or[[locks_held]]
. However, it was pointed out that the individual bindings are just aliases to fields of an (unnamed) object, so it doesn’t make sense to apply attributes to them; attributes can be applied to the deconstructed object as a whole, or to one of its fields at the point of the field’s declaration. - Keeping the alias syntax extendable. This proposed reverting the part of the down with
typename
! proposal, approved at the last meeting, that allowed omitting thetypename
inusing alias = typename T::type;
whereT
was a dependent type. The rationale was that even though today only a type is allowed in that position (thus making thetypename
disambiguator redundant), this prevents us from reusing the same syntax for expression aliases in the future. EWG already considered this, and didn’t find it compelling: the preference was to make the “land grab” for a syntax that is widely used today, instead of keeping it in reserve for a hypothetical future feature. - Forward without
forward
.The idea here is to abbreviate thestd::forward<decltype(x)>(x)
boilerplate that often occurs in generic code, to>>x
(i.e. a unary>>
operator applied tox
). EWG sympathized with the desire to eliminate this boilerplate, but felt that>>
, or indeed any other unary operator, would be too confusing of a syntax, especially when occuring after an=
in a lambda init-capture (e.g.[foo=>>foo](...){ ... }
). EWG was willing to entertain a keyword instead, but the best people could come up with wasfwdexpr
and that didn’t have consensus; as a result, the future of this proposal is uncertain. - Relaxing the rules about invoking an
explicit
constructor with a braced-init-list. This would have allowed , among a few other changes, writingreturn {...};
instead ofreturn T{...};
in a function whose declared return type isT
, even if the invoked constructor wasexplicit
. This has been proposed before, but rejected on the basis that it makes it easy to introduce bugs (see e.g. this response). The author proposed addressing those concerns by introducing some new rules to limit the cases in which this was allowed, but EWG did not find the motivation sufficiently compelling to further complicate C++’s already complex initialization rules. - Another attempt at standardizing arrays of runtime bound (ARBs, a pared-down version of C’s variable-length arrays), and a C++ wrapper class for them,
stack_array
. ARBs and a wrapper class calleddynarray
were previously headed for standardization in the form of an Array Extensions TS, before the project was scrapped becausedynarray
was found to be unimplementable. This proposal would solve the implementability concerns by restricting the usage ofstack_array
(e.g. it couldn’t be used as a class member). EWG was concerned that the restrictions would result in a type that’s not very usable. (It was pointed out that a design to make such a type more composable was proposed previously, but the author didn’t have time to pursue it further.) Ultimately, EWG didn’t feel that this proposal had a better chance of succeeding than the last time standardization of ARBs was attempted. However, a future direction that might be more promising was outlined: introducing a core language “allocation expression” that allocates a unnamed (and runtime-sized) stack array and returns a non-owning wrapper, such as astd::span
, to access it. - A modern C++ signature for
main()
. This would have introduced a new signature formain()
(alongside the existing allowed signatures) that exposed the command-line arguments using an iterable modern C++ type rather than raw pointers (the specific proposal wasint main(std::initializer_list<std::string_view>)
. EWG was not convinced that such a thing would be easier to use and learn thanint main(int argc, char*[] argv);
. It was suggested that instead, a trivial library facility that tookargc
andargv
as inputs and exposed an iterable interface could be provided; alternatively (or in addition), a way to access command-line arguments from anywhere in the program (similar to Rust’sstd::env::args()
) could be explored. - Abbreviated lambdas for fun and profit. This proposal would introduce a new abbreviated syntax for single-expression lambdas; a previous version of it was presented and largely rejected in Kona. Not much has changed to sway EWG’s opinion since then; if anything, additional technical issues were discovered.
For example, one of the features of the abbreviated syntax is “automatic SFINAE”. That is,[x] => expr
would mean[x] -> decltype(expr) { return expr; }
; the appearance ofexpr
in the return type rather than just the body would mean that a substitution failure inexpr
wouldn’t be a hard error, it would just remove the function overload being considered from the overload set (see the paper for an example). However, it was pointed out that in e.g.[x] -> decltype(x) { return x; }
, thex
in thedecltype
and thex
in the body refer to two different entities: the first refers to the variable in the enclosing scope that is captured, and the second to the captured copy. If we try to make[x] => x
“expand to” that, then we get into a situation where thex
in the abbreviated form refers to two different entities for two different purposes, which would be rather confusing. Alternatively, we could say in the abbreviated form,x
refers to the captured copy for both purposes, but then we are applying SFINAE in new scenarios, and some implementers are strongly opposed to that.
It was also pointed out that the abbreviated form’s proposed return semantics were “return by reference”, while regular lambdas are “return by value” by default. EWG felt it would be confusing to have two different defaults like this.
- Making the lambda capture syntax more liberal in what it accepts. C++ currently requires that in a lambda capture list, the capture-default, if present, come before any explicit captures. This proposal would have allowed them to be written in any order; in addition, it would have allowed repeating variables that are covered by the capture-default as explicit captures for emphasis. EWG didn’t find the motivation for either of these changes compelling.
- Lifting overload sets into objects. This is a resurrection of an earlier proposal to allow passing around overload sets as objects. It addressed previous concerns with that proposal by making the syntax more explicit: you’d pass
[]f
rather than justf
, wheref
was the name of the overloaded function. There were also provisions for passing around operators, and functions that performed member access. EWG’s feedback was that this proposal seems to be confused between two possible sets of desired semantics:- a way to build super-terse lambdas, which essentially amounts to packaging up a name; the overload set itself isn’t formed at the time you create the lambda, only later when you instantiate it
- a way to package and pass around overload sets themselves, which would be formed at the time you package them
EWG didn’t have much of an appetite for #1 (possibly because it had just rejected another terse-lambda proposal), and argued that #2 could be achieved using reflection.
Discussion papers
There were also a few papers submitted to EWG that weren’t proposals per se, just discussion papers.
These included a paper arguing that Concepts does not significantly improve upon C++17, and a response paper arguing that it in fact does. The main issue was whether Concepts delivers on its promise of making template error messages better; EWG’s consensus was that they do when compared to unconstrainted templates, but perhaps not as much as one would hope when compared to C++17 techniques for constraining templates, like enable_if
. There may be room for implementations (to date there is just the one in GCC) to do a better job here. (Of course, Concepts are also preferable over enable_if
in other ways, such as being much easier to read.)
There was also a paper describing the experiences of the author teaching Concepts online. One of the takeaways here is that students don’t tend to find the variety of concept declaration syntaxes confusing; they tend to mix them freely, and they tend to like the abbreviated function template (AFT) syntax.
Modules
I mentioned above that a significant focus of the meeting was to address the national body comments on the Modules PDTS, and hopefully get to a publication vote on the final Modules TS.
EWG looked at Modules on two occasions: first to deal with PDTS comments that had language design implications, and second to look at new proposals concerning Modules. The latter were all categorized as “post-TS”: they would not target the Modules TS, but rather “Modules v2”, the next iteration of Modules (for which the ship vehicle has not yet been decided).
Modules TS
The first task, dealing with PDTS comments in EWG, was a short affair. Any comment that proposed a non-trivial design change, or even remotely had the potential to delay the publication of the Modules TS, was summarily rejected (with the intention that the concern could be addressed in Modules v2 instead). It was clear that the committee leadership was intent on shipping the Modules TS by the end of the meeting, and would not let it get derailed for any reason.
“That’s a good thing, right?” you ask. After all, the sooner we ship the Modules TS, the sooner people can start trying it out and providing feedback, and thus the sooner we can get a refined proposal into the official standard, right? I think the reality is a bit more nuanced than that. As always, it’s a tradeoff: if we ship too soon, we can risk shipping a TS that’s not sufficiently polished for people to reasonably implement and use it; then we don’t get much feedback and we effectively waste a TS cycle. In this case, I personally feel like EWG could have erred a bit more on the side of shipping a slightly more polished TS, even if that meant delaying the publication by a meeting (it ended up being delayed by at least a couple of months anyways). That said, I can also sympathize with the viewpoint that Modules has been in the making for a very long time and we need to ship something already.
Anyways, for this reason, most PDTS comments that were routed to EWG were rejected. (Again, I should emphasize that this means “rejected for the TS“, not “rejected forever”.) The only non-rejection response that EWG gave was to comment US 041, where EWG confirmed that the intent was that argument-dependent lookup could find some non-exported entities in some situations.
Of course, there were other PDTS comments that weren’t routed to EWG because they weren’t design issues; these were routed to CWG, and CWG spent much of the week looking at them. At one point towards the end of the week, CWG did consult EWG about a design issue that came up. The question concerned whether a translation unit that imports a module sees a class type declared in that module as complete or incomplete in various situations. Some of the possibilities that have to be considered here are whether the module exports the class’s forward declaration, its definition, or both; whether the module interface unit contains a definition of the class (exported or not) at all; and whether the class appears in the signature of an exported entity (such as a function) without itself being exported.
There are various use cases that need to be considered when deciding the behaviour here. For example, a module may want to export functions that return or take as parameters pointers or references to a type that’s “opaque” to the module’s consumer, i.e. the module’s consumer can’t create an instance of such a class or access its fields; that’s a use case for exporting a type as incomplete. At the same time, the module author may want to avoid splitting her module into separate interface and implementation units at all, and thus wants to define the type in the interface unit while still exporting it as incomplete.
The issue that CWG got held up on was that the rules as currently specified seemed to imply that in a consumer translation unit, an imported type could be complete and incomplete at the same time, depending on how it was named (e.g. directly vs. via decltype(f())
where it was the return type of a function f
). Some implementers indicated that this would be a significant challenge to implement, as it would require a more sophisticated implementation model for types (where completeness was a property of “views of types” rather than of types themselves) that no existing language feature currently requires.
Several alternatives were proposed which avoided these implementation challenges. While EWG was favourable to some of them, there was also opposition to making what some saw as a design change to the Modules TS at this late stage, so it was decided that the TS would go ahead with the current design, possibly annotated as “we know there’s a potential problem here”, and it would be fixed up in v2.
I find the implications of this choice a bit unfortunate. It sounded like the implementers that described this model as being a significant challenge to implement, are not planning to implement it (after all, it’s going to be fixed in v2; why redesign your compiler’s type system if ultimately you won’t need it). Other implementers may or may not implement this model. Either way, we’ll either have implementation divergence, or all implementations will agree on a de facto model that’s different from what the spec says. This is one of those cases where I feel like waiting to polish the spec a bit more, so that it’s not shipped in a known-to-be-broken state, may have been advised.
I mentioned in my previous report that I thought the various Modules implementers didn’t talk to each other enough about their respective implementation strategies. I still feel like that’s very much the case. I feel like discussing each other’s implementation approaches in more depth would have unearthed this issue, and allowed it to be dealt with, sooner.
Modules v2
Now moving on to the proposals targeting Modules v2 that EWG reviewed:
- Two of them (module interface imports and namespace pervasiveness and modules) it turned out were already addressed in the Modules TS by changes made in response to PDTS comments.
- Placement of module declarations. Currently, if a module unit contains declarations in the global module, the module declaration (which effectively “starts” the module) needs to go after those global declarations. However, this makes it more difficult for both humans and tools to find the module declaration. This paper proposes a syntax that allows having the module declaration be the first declaration in the file, while still having a way to place declarations in the global module. It was observed that this proposal would make it easier to make
module
a context-sensitive keyword, which has also been requested. EWG encouraged continued exploration in this direction. - Module partitions. This iterates on the previous module partitions proposal (found in this paper), with a new syntax:
module basename : partition;
(unlike in the previous version,partition
here is not a keyword, it’s the partition’s name). EWG liked this approach as well. Module partitions also make proclaimed-ownership-declarations unnecessary, so those can be axed. - Making module names strings. Currently, module names are identifier sequences separated by dots (e.g.
foo.bar.baz
), with the dots not necessarily implying a hierarchical relationship; they are mapped onto files in an implementation-defined manner. Making them strings instead would allow mapping onto the filesystem more explicitly. There was no consensus for this change in EWG. - Making
module
a context-sensitive keyword. As always, making a common word likemodule
a hard keyword breaks someone. In this case, it shows up as an identifier in many mature APIs like Vulkan, CUDA, Direct X 9, and others, and in some of these cases (like Vulkan) the name is enshrined into a published specification. In some cases, the problem can be solved by making the keyword context-sensitive, and that’s the case formodule
(especially if the proposal about the placement of module declarations is accepted). EWG agreed to make the keyword context-sensitive. The authors of this paper asked if this could be done for the TS rather than for Modules v2; that request was rejected, but implementers indicated that they would implement it as context-sensitive ASAP, thus avoiding problems in practice. - Modules TS does not support intended use case. The bulk of the concerns here were addressed in the Modules TS while addressing PDTS comments, except for a proposed extension to allow using-declarations with an unqualified name. EWG indicated it was open to such an extension for v2.
- Two papers about support for exporting macros, which remains one of the most controversial questions about Modules. The first was a “rumination” paper, which was mostly arguing that we need a published TS and deployment experience before we can settle the question; the second argued that having deployed modules (clang’s pre-TS implementation) in a large codebase (Apple’s), it’s clear that macro support is necessary. A number of options for preserving hygiene, such as only exporting and importing individual macros, were discussed. EWG expressed a lukewarm preference to continuing to explore macro support, particularly with such fine-grained control for hygiene.
Other Working Groups
The Library Evolution Working Group, as usual, reviewed a decent amount of proposed new library features. While I can’t give a complete listing of the proposals discussed and their outcomes (having been in EWG all week), I’ll mention a few highlights of accepted proposals:
Targeting C++20:
- Extending
chrono
to calendars and time zones. This essentially gives C++ a standard date library. hash_combine()
, a mechanism for easily writing hash functions for user-defined types. It was pointed out that some competing proposals for this may be brought forward as well.bind_front()
, an improved version ofstd::bind()
(but not a replacement as it doesn’t cover all ofstd::bind
‘s use cases)spanstream
, a modern replacement for the long-deprecatedstd::strstream
.- A
contains()
function for associative containers - Efficient access to
stringbuf
‘s buffer - (among many others)
std::span
(formerly called array_view
) is also targeting C++20, but has not quite gotten final approval from LEWG yet.
Targeting the Library Fundamentals TS v3:
- mdspan, a multi-dimensional array view. (How can a multi-dimensional array view be approved sooner than a single-dimensional one, you ask? It’s because
mdspan
is targeting a TS, butspan
is targeting the standard directly, sospan
needs to meet a higher bar for approval.) - std::expected<T>, a “value or error” variant type very similar to Rust’s
Result
Targeting the Ranges TS:
- Range adaptors (“views”) and utilities. Range views are ranges that lazily consume elements from an underlying range, while performing an additional operation like transforming the elements or filtering them. This finally gives C++ a standard facility that’s comparable to C#’s LINQ (sans the SQL syntax), Java 8’s streams, or Rust’s iterators. C++11 versions of the facilities proposed here are available today in the range-v3 library (which was in turn inspired by Boost.Range).
There was an evening session to discuss the future of text handling in C++. There was general agreement that it’s desirable to have a text handling library that has notions of code units, code points, and grapheme clusters; for many everyday text processing algorithms (like toupper
), operating at the level of grapheme clusters makes the most sense. Regarding error handling, different people have different needs (safety vs. performance), and a policy-based approach to control error handling may be advisable. Some of the challenges include standard library implementations having to ship a database of Unicode character classifications, or hook into the OS’s database. The notion of whether we should have a separate character type to represent UTF-8 encoded text, or just use char
for that purpose, remains contentious.
SG 7 (Compile-Time Programming)
SG 7, the Compile-Time Programming (previously Reflection) Study Group met for an evening session.
An updated version of a proposed extension to the static reflection proposal to allow reflecting over functions was briefly reviewed and sent onwards for review in EWG and LEWG at future meetings.
The rest of the evening was spent discussing an updated version of the metaclasses proposal. To recap, a metaclass defines a compile-time transformation on a class, and can be applied to a class to produce a transformed class (possibly among other things, like helper classes / functions). The discussion focused on one particular dimension of the design space here: how the transformation should be defined. Three options were given:
- The metaclass operates on a mutable input class, and makes changes to it to produce the transformed class. This is how it worked in the original proposal.
- Like #1, but the metaclass operates on an immutable input class, and builds the transformed class from the ground up as its output.
- Like #2, but the metaclass code operates on the “meta level”, where the representation of the input and output types is an ordinary object of type
meta::type
. This dispenses with most of the special syntax of the first two approaches, making the metaclass look a lot like a normalconstexpr
function.
SG 7 liked the third approach the best, noting that it not only dispenses with the need for the $
syntax (which couldn’t have been the final syntax anyways, it would have needed to be something uglier), but makes the proposal more general (opening up more avenues for how and where you can invoke/apply the metaclass), and more in line with the preference the group previously expressed to have reflection facilities operate on a homogeneous value representation of the program’s entities.
Discussion of other dimensions of the design space, such as what the invocation syntax for metaclasses should look like (i.e. how you apply them to a class) was deferred to future meetings.
SG 12 (Undefined Behaviour and Vulnerabilities)
SG 12, the Undefined Behaviour Study Group, recently had its scope expanded to also cover documenting vulnerabilities in the C++ language, and ways to avoid them.
This latter task is a joint effort between SG 12 and WG 23, a sister committee of the C++ Standards Committee that deals with vulnerabilities in programming languages in general. WG 23 produces a language-agnostic document that catalogues vulnerabilities without being specific to a language, and then language-specific annexes for a number of programming languages. For the last couple of meetings, WG 23 has been collaborating with our SG 12 to produce a C++ annex; the two groups met for that purpose for two days during this meeting. The C++ annex is at a pretty early stage, but over time it has the potential to grow to be a comprehensive document outlining many interesting types of vulnerabilities that C++ programmers can run into, and how to avoid them.
SG 12 also had a meeting of its own, where it looked at a proposal to make certain low-level code patterns that are widely used but technically have undefined behaviour, have defined behaviour instead. This proposal was reviewed favourably.
C++ Stability and Velocity
On Friday evening, there was a session to discuss the stability and velocity of C++.
One of the focuses of the session was reviewing the committee’s policy on deprecating and removing old features that are known to be broken or that have been superseded by better alternatives. Several language features (e.g. dynamic exception specifications) and library facilities (e.g. std::auto_ptr
) have been deprecated and removed in this way.
One of the library facilities that were removed in C++17 was the deprecated “binders” (std::bind1st
and std::bind2nd
). These have been superseded by the C++11 std::bind
, but, unlike say auto_ptr
, they aren’t problematic or dangerous in any way. It was argued that the committee should not deprecate features like that, because it causes unnecessary code churn and maintenance cost for codebases whose lifetime and update cycle is very long (on the order of decades); embedded software such as an elevator control system was brought up as a specific example.
While some sympathized with this viewpoint, the general consensus was that, to be able to evolve at the speed it needs to to satisfy the needs of the majority of its users, C++ does need to be able to shed old “cruft” like this over time. Implementations often do a good job of maintaining conformance modes with older standard versions (and even “escape hatches” to allow old features that have been removed to be used together with new features that have since been added), thus allowing users to continue using removed features for quite a while in practice. (Putting bind1st
and bind2nd
specifically back into C++20 was polled, but didn’t have consensus.)
The other focus of the session was the more general tension between the two pressures of stability and velocity that C++ faces as it evolves. It was argued that there is a sense in which the two are at odds with each other, and the committee needs to take a clearer stance on which is the more important goal. Two examples of cases where backwards compatibility constraints have been a drag on the language that were brought up were the keywords used for coroutines (co_await
, co_yield
, etc. – wouldn’t it have been nice to just be able to claim await
and yield
instead?), and the const-correctness issue with std::function
which still remains to be fixed. A poll on which of stability or velocity is more important for the future direction of C++ revealed a wide array of positions, with somewhat of a preference for velocity.
Conclusion
This was a productive meeting, whose highlights included the Modules TS making good progress towards its publication; C++20 continuing to take shape as the draft standard gained the consistent comparisons feature among many other smaller ones; and range views/adaptors being standardized for the Ranges TS.
The next meeting of the Committee will be in Jacksonville, Florida, the week of March 12th, 2018. It, too, should be an exciting meeting, as design discussion of Concepts resumes (with the future of AFTs possibly being settled), and the Modules TS is hopefully finalized (if that doesn’t already happen between meetings). Stay tuned for my report!
Other Trip Reports
Others have written reports about this meeting as well. Some that I’ve come across include Herb Sutter’s and Bryce Lelbach’s. I encourage you to check them out!
I think “Make std::memory_order a scoped enumeration” is linking to the wrong proposal.
Fixed, thanks!
You say here that “
currently zero-initializes”; this doesn’t match my reading of the standard or the observable behavior of GCC (in that link, it just calls
, but if you change the line to
, it zero-initializes.
Am I misreading what you meant, or was that a typo? And if it was, what did you mean there?
Thanks for pointing that out; that was a mistake on my part. The actual comment that was made concerned
std::vector<char>
, notnew char[N]
. You’re right that fornew char[N]
you can already control whether or not initialization happens.I corrected this in the post.
Thanks for the epic post! 🙂
Sad to see both forwarding and the reasonable lambda syntax going up in flames. Not to mention the whole overload set thing. All of these kinds of proposal collapsing makes it seem like the committee just doesn’t want to fix the problem.
I’m also terrified of the idea of putting zero-sized subobjects in the way the current proposal says to do so. Specifically, as an “optimization” rather than a guarantee. It’s like somebody forgot that part of standard layout’s guarantees was that EBO was *required* in some circumstances. We should have something similar for zero-sized members: a way to ensure that, under certain circumstances, implementations must make them zero sized.
As always, thanks for the trip report Botond!
As for perfect forwarding: this may be a rather naive idea, but wouldn’t it be possible to add a language extension which allows std::forward to do automatically deduce the type whenever it is called without a template parameter? It is my understanding that std::forward cannot be called without a template parameter today, so this wouldn’t break any backward compatibility. It would essentially be some kind of automatic type deduction, allowed for std::forward only.
Good question. Basically, if there were special rules that applied to
std::forward
, it would no longer be function, but rather an operator. The idea of placing operators in namespacestd
came up in the past, but there was implementer opposition to it. That leaves using a keyword, which was considered but people couldn’t agree on one (as I mentioned,fwdexpr
was the best candidate, but it didn’t have enough support).So, because they couldn’t agree on a keyword, we have to keep using the thing everyone agrees is problematic, slow, and too verbose?
It seems odd how the committee can adopt huge changes like the spaceship operator, but it’s the little problems that just never get fixed. All because some people in a room can’t agree on what to name it.
Is there some chance of the committee becoming more focused on solving problems and less concerned about how a solution is spelled? This seems to be one of the problems Stroustrup outlined in his “Grill the Committee” talk back at C++Con2017, that the committee spends a lot of time arguing about trivial things rather than actually solving problems.
Sometimes it works. The committee hemed and hawed over the generation of operators proposals long enough that someone came up with `operator`, which is infinitely better than all of those proposals. But more often than not, it seems like it doesn’t.
It’s one thing for a good proposal to die because they couldn’t come up with a satisfactory answer for technical issue X. That leaves open the possibility that someone could find a wholly different solution. But the forwarding issue has no different solution: either you use a template, an existing operator, or a keyword. There’s no “different solution” out there waiting to be discovered.
One thing to keep in mind is that opinions on a hypothetical modification to a proposal do not carry as much weight as opinions on a proposal itself. When discussing a hypothetical modification, there is a lot of uncertainty because it’s not written down and the details are left to the imagination; people also haven’t had time to consider / dwell on it.
In this case, the proposal was for a unary
>>
operator; the keyword alternative was just briefly brought up and discussed on the spot.If you believe strongly in a keyword-based solution to this problem, I would encourage you to write a proposal for it, fleshing out all the details and making a case for it. That may get a better reception than the on-the-fly suggestion to use a keyword did.
Your trip reports are absolutely great. Will have great historic value as well.
To be more specific, your sections about accepted, need-work and rejected are a godsend. And while we are at it, Happy Christmas!
Thanks! Glad to hear they are useful. Merry Christmas to you too!