Herb Sutter's Blog, page 4
September 28, 2023
cppfront: Autumn update
Since the 2022-12-31 year-end mini-update and the 2023-04-30 spring update, progress has continued on cppfront. (If you don’t know what this personal project is, please see the CppCon 2022 talk on YouTube for an overview, and the CppNow 2023 talk on YouTube for an interim update.)
I’ll be giving a major update next week at CppCon. I hope to see many of you there! In the meantime, here are some notes about what’s been happening since the spring update post, including:
Acknowledgments and thanksStarted self-hostingNo data left behind: Mandatory explicit discardrequires clausesGeneralized aliases+constexpr with ==Safe enum and flag_enum metafunctionsSafe union metafunctionWhat’s nextAcknowledgments: Thank you!
Thank you to all these folks who have participated in the cppfront repo by opening issues and PRs, and to many more who participated on PR reviews and comment threads! These contributors represent people from high school and undergrad students to full professors, from commercial developers to conference speakers, and from every continent except Antarctica.
Started self-hostingI haven’t spent a lot of time yet converting cppfront’s own code from today’s syntax 1 to my alternate syntax 2 (which I’m calling “Cpp1” and “Cpp2” for short), but I started with all of cppfront’s reflection API and metafunctions which are now mostly written in Cpp2. Here’s what that reflect.h2 file compilation looks like when compiled on the command line on my laptop:

But note you can still build cppfront as all-today’s-C++ using any fairly recent C++20 compiler because I distribute the sources also as C++ (just as Bjarne distributed the cfront sources also as C).

Initialization and data flow are fundamental to safe code, so from the outset I ensured that syntax 2 guaranteed initialization-before use, I made all converting constructors explicit by default… and I made [[nodiscard]] the default for return values (1-min talk clip).
The more I thought about [[nodiscard]], the more determined I was that data must never be silently lost, and data-lossy operations should be explicit. So I’ve decided to try an aggressive experiment:
make “nodiscard” the law of the land, implicitly required all the time, with no opt-out… including when calling existing C++ libraries (including std::) that were never designed for their return values to be treated as [[nodiscard]]!Now, I wasn’t totally crazy: See the Design note: Explicit discard for details on how I first surveyed other languages’ designers about experience in their languages — notably C#, F#, and Python. In particular, F# does the same thing with .NET APIs — F# requires explicit |> ignore to discard unused return values, including for .NET APIs that were never designed for that and were largely written in other languages. Don Syme told me it has not been a significant pain point, and that was encouraging, so I’m following suit.
My experience so far is that it’s pretty painless, and I write about one explicit discard for every 200 lines of code, even when using the C++ standard library (which cppfront does pervasively, because the C++ standard library is the only library cppfront uses). And, so far, every time cppfront told me I had to write an explicit discard, I learned something useful (e.g., before this I never realized that emplace_back started to return something since C++17! push_back still doesn’t) and I found I liked that my code explicitly self-documented it was not looking at output values… my code looked better.
The way to do an explicit discard is to assign the result to the “don’t care” wildcard. It’s unobtrusive, but explicit and clear:
_ = vec.emplace_back(1,2,3);Now all Cpp2-authored C++ functions are emitted as [[nodiscard]], except only for assignment and streaming operators because those are designed for chaining and every chain always ends with a discarded return.
And the whole language hangs together well: Explicit discard works very naturally with inout and out parameters too, not just return values. If you have a local variable x and pass it to an inout parameter, what if that’s the last use of the variable?
{ x := my_vector.begin(); std::advance(x, 2); // ERROR, if param is Cpp2 'inout' or Cpp1 non-const '&'}In this example, that call to std::advance(x, 2); is a definite last use of x, and so Cpp2 will automatically pass x as an rvalue and make it a move candidate… and presto! the call won’t compile because you can’t pass an rvalue to a Cpp2 inout parameter (the same as a Cpp1 non-const-& parameter, so this correctly detects the output side effects also when calling existing C++ functions that take references to non-const). That’s a feature, not a bug, because if that’s the last use of x that means the function is not looking at x again, so it’s ignoring the “out” value of the std::advance(x, 2) function call, which is exactly like ignoring a return value. And the guidance is the same: If you really meant to do that, just explicitly discard x‘s final value:
{ x := my_vector.begin(); std::advance(x, 2); _ = x; // all right, you said you meant it, carry on then...}Adding _ = x; afterward naturally makes that the last use of x instead. Problem solved, and it self-documents that the code really meant to ignore a function’s output value.
I really, really like how my C++ code’s data flow is explicit, and fully protected and safe, in syntax 2. And I’m very pleased to see how it just works naturally throughout the language — from universal guaranteed initialization, to explicit constructors by default, to banning implicitly discarding any values, to uniform treatment of returned values whether returned by return value or the “out” part of inout and out parameters, and all of it working also with existing C++ libraries so they’re safer and nicer to use from syntax 2. Data is always initialized, data is never silently lost, data flow is always visible. Data is precious, and it’s always safe. This feels right and proper to me.
requires clausesI also added support for requires clauses, so now you can write those on all templates. The cppfront implementation was already generating some requires clauses already (see this 1-min video clip). Now programmers can write their own too.
Generalized aliases+constexpr with ==This required a bit of fighting with a GCC 10 bug about requires-clauses on declarations, that was fixed in GCC 11+ but was never backported. But because this was the only problem I’ve encountered with GCC 10 that I couldn’t paper over, and because I could give a clear diagnostic that a few features in Cpp2 that rely on requires clauses aren’t supported on GCC 10, so far I’ve been able to retain GCC 10 as a supported compiler and emit diagnostics if you try to use those few features it doesn’t support. GCC 11 and higher are all fine and support all Cpp2 semantics.
In the April blog post, I mentioned I needed a way to write type and namespace aliases, and that because all Cpp2 declarations are of the form thing : type = value, I decided to try using the same syntax but with == to denote “always equal to.”
// namespace aliaslit: namespace == ::std::literals;// type aliaspmr_vec: type == std::vector>;I think this clearly denotes that lit is always the same as ::std::literals, and pmr_vec is always the same as std::vector>.
Since then, I’ve thought about how this should be best extended to functions and objects, and I realized the requirements seem to overlap with something else I needed to support: constexpr functions and objects. Which, after all, are functions/objects that return/have “always the same values” known at compile time…
// function with "always the same value" (constexpr function)increment: (value: int) -> int == value+1; // Cpp2 lets you omit { return } around 1-line bodies// object with "always the same value" (constexpr object)forty_two: i64 == 42;I particularly needed these in order to write the enum metafunctions…
Safe enum and flag_enum metafunctionsIn the spring update blog post, I described the first 10 working compile-time metafunctions I implemented in cppfront, from the set of metafunctions I described in my ISO C++ paper P0707. Since then, I’ve also implemented enum and union.
The most important thing about metafunctions is that they are compile-time library code that uses the reflection and code generation API, that lets the author of an ordinary C++ class type easily opt into a named set of defaults, requirements, and generated contents. This approach is essential to making the language simpler, because it lets us avoid hardwiring special “extra” types into the language and compiler.
In Cpp2, there’s no enum feature hardwired into the language. Instead you write an ordinary class type and just apply the enum metafunction:
// skat_game is declaratively a safe enumeration type: it has// default/copy/move construction/assignment and <=> with // std::strong_ordering, a minimal-size signed underlying type// by default if the user didn't specify a type, no implicit// conversion to/from the underlying type, in fact no public// construction except copy construction so that it can never// have a value different from its listed enumerators, inline// constexpr enumerators with values that automatically start// at 1 and increment by 1 if the user didn't write their own// value, and conveniences like to_string()... the word "enum"// carries all that meaning as a convenient and readable// opt-in, without hardwiring "enum" specially into the language//skat_game: @enum type = { diamonds := 9; hearts; // 10 spades; // 11 clubs; // 12 grand := 20; null := 23;}Consider hearts: It’s a member object declaration, but it doesn’t have a type (or a default value) which is normally illegal, but it’s okay because the @enum metafunction fills them in: It iterates over all the data members and gives each one the underlying type (here explicitly specified as i16, otherwise it would be computed as the smallest signed type that’s big enough), and an initializer (by default one higher than the previous enumerator).
Why have this metafunction on an ordinary C++ class, when C++ already has both C’s enum and C++11’s enum class? Because:
it keeps the language smaller and simpler, because it doesn’t hardwire special-purpose divergent splinter types into the language and compiler(cue Beatles, and: “all you need is class (wa-wa, wa-wa-wa), all you need is class (wa-wa, wa-wa-wa)”);it’s a better enum than C enum, because C enum is unscoped and not as strongly typed (it implicitly converts to the underlying type); andit’s a better enum class than C++11 enum class, because it’s more flexible…… consider: Because an enumeration type is now “just a type,” it just naturally can also have member functions and other things that are not possible for Cpp1 enums and enum classes (see this StackOverflow question):
janus: @enum type = { past; future; flip: (inout this) == { if this == past { this = future; } else { this = past; } }}There’s also a flag_enum variation with power-of-two semantics and an unsigned underlying type:
// file_attributes is declaratively a safe flag enum type:// same as enum, but with a minimal-size unsigned underlying// type by default, and values that automatically start at 1// and rise by powers of two if the user didn't write their // own value, and bitwise operations plus .has(flags), // .set(flags), and .clear(flags)... the word "flag_enum"// carries all that meaning as a convenient and readable// opt-in without hardwiring "[Flags]" specially into the// language//file_attributes: @flag_enum type = { cached; // 1 current; // 2 obsolete; // 4 cached_and_current := cached | current;}Safe union metafunctionAnd you can declaratively opt into writing a safe discriminated union/variant type:
// name_or_number is declaratively a safe union/variant type: // it has a discriminant that enforces only one alternative // can be active at a time, members always have a name, and// each member has .is_member() and .member() accessors...// the word "union" carries all that meaning as a convenient // and readable opt-in without hardwiring "union" specially // into the language//name_or_number: @union type = { name: std::string; num : i32;}Why have this metafunction on an ordinary C++ class, when C++ already has both C’s union and C++11’s std::variant? Because:
it keeps the language smaller and simpler, because it doesn’t hardwire special-purpose divergent splinter types into the language and compiler(cue the Beatles earworm again: “class is all you need, class is all you need…”);it’s a better union than C union, because C union is unsafe; andit’s a better variant than C++11 std::variant, because std::variant is hard to use because its alternatives are anonymous (as is the type itself; there’s no way to distinguish in the type system between a variant that stores either an employee id or employee name, and a variant that stores either a lucky number or a pet unicorn’s dominant color).Each @union type has its own type-safe name, has clear and unambiguous named members, and safely encapsulates a discriminator to rule them all. Sure, it uses unsafe casts in the implementation, but they are fully encapsulated, where they can be tested once and be safe in all uses. That makes @union:
as easy to use as a C union, as safe to use as a std::variant… and as a bonus, because it’s an ordinary type, it can naturally have other things normal types can have, such as template parameter lists and member functions:// a templated custom safe unionname_or_other: @union type= { name : std::string; other : T; // a custom member function to_string: (this) -> std::string = { if is_name() { return name(); } else if is_other() { return other() as std::string; } else { return "invalid value"; } }}main: () = { x: name_or_other = (); x.set_other(42); std::cout << x.other() * 3.14 << "\n"; std::cout << x.to_string(); // prints "42", but is legal whichever alternative is active}What’s nextFor the rest of the year, I plan to:
continue self-hosting cppfront, i.e., migrate more of cppfront’s own code to be written in Cpp2 syntax, particularly now that I have enum and union (cppfront uses enum class and std::variant pervasively);continue working my list of pending Cpp2 features and implementing them in cppfront; andwork with a few private alpha testers to start writing a bit of code in Cpp2, to alpha-test cppfront and also to alpha-test my (so far unpublished) draft documentation.But first, one week from today, I’ll be at CppCon to give a talk about this progress and why full-fidelity compatibility with ISO C++ is essential (and what that means): “Cooperative C++ Evolution: Toward a TypeScript for C++.” I look forward to seeing many of you there!
August 13, 2023
My C++ Now 2023 talk is online: “A TypeScript for C++”
Thanks again to C++ Now for inviting me to speak this year in glorious Aspen, Colorado, USA! It was nice to see many old friends again there and make a few new ones too.
The talk I gave there was just posted on YouTube, you can find it here:
At CppCon 2022, I argued for why we should try to make C++ 10x simpler and safer, and I presented my own incomplete experimental compiler, cppfront. Since then, cppfront has continued progressing: My spring update post covered the addition of types, a reflection API, and metafunctions, and this talk was given a week after that post and shows off those features with discussion and live demos.
This talk also clearly distinguishes between what I call the “Dart plan” and the “TypeScript plan” for aiming at a 10x improvement for an incumbent popular language. Both plans have value, but they have different priorities and therefore choose different constraints… most of all, they either embrace up-front the design constraint of perfect C++ interop compatibility, or they forgo it (forever; as I argue in the talk, it can never be achieved retroactively, except by starting over, because it’s a fundamental up-front constraint). No one else has tried the TypeScript plan for C++ yet, and I see value in trying it, and so that’s the plan I’m following for cppfront.
When people ask me “how is cppfront different from all the other projects trying to improve/replace C++?” my answer is “cppfront is on the TypeScript plan.” All the other past and present projects have been on the Dart plan, which again is a fine plan too, it just has different priorities and tradeoffs particularly around compatibility.
The video description has a topical guide linking to major points in the talk. Here below is a more detailed version of that topical guide… I hope you enjoy the talk!
1:00 Intro and roadmap for the talk2:28 1. cppfront recap2:35 – green-field experiments are great; but cppfront is about refreshing C++ itself
3:28 – “when I say compatibility .. I mean I can call any C++ code that exists today … with no shims, no thunks, no overheads, no indirections, no wrapping”
4:05 – can’t take a breaking change to existing code without breaking the world
5:22 – to me, the most impactful release of C++ was C++11, it most changed the way we wrote our code
6:20 – what if we could do C++11 again, but a coordinated set of features to internally evolve C++
6:52 – cppfront is an experiment in progress, still incomplete
7:41 – thanks to 100+ cppfront contributors!
8:00 – summary slide of features demonstrated at CppCon 2022
– safety for C++; goal of 50x fewer CVEs due to type/bounds/lifetime/init safety
– simplicity for C++; goal of 10x less to know
10:00 – 2. cppfront: what’s new10:05 – (a) 3 smaller new features showing simplicity+safety+efficiency10:15 – <=> from this work has already been standardized
11:05 – simplicity, safety and efficiency rarely in tension, with the right abstractions
12:55 – chained comparisons: simple, safe (mathematically), efficient (single eval)
15:08 – named loops/break/continue: simple, safe (structured), efficient
16:51 – main’s arguments: simple (std:: always available), safe (bounds/null check by default), efficient (pay only if you ask for main’s parameters)
18:30 – (b) user-defined types19:20 – explicit `this`
20:20 – defaults: rarely write access-specifiers
21:30 – (recall from CppCon 2022: composable initialization safety with `out` parameters)
23:50 – unified `operator=`: {construct,assign}x{copy,move} is a single function (by default)
25:48 – visual for unified `operator=`
27:28 – walk through example code generation for unified `operator=`
31:35 – virtual/override/final are qualifiers on `this`
35:05 – DEMO: inheritance (GCC this time)
40:43 – easter egg
41:55 – can interleave bases and members, more control over layout and lifetime
43:10 – (c) reflection and type metafunctions43:10 – recap overview from CppCon 2017
54:23 – DEMO: applying type metafunctions
56:10 – 3. compatibility for C++56:35 – John Carmack on compatibility in the real world
59:40 – recall: summary of “Bridge to NewThingia” talk
1:02:05 – avoiding an adoption step function requires high fidelity compatibility
1:04:25 – C++ from C, TypeScript from JavaScript, Swift from Objective-C, Roslyn from prior compiler
1:05:45 – emphasizing and dissecting TypeScript’s compatibility story
1:07:55 – Dart: similar goal, but not designed to be compatible, and you’ll never be able to back into compatibility without starting over
1:08:55 – examples of why incompatibility costs a decade:
1:08:57 – – VC++ 6.0 to 10.0 … 12 years
1:10:28 – – Python 2 to 3 … 12 years (Python is C++’s #1 sister language)
1:18:30 – – C99 to C11 … 12 years
1:18:50 – – C++11 basic_string (approved in 2008) to 2019 support on all major platforms … 11 years
1:19:25 – the “lost decade” pattern: lack of seamless compatibility will cost you a decade in adoption
1:20:45 – three “plans”: the “10% plan”, the “Dart plan”, and the “TypeScript plan”1:21:00 – “10% plan”: incremental evolution-as-usual
1:21:40 – so how do we get a 10x improvement?
1:21:50 – “Dart plan”: designing something new, not worry about compatible interop, competitive
1:23:20 – “TypeScript plan”: designing for something compatible, cooperative
1:25:40 – what it takes to evolve C++ compatibly, which no other effort has tried before
1:28:50 – filling in the blank: ______ for C++
June 17, 2023
Trip report: Summer ISO C++ standards meeting (Varna, Bulgaria)
Minutes ago, the ISO C committee finished its meeting in-person in Varna, Bulgaria and online via Zoom, where we formally began adopting features into C 26.
Our hosts, VMware and Chaos, arranged for high-quality facilities for our six-day meeting from Monday through Saturday. We had over 170 attendees, about two-thirds in-person and the others remote via Zoom, formally representing 20 nations. Also, at each meeting we regularly have new attendees who have never attended before, and this time there were 17 new first-time attendees, mostly in-person; to all of them, once again welcome!
The committee currently has 23 active subgroups, most of which met in parallel tracks throughout the week. Some groups ran all week, and others ran for a few days or a part of a day and/or evening, depending on their workloads. You can find a brief summary of ISO procedures here.
This week’s meeting: Starting C 26ISO C is on a three-year development cycle, which includes a “feature freeze” about one year before we ship and publish that edition of the standard. For example, the feature freeze for C 23 was in early 2022.

But this doesn’t mean we only have two years’ worth of development time in the cycle and the third year is bug fixes and red tape. Instead, the subgroups are a three-stage pipeline and continue concurrently working on new feature development all the time, and the feature freezes are just the checkpoints where we pause loading new features into this particular train. So for the past year, as the subgroups finished work on fit-and-finish for the C 23 features, they also increasingly worked on C 26 features.
That showed this week, as we adopted the first 40 proposed change papers for C 26, many of which had been ready for a couple of meetings and were just waiting for the C 26 train to open for loading to be adopted. Of those 40 change papers, two were “apply the resolutions for all Ready issues” papers that applied a bunch of generally-minor changes. The other 38 were individual changes, everything from bug fixes to new features like hazard pointers and RCU.
Here are some of the highlights…
Adopted for C 26: Core language changes/featuresThe core language adopted 11 papers, including the following. Taking them in paper number order, which is roughly the order in which work started on the paper…
P2169 “A nice placeholder with no name” by Corentin Jabot and Michael Park officially adds support for the _ wildcard in C 26. Thanks to the authors for all their research and evidence for how it could be done in a backward-compatible way! Here are some examples that will now be legal as compilers start to support draft-C 26 syntax:
std::lock_guard _(mutex);
auto [x, y, _] = f();
inspect(foo) { _ => bar; };
Some compiler needs to implement -Wunderbar.
The palindromic P2552 “On the ignorability of standard attributes” by Timur Doumler sets forth the Three Laws of Robotics… er, I mean, the Three Rules of Ignorability for standard attributes. The Three Rules are a language design guideline for all current and future standard attributes going forward… see the paper for the full rules, but my informal summary is:
[Already in C 23] Rule 1. Standard attributes must be parseable (i.e., can’t just contain random nonsense).[Already in C 23] Rule 2. Removing a standard attribute can’t change the program’s meaning: It can reduce the program’s possible legal behaviors, but it can’t invent new behaviors.[New] Rule 3. Feature test macros shouldn’t pretend to support an attribute unless the implementation actually implements the attribute’s optional semantics (i.e., doesn’t just parse it but then ignore it).P2558 “Add @, %, and ` to the basic character set” by Steve Downey is not a paper whose name was redacted for cussing; it’s a language extension paper that follows in C’s footsteps, and allows these characters to be used in valid C programs, and possibly in future C language evolution.
P2621 “UB? In my lexer?” by Corentin Jabot removes the possibility that just tokenizing C code can be a source of undefined behavior in a C compiler itself. (Did you know it could be UB? Now it can’t.) Note however that this does not remove all possible UB during compilation; future papers may address more of those compile-time UB sources.
P2738 “constexpr cast from void*: towards constexpr type-erasure” by Corentin Jabot and David Ledger takes another step toward powerful compile-time libraries, including enabling std::format to potentially support constexpr compile-time string formatting. Speaking of which…
P2741 “User-generated static_assert messages” by Corentin Jabot lets compile-time static_assert accept stringlike messages that are not string literals. For example, the popular {fmt} library (but not yet std::format, but see above!) supports constexpr string formatting, and so this code would work in C 26:
static_assert(sizeof(S) == 1, fmt::format("Unexpected sizeof: expected 1, got {}", sizeof(S)));
Together with P2738, an implementation of std::format that uses both of the above features would now be able to used in a static_assert.
Adopted for C 26: Standard library changes/featuresThe standard library adopted 28 papers, including the following. Starting again with the lowest paper number…
This first one gets the award for “being worked on the longest” (just look at the paper number, and the R revision number): P0792R14, “function_ref: A type-erased callable reference” by Vittorio Romeo, Zhihao Yuan, and Jarrad Waterloo adds function_ref as a vocabulary type with reference semantics for passing callable entities to the standard library.
P1383 “More constexpr for and ” by Oliver J. Rosten adds constexpr to over 100 more standard library functions. The march toward making increasing swathes of the standard library usable at compile time continues… Jason Turner is out there somewhere saying “Moar Constexpr!” and “constexpr all the things!”
Then, still in paper number order, we get to the “Freestanding group”:
P2013 “Freestanding language: Optional ::operator new” by Ben Craig makes ::operator new optional in freestanding implementations.P2198 “Freestanding feature-test macros and implementation-defined extensions” by Ben Craig lets programs targeting freestanding implementations detect which standard library facilities are available.P2338 “Freestanding library: Character primitives and the C library” by Ben Craig adds to the freestanding C and C standard library all features that can be implemented without OS calls and space overhead, including and char_traits.P2510 “Formatting pointers” by Mark de Wever allows nice formatting of pointer values without incanting reinterpret_cast to an integer type first. For example, this will now work: format("{:P}", ptr);
P2530 “Hazard pointers for C 26” by Maged M. Michael, Michael Wong, Paul McKenney, Andrew Hunter, Daisy S. Hollman, JF Bastien, Hans Boehm, David Goldblatt, Frank Birbacher, and Mathias Stearn adds a subset of the Concurrency TS2 hazard pointer feature to add hazard pointer-based deferred cleanup to C 26.
P2545 “Read-Copy-Update (RCU)” by Paul McKenney, Michael Wong, Maged M. Michael, Andrew Hunter, Daisy Hollman, JF Bastien, Hans Boehm, David Goldblatt, Frank Birbacher, Erik Rigtorp, Tomasz Kamiński, Olivier Giroux, David Vernet, and Timur Doumler as another complementary way to do deferred cleanup in C 26.
P2548 “copyable_function” by Michael Hava adds a copyable replacement for std::function, modeled on move_only_function.
P2562 “constexpr stable sorting” by Oliver J. Rosten enables compile-time use of the standard library’s stable sorts (stable_sort, stable_partition, inplace_merge, and the ranges:: versions). Jason Turner is probably saying “Moar!”…
P2641 “Checking if a union alternative is active” by Barry Revzin and Daveed Vandevoorde introduces the consteval bool is_within_lifetime(const T* p) noexcept function, which works in certain compile-time contexts to find out whether p is a pointer to an object that is within its lifetime — such as checking the active member of a union, but during development the feature was made even more generally useful than just that one use case. (This is technically a core language feature, but it’s in one of the “magic std:: features that look like library functions but are actually implemented by the compiler” section of the standard, in this case the metaprogramming clause.)
P2757 “Type-checking format args” by Barry Revzin enables even more compile-time checking for std::format format strings.
Those are just 12 of the adopted papers as highlights… there were 16 more papers adopted that also apply more extensions and fixes for the C 26 standard library.
Other progressWe also adopted the C 26 schedule for our next three-year cycle. It’s the same as the schedule for C 23 but just with three years added everywhere, just as the C 23 schedule was in turn the same as the schedule for C 20 plus three years.
The language evolution subgroup (EWG) saw 30 presentations for papers during the week, mostly proposals targeting C 26, including fine-tuning for some of the above that made it into C 26 at this meeting.
The standard library evolution subgroup (LEWG) focused on advancing “big” papers in the queue that really benefit from face-to-face meetings. Notably, there is now design consensus on P1928 SIMD, P0876 Fibers, and P0843 inplace_vector, and those papers have been forwarded to the library wording specification subgroup (LWG) and may come up for adoption into C 26 at our next meeting in November. Additional progress was made on P0447 hive, P0260 Concurrent Queues, P1030 path_view, and P2781 constexpr_v.
The library wording specification subgroup (LWG) is now caught up with their backlog, and spent a lot of time iterating on the std::execution and sub_mdspan proposals (the latter was adopted this week).
The contracts subgroup made further progress on refining contract semantics targeting C 26, including to get consensus on removing build modes and having a contract violation handling API.
The concurrency and parallelism subgroup are still on track to move forward with std::execution and SIMD parallelism for C 26, which in the words of the subgroup chair will make C 26 a huge release for the concurrency and parallelism group.
Thank you to all the experts who worked all week in all the subgroups to achieve so much this week!
What’s nextOur next two meetings will be in Kona, HI, USA in November hosted by WorldQuant and the Standard C Foundation, and Tokyo, Japan in March hosted by Woven by Toyota.
Wrapping upThank you again to the over 170 experts who attended on-site and on-line at this week’s meeting, and the many more who participate in standardization through their national bodies!
But we’re not slowing down… we’ll continue to have subgroup Zoom meetings, and then in less than five months from now we’ll be meeting again in person Zoom to continue adding features to C 26. Thank you again to everyone reading this for your interest and support for C and its standardization.
May 1, 2023
cppfront: Spring update
Since the year-end mini-update, progress has continued on cppfront. (If you don’t know what this personal project is, please see the CppCon 2022 talk on YouTube.)
This update covers Acknowledgments, and highlights of what’s new in the compiler and language since last time, including:
simple, mathematically safe, and efficient chained comparisonsnamed break and continue“simple and safe” starts with . . . mainuser-defined type, including unifying all special member functions as operator= type/namespace/function/object aliasesheader reflect.h with the start of the reflection API and the first 10 working compile-time metafunctions from P0707unifying functions and blocks, including removing : and = from the for loop syntaxAcknowledgments: 267 issues, 128 pull requests, and new collaboratorsI want to say a big “thank you” again to everyone who has participated in the cppfront repo. Since the last update, I’ve merged PRs from Jo Bates, Gabriel Gerlero, jarzec, Greg Marr, Pierre Renaux, Filip Sajdak and Nick Treleaven. Thanks also to many great issues opened by (as alphabetically as I can): Abhinav00, Robert Adam, Adam, Aaron Albers, Alex, Graham Asher, Peter Barnett, Sean Baxter, Jan Bielak, Simon Buchan, Michael Clausen, ct-clmsn, Joshua Dahl, Denis, Matthew Deweese, dmicsa, dobkeratops, Deven Dranaga, Konstantin F, Igor Ferreira, Stefano Fiorentino, fknauf, Robert Fry, Artie Fuffkin, Gabriel Gerlero, Matt Godbolt, William Gooch, ILoveGoulash, Víctor M. González, Terence J. Grant, GrigorenkoPV, HALL9kv0, Morten Hattesen, Neil Henderson, Michael Hermier, h-vetinari, Stefan Isak, Tim Keitt, Vanya Khodor, Hugo Lindström, Ferenc Nandor Janky, jarzec, jgarvin, Dominik Kaszewski, kelbon, Marek Knápek, Emilia Kond, Vladimir Kraus, Ahmed Al Lakani, Junyoung Lee, megajocke, Thomas Neumann, Niel, Jim Northrup, Daniel Oberhoff, Jussi Pakkanen, PaTiToMaSteR, Johel Ernesto Guerrero Peña, Bastien Penavayre, Daniel Pfeifer, Piotr, Davide Pomi, Andrei Rabusov, rconde01, realgdman, Alex Reinking, Pierre Renaux, Alexey Rochev, RPeschke, Sadeq, Filip Sajdak, satu, Wolf Seifert, Tor Shepherd, Luke Shore, Zenja Solaja, Francis Grizzly Smit, Sören Sprößig, Benjamin Summerton, Hypatia of Sva, SwitchBlade, Ramy Tarchichy, tkielan, Marzo Sette Torres Junior, Nick Treleaven, Jan Tusil, userxfce, Ezekiel Warren, Kayla Washburn, Tyler Weaver, Will Wray, and thanks also to many others who participated on PR reviews and comment threads.
These contributors represent people from high school and undergrad students to full professors, from commercial developers to conference speakers, and from every continent except Antarctica. Thank you!
Next, here are some highlights of things added to the cppfront compiler in the four months since the previous update linked at top.
Simple, mathematically safe, and efficient chained comparisons (commit)P0515 “Consistent comparison” (aka “spaceship”) was the first feature derived from this Cpp2 work to be adopted into the ISO Standard for C++, in C++20. That means cppfront didn’t have to do much to implement operator<=> and its generative semantics, because C++20 compilers already do so, which is great. Thank you again to everyone who helped land this Cpp2 feature in the ISO C++ Standard.
However, one part of P0515 isn’t yet merged into ISO C++: chained comparisons from P0515 section 3.3, such as min <= index < max. See also Barry Revzin‘s great followup ISO C++ proposal paper P0893 “Chaining comparisons.” The cppfront compiler now implements this as described in those ISO proposal papers, and:
Supports all mathematically meaningful and safe chains like min <= index < max, with efficient single evaluation of index. (In today’s C++, this kind of comparison silently compiles but is a bug. See P0893 for examples from real-world use.)Rejects nonsense chains like a >= b < c and d != e != f at compile time. (In today’s C++, and in other languages like Python, they silently compile but are necessarily a bug because they are conceptually meaningless.)I think this is a great example to demonstrate that “simple,” “safe,” and “fast” are often not in tension, and how it’s often possible to get all three at the same time without compromises.
Named break and continue (commit)This feature further expands the Cpp2 “name :” way of introducing all names, to also support introducing loop names. Examples like the following now work… see test file pure2-break-continue.cpp2 for more examples.
outer: while i“Simple and safe” starts with . . . mainmain can now be defined to return nothing, and/or as main: (args) to have a single argument of type std::vector.
For example, here is a complete compilable and runnable program (in -pure-cpp2 mode, no #include is needed to use the C++ standard library)…
main: (args) = std::cout << "This program's name is (args[0])$";Yes, this really is 100% C++ under the covers as you can see on Godbolt Compiler Explorer… “just nicer”:
The entire C++ standard library is available directly with zero thunking, zero wrapping, and zero need to #include or import because in pure Cpp2 the entire ISO C++ standard library is just always automatically there. (Yes, if you don’t like cout, you can use the hot-off-the-press C++23 std::print too the moment that your C++ implementation supports it.)Convenient defaults, such as no need to write -> int, and no need to write braces around a single-statement function body.Convenient semantics and services, such as $ string interpolation. Again, all fully compatible with today’s C++ (e.g., string interpolation uses std::to_string where available).Type and memory safety by default even in this example: Not only is args defaulting to the existing best practices of C++ standard safety with ISO C++ vector and string_view, but the args[0] call is automatically bounds-checked by default too.type: User-defined typesUser-defined types are written using the same name : kind = value syntax as everything in Cpp2:
mytype: type ={ // data members are private by default x: std::string; // functions are public by default protected f: (this) = { do_something_with(x); } // ...}Here are some highlights…
First, types are order-independent. Cpp2 still has no forward declarations, and you can just write types that refer to each other. For example, see the test case pure2-types-order-independence-and-nesting.cpp2.
The this parameter is explicit, and has special sauce:
this is a synonym for the current object (not a pointer).this defaults to the current type.this‘s parameter passing style declares what kind of function you’re writing. For example, (in this) (or just (this) since “in” is the default as usual) clearly means a “const” member function because “in” parameters always imply constness; (inout this) means a non-const member function; (move this) expresses and emits a Cpp1 &&-qualified member function; and so on.For example, here is how to write const member function named print that takes a const string value and prints this object’s data value and the string message (yes, everything in Cpp2 is const by default except for local-scope variables):
mytype: type ={ data: i32; // some data member (private by default) print: (this, msg: std::string) = { std::cout << data << msg; // "data" is shorthand for "this.data" } // ...}All Cpp1 special member functions (including construction, assignment, destruction) and conversions are unified as operator=, default to memberwise semantics and safe “explicit” by default, and there’s a special that parameter that makes writing copy/move in particular simpler and safer. On the cppfront wiki, see the Design Note “operator=, this & that” for details. Briefly summarizing here:
The only special function every type must have is the destructor. If you don’t write it by hand, a public nonvirtual destructor is generated by default.If no operator= functions are written by hand, a public default constructor is generated by default.All other operator= functions are explicitly written, either by hand or by opting into applying a metafunction (see below).The most general form of operator= is operator=: (out this, that) which works as a unified general {copy, move} x { constructor, assignment } operator, and generates all of four of those in the lowered Cpp1 code if you didn’t write a more specific one yourself (see Design Note linked above for details).All copy/move/comparison operator= functions are memberwise by default in Cpp2 (including assignment, which is not memberwise by default in today’s Cpp1).All conversion operator= functions are safely “explicit” by default. To opt into an implicit conversion, write the implicit qualifier on the this parameter.All functions can have a that parameter which is just like this (knows it’s the current type, can be passed in all the usual ways, etc.) but refers to some other object of this type rather than the current object. It has some special sauce for simplicity and safety, including that the language ensures that the members of a that object are safely moved from only once.Note: Because generated functions are always opt-in, you can never get a generated function that’s wrong for your type, and so Cpp2 doesn’t need to support “=delete” to suppress unwanted generated functions.
Virtual functions and base classes are all about “this”:
Virtual functions are written by specifying exactly one of virtual, override, or final on the this parameter.Base classes are written as members named this. For example, just as a class could write a data member as data: string = "xyzzy";, which in Cpp2 is pronounced “data is a string with default value ‘xyzzy'”, a base class is written as this: Shape = (default, values);, which is naturally pronounced as “this IS-A Shape with these default values.” There is no separate base class list or separate member initializer list.Because base and member subobjects are all declared in the same place (the type body) and initialized in the same place (an operator= function body), they can be written in any order, including interleaved, and are still guaranteed to be safely initialized in declared order. This means that in Cpp2 you can declare a data member object before a base class object, so that it naturally outlives the base class object, and so you don’t need workarounds like Boost’s base_from_member because all of the motivating examples for that can be written directly in Cpp2. See my comments on cppfront issue #334 for details.Alias support: Type, namespace, function, and object aliases (commit)Cpp2 already defines every new entity using the syntax “name : kind = value“.
So how should it declare aliases, which declare not a new entity but a synonym for an existing entity? I considered several alternatives, and decided to try out the identical declaration syntax except changing = (which connotes value setting) to == (which connotes sameness):
// Namespace aliaslit: namespace == ::std::literals;// Type aliaspmr_vec: type == std::vector>;// Function aliasfunc :== some_original::inconvenient::function_name;// Object aliasvec :== my_vector; // note: const&, aliases are never mutableNote again the const default. For now, Cpp2 supports only read-only aliases, not read-write aliases.
Header reflect.h: Initial support for reflection API, and implementing the first 10 working metafunctions from P0707… interface, polymorphic_base, ordered, weakly_ordered, partially_ordered, basic_value, value, weakly_ordered_value, partially_ordered_value, structDisclaimer: I have not yet implemented a reflection operator that Cpp2 code can invoke, or written a Cpp2 interpreter to run inside the compiler. But I am doing everything else needed for type metafunctions: cppfront has started a usable reflection metatype API, and has started getting working metafunctions that are compile-time code that uses that metatype API… the only thing missing is that those functions aren’t run through an interpreter (yet).
For example, cppfront now supports code like the following… importantly, “value” and “interface” are not built-in types hardwired into the language as they are in Java and C# and other languages, but rather each is a function that uses the reflection API to apply requirements and defaults to the type (C++ class) being written:
// Point2D is declaratively a value type: it is guaranteed to have// default/copy/move construction and <=> std::strong_ordering// comparison (each generated with memberwise semantics// if the user didn't write their own, because "@value" explicitly// opts in to ask for these functions), a public destructor, and// no protected or virtual functions... the word "value" carries// all that meaning as a convenient and readable opt-in, but// without hardwiring "value" specially into the language//Point2D: @value type = { x: i32 = 0; // data members (private by default) y: i32 = 0; // with default values // ...}// Shape is declaratively an abstract base class having only public// and pure virtual functions (with "public" and "virtual" applied// by default if the user didn't write an access specifier on a// function, because "@interface" explicitly opts in to ask for// these defaults), and a public pure virtual destructor (generated// by default if not user-written)... the word "interface" carries// all that meaning as a convenient and readable opt-in, but// without hardwiring "interface" specially into the language//Shape: @interface type = { draw: (this); move: (inout this, offset: Point2D);}At compile time, cppfront parses the type’s body and then invokes the compile-time metafunction (here value or interface), which enforces requirements and applies defaults and generates functions, such as… well, I can just paste the actual code for interface from reflect.h, it’s pretty readable:
//-----------------------------------------------------------------------// Some common metafunction helpers (metafunctions are just// functions, so they can be factored as usual)//auto add_virtual_destructor(meta::type_declaration& t) -> void{ t.require( t.add_member( "operator=: (virtual move this) = { }"), "could not add virtual destructor");}//-----------------------------------------------------------------------// // "... an abstract base class defines an interface ..."// // -- Stroustrup (The Design and Evolution of C++, 12.3.1)////-----------------------------------------------------------------------// // interface//// an abstract base class having only pure virtual functions//auto interface(meta::type_declaration& t) -> void{ auto has_dtor = false; for (auto m : t.get_members()) { m.require( !m.is_object(), "interfaces may not contain data objects"); if (m.is_function()) { auto mf = m.as_function(); mf.require( !mf.is_copy_or_move(), "interfaces may not copy or move; consider a virtual clone() instead"); mf.require( !mf.has_initializer(), "interface functions must not have a function body; remove the '=' initializer"); mf.require( mf.make_public(), "interface functions must be public"); mf.make_virtual(); has_dtor |= mf.is_destructor(); } } if (!has_dtor) { add_virtual_destructor(t); }}Note: For now I wrote the code in today’s Cpp1 syntax, which works fine as Cpp2 is just a fully compatible alternate syntax for the same true C++… later this year I aim to start self-hosting and writing more of cppfront itself in Cpp2 syntax, including functions like these.
Note a few things that are demonstrated here:
.require (a convenience to combine a boolean test with the call to .error if the test fails) shows how to implement enforcing custom requirements. For example, an interface should not contain data members. If any requirement fails, the error output is presented as part of the regular compiler output — metafunctions extend the compiler, in a disciplined way. .make_virtual shows how to implement applying a default. For example, interface functions are virtual by default even if the user didn’t write (virtual this) explicitly. .add_member shows how to generate new members from legal source code strings. In this example, if the user didn’t write a destructor, we write a virtual destructor for them by passing the ordinary code to the .add_member function, which reinvokes the lexer to tokenize the code, the parser to generate a declaration_node parse tree from the code, and then if that succeeds adds the new declaration to this type.The whole metafunction is invoked by the compiler right after initial parsing is complete (right after we parse the statement-node that is the initializer) and before the type is considered defined. Once the metafunction returns, if it had no errors then the type definition is complete and henceforth immutable as usual. This is how the metafunction gets to participate in deciding the meaning of the code the user wrote, but does not create any ODR confusion — there is only one immutable definition of the type, a type cannot be changed after it is defined, and the metafunction just gets to participate in defining the type just before the definition is cast in stone, that’s all.The metafunction is ordinary compile-time code. It just gets invoked by the compiler at compile time in disciplined and bounded ways, and with access to bounded things.Today in cppfront, metafunctions like value and interface are legitimately doing everything envisioned for them in P0707 except for being run through an interpreter — the metafunctions are using the meta:: API and exercising it so I can learn how that API should expand and become richer, cppfront is spinning up a new lexer and parser when a metafunction asks to do code generation to add a member, and then cppfront is stitching the generated result into the parse tree as if it had been written by the user explicitly… this implementation is doing everything I envisioned for it in P0707 except for being run through an interpreter.
As of this writing, here are the currently implemented metafunctions in reflect.h are as described in P0707 section 3, sometimes with a minor name change… and including links to the function source code…
interface: An abstract class having only pure virtual functions.
Requires (else diagnoses a compile-time error) that the user did not write a data member, a copy or move operation, or a function with a body.Defaults functions to be virtual, if the user didn’t write that explicitly. Generates a pure virtual destructor, if the user didn’t write that explicitly.polymorphic_base (in P0707, originally named base_class): A pure polymorphic base type that has no instance data, is not copyable, and whose destructor is either public and virtual or protected and nonvirtual.
Requires (else diagnoses a compile-time error) that the user did not write a data member, a copy or move operation, and that the destructor is either public+virtual or protected+nonvirtual.Defaults members to be public, if the user didn’t write that explicitly. Generates a public pure virtual destructor, if the user didn’t write that explicitly.ordered: A totally ordered type with operator<=> that implements std::strong_ordering.
Requires (else diagnoses a compile-time error) that the user did not write an operator<=> that returns something other than strong_ordering.Generates that operator<=> if the user didn’t write one explicitly by hand.Similarly, weakly_ordered and partially_ordered do the same for std::weak_ordering and std::partial_ordering respectively. I chose to call the strongly-ordered one “ordered,” not “strong_ordered,” because I think the one that should be encouraged as the default should get the nice name.
basic_value: A type that is copyable and has value semantics. It must have all-public default construction, copy/move construction/assignment, and destruction, all of which are generated by default if not user-written; and it must not have any protected or virtual functions (including the destructor).
Requires (else diagnoses a compile-time error) that the user did not write some but not all of the copy/move/ construction/assignment and destruction functions, a non-public destructor, or any protected or virtual function.Generates a default constructor and memberwise copy/move construction and assignment functions, if the user didn’t write them explicitly.value: A basic_value that is totally ordered.
Note: Many of you would call this a “regular” type… but I recognize that there’s a difference of opinion about whether “regular” includes ordering. That’s one reason I’ve avoided the word “regular” here, and this way we can all separately talk about a basic_value (which may not include ordering) or a value (which does include strong total ordering; see next paragraph for weaker orderings) and we can know we’re all talking about the same thing.
Similarly, weakly_ordered_value and partially_ordered_value do the same for weakly_ordered and partially_ordered respectively. I again chose to call the strongly-ordered one “value,” not “strongly_ordered_value,” because I think the one that should be encouraged as the default should get the nice name.
struct (in P0707, originally named plain_struct because struct is a reserved word in Cpp1… but struct isn’t a reserved word in Cpp2): A basic_value where all members are public, there are no virtual functions, and there are no user-written (non-default operator=) constructors, assignment operators, or destructors.
Requires (else diagnoses a compile-time error) that the user wrote a virtual function or a user-written operator=.Defaults members to be public, if the user didn’t write that explicitly. Local statement/block parameters (commit)I had long intended to support the following unification of functions and blocks, where cppfront already provided all of these except only the third case:
f:(x: int = init) = { ... } // x is a parameter to the functionf:(x: int = init) = statement; // same, { } is implicit :(x: int = init) = { ... } // x is a parameter to the lambda :(x: int = init) = statement; // same, { } is implicit (x: int = init) { ... } // x is a parameter to the block (x: int = init) statement; // same, { } is implicit { ... } // x is a parameter to the block statement; // same, { } is implicit(Recall that in Cpp2 : always and only means “declaring a new thing,” and therefore also always has an = immediately or eventually to set the value of that new thing.)
The idea is to treat functions and blocks/statements uniformly, as syntactic and semantic subsets of each other:
A named function has all the parts: A name, a : (and therefore =) because we’re declaring a new entity and setting its value, a parameter list, and a block (possibly an implicit block in the convenience syntax for single-statement bodies).An unnamed function drops only the name: It’s still a declared new entity so it still has : (and =), still has a parameter list, still has a block.(not implemented until now) A parameterized block drops only the name and : (and therefore =). A parameterized block is not a separate entity (there’s no : or =), it’s part of its enclosing entity, and therefore it doesn’t need to capture.Finally, if you drop also the parameter list, you have an ordinary block.In this model, the third (just now implemented) option above allows a block parameter list, which does the same work as “let” variables in other languages, but without a “let” keyword. This would subsume all the Cpp1 loop/branch scope variables (and more generally than in Cpp1 today, because you could declare multiple parameters easily which you can’t currently do with the Cpp1 loop/branch scope variables).
So this now works, pasting from test case pure2-statement-scope-parameters.cpp2:
main: (args) = { local_int := 42; // 'in' statement scope variable // declares read-only access to local_int via i (i := local_int) for args do (arg) { std::cout << i << "\n"; // prints 42 } // 'inout' statement scope variable // declares read-write access to local_int via i (inout i := local_int) { i++; } std::cout << local_int << "\n"; // prints 43}Note that block parameters enable us to use the same declarative data-flow for local statements and blocks as for functions: Above, we declare a block (a statement, in this case a single loop, is implicitly treated as a block) that is read-only with respect to the local variable, and declare another to be read-write with respect to that variable. Being able to declare data flow is important for writing correct and safe code.
Corollary: Removed : and = from forEagle-eyed readers of the above example will notice a change: As a result of unifying functions and blocks, I realized that the for loop syntax should use the third syntax, not the first or second, because the loop body is a parameterized block, not a local function. So changed the for syntax from this
// previous syntaxfor items do: (item) = { x := local + item; // ...}to this, which is the same except that it removes : and =
// current syntaxfor items do (item) { x := local + item; // ...}Note that what follows for ... do is exactly a local block, just the parameter item doesn’t write an initializer because it is implicitly initialized by the for loop with each successive value in the range.
By the way, this is the first breaking change from code that I’ve shown publicly, so cppfront also includes a diagnostic for the old syntax to steer you to the new syntax. Compatibility!
Other featuresAlso implemented since last time:
As always, lots of bug fixes and diagnostic improvements.Use _ as wildcard everywhere, and give a helpful diagnostic if the programmer tries to use “auto.”Namespaces. Every namespace must have a name, and the anonymous namespace is supported by naming it _ (the “don’t care” wildcard). For now these are a separate language feature, but I’m still interested in exploring making them just another metafunction.Explicit template parameter lists. A type parameter, spelled “: type”, is the default. For examples, see test case pure2-template-parameter-lists.cpp2Add requires-clause support.Make : _ (deduced type) the default for function parameters. In response to a lot of sustained user demand in issues and comments — thanks! For example, add: (x, y) -> _ = x+y; is a valid Cpp2 generic function that means the same as (and compiles to) [[nodiscard]] auto add(auto const& x, auto const& y) -> auto { return x+y; } in Cpp1 syntax.Add alien_memory as a better spelling for T volatile. The main problem with volatile isn’t the semantics — those are deliberately underspecified, and appropriate for talking about “memory that’s outside the C++ program that the compiler can’t assume it knows anything about” which is an important low-level concept. The problems with volatile are that (a) it’s wired throughout the language as a type qualifier which is undesirable and unnecessary, and (b) the current name is confusing and has baggage and so it should be named something that connotes what it’s actually for (and I like “alien” rather than “foreign” because I think “alien” has a better and stronger connotation).Reject more implicit narrowing, notably floating point narrowing.Reject shadowing of type scope names. For example, in a type that has a member named data, a member function can’t write a local variable named data.Add support for forward return and generic out parameters.Add support for raw string literals with interpolation.Add compiler switches for compatibility with popular no-exceptions/no-RTTI modes (-fno-exceptions and -fno-rtti), specifying the output file (-o, with the optional of -o stdout), line error output (MSVC style or GCC style)Add single-word aliases (e.g., ulonglong) to replace today’s multi-keyword platform-width C types, with diagnostics support to aid migration. This is in addition to known-width Cpp2 types (e.g., i32) that are already there and should often be preferred.Allow unnamed objects (not just unnamed functions, aka lambdas) at expression scope.Reclaim many Cpp1 keywords for ordinary use. For example, a type or variable can be named “and” or “struct” in Cpp2, and it’s fully compatible (it’s prefixed with “cpp2_” when lowered to Cpp1, so Cpp1 code still has a way to refer to it, but Cpp2 gets to use the nice names). This isn’t just sugar… without this, I couldn’t write the “struct” metafunction and give it the expected nice name.Support final on a type.Add support for .h2 header files.What’s nextWell, that’s all so far.
For cppfront, over the summer and fall I plan to:
implement more metafunctions from my paper P0707, probably starting with enum and union (a safe union) — not only because they’re next in the paper, but also because I use those features in cppfront today and so I’ll need them working in Cpp2 when it comes time to…… start self-hosting cppfront, i.e., start migrating parts of cppfront itself to be written in Cpp2 syntax;continue working my list of pending Cpp2 features and implementing them in cppfront; andstart finding a few private alpha testers to work with, to start writing a bit of code in Cpp2 to alpha-test cppfront and also to alpha-test my (so far unpublished) draft documentation.For conferences:
One week from today, I’ll be at C++Now to give a talk about this progress and why full-fidelity compatibility with ISO C++ is essential (and what it means). C++Now is a limited-attendance conference, and it’s nearly sold out but the organizers say there are a few seats left… you can still register for C++Now until Friday.In early October I hope to present a major update at CppCon 2023, where registration just opened (yes, you can register now! run, don’t walk!). I hope to see many more of you there at the biggest C++ event, and that only happens once a year — like every year, I’ll be there all week long to not miss a minute.April 3, 2023
Interview on CppCast

A few days ago I recorded CppCast episode 357. Thanks to Timur Doumler and Phil Nash for inviting me on their show – and for continuing CppCast, which was so wonderfully founded by Rob Irving and Jason Turner!
This time, we chatted about news in the C++ world, and then about my Cpp2 and cppfront experimental work.
The podcast doesn’t seem to have chapters, but here are a few of my own notes about sections of interest:
00:00 Intro04:30 News: LLVM 16.0.0, “C++ Initialisation” book, new user groups15:45 Start of interview16:08 Why I don’t view Cpp2 as a “successor language”16:25 A transpiler is a compiler (see also: cfront, PyPy, TypeScript, …)17:20 Origins of the Cpp2 project, 2015/1619:00 100% compatibility as a primary goal and design constraint22:00 Avoid divergence, continue in same path C++ is already going22:50 What compatibility means: 100% link compat always on, 100% source compat always available but pay only if you need it24:14 Making the syntax unique in a simple way, start with “name :”28:10 Avoid divergence and still make a major simplification, by letting programmers directly declare their intent30:30 Bringing the pieces to ISO and the community for feedback31:55 What about “epochs”/“editions”? tl;dr: It’s exactly the right question, but I think the right answer is “epoch” (singular)35:42 C++ is popular and will endure no matter what we do; question is can we make it nicer37:05 My personal experiment, and others are now helping38:20 What “safeties” I’m targeting, and what degree of safety, and why formally provable guarantees are nice but neither necessary nor sufficient (I expect this view to be controversial)44:00 The issue is making things 50x (98%) safer, vs. 100% safer, because what does requiring that last 2% actually cost the design in incompatibility / difficulty of use47:05 The zero-overhead principle is non-negotiable, and so is always being able to “open the hood” to take control, otherwise it’s not C++ anymore48:20 Examples: dynamic bounds/null checking is opt-out, now the default but still pay for it only if you use it50:20 Will cppfront support all major compilers and platforms? It already does, any reasonably conforming C++20 compiler (any gcc/clang/msvc since about 2020), and that will continue52:15 Keeping the generated source code very close to the original is a priority53:25 “TypeScript for C++” plan vs. “Dart for C++” plan55:20 TypeScript did what Bjarne’s cfront did: Transpiler that let you always keep your generated JavaScript/C code, so you could drop using the new language anytime if you want with #NoRegrets, risk-free57:20 Shout out to Anders Hejlsberg, IIRC the only human to produce a million-user programming language more than once, and his approach to TypeScript vs. C#59:20 Why generating C++ code isn’t in tension with the goal of compatibility (it’s actually synergistic), and the targeted subset is C++20 (with a workaround only when modules are not yet available on a given compiler)1:00:40 Why C++20 is super important (if constexpr, requires-expressions)1:01:40 Why any C++ evolution/successor language attempt that for now only tries to be compatible with C++17 faces a big hill/disadvantage1:02:40 What’s next for Cpp2 and cppfront1:05:35 Where can people learn more: cppfront repo, CppCon 2022 talk, C++Now talk coming up in a month, then CppCon 2023 in October1:07:28 C++ world is alive and well and thriving, now embracing challenges like safety to keep C++ working well for all of usIn at least one place I said “cppfront” where I meant “cfront”… I think the intent should be clear from context.
Thanks again to everyone who has helped me personally with cppfront through issues and PRs, and to all the good folks who helped the entire C++ world by working hard and creatively through the pandemic and shipping another solid release of C++ in C++23.
February 13, 2023
C++23 “Pandemic Edition” is complete (Trip report: Winter ISO C++ standards meeting, Issaquah, WA, USA)
On Saturday, the ISO C++ committee completed technical work on C++23 in Issaquah, WA, USA! We resolved the remaining international comments on the C++23 draft, and are now producing the final document to be sent out for its international approval ballot (Draft International Standard, or DIS) and final editorial work, to be published later in 2023.
Our hosts, the Standard C++ Foundation, WorldQuant, and Edison Design Group, arranged for high-quality facilities for our six-day meeting from Monday through Saturday. We had about 160 attendees, more than half in-person and the others remote via Zoom. We had 19 nations formally represented, 9 in-person and 10 via Zoom. Also, at each meeting we regularly have new attendees who have never attended before, and this time there were 25 new first-time attendees in-person or on Zoom; to all of them, once again welcome!

The C++ committee currently has 26 active subgroups, 13 of which met in parallel tracks throughout the week. Some groups ran all week, and others ran for a few days or a part of a day and/or evening, depending on their workloads. You can find a brief summary of ISO procedures here.
From Prague, through the pandemic, to an on-time C++23 “Pandemic Edition”The previous standard, C++20, was completed in Prague in February 2020, a month before the pandemic lockdowns began. At that same meeting, we adopted and published our C++23 schedule… without realizing that the world was about to turn upside down in just a few weeks. Incredibly, thanks to the effort and resilience of scores of subgroup chairs and hundreds of committee members, we still did it: Despite a global pandemic, C++23 has shipped exactly on time and at high quality.
The first pandemic-cancelled in-person meeting would have been the first meeting of the three-year C++23 cycle. This meant that nearly all of the C++23 release cycle, and the entire “development” phase of the cycle, was done virtually via Zoom with many hundreds of telecons from 2022 through 2022. Last week’s meeting was only our second in-person meeting since February 2020, and our second-ever hybrid meeting with remote Zoom participation. Both had a high-quality hybrid Zoom experience for remote attendees around the world, and I want to repeat my thanks from November to the many volunteers who worked hard and carried hardware to Kona and Issaquah to make this possible. I want to again especially thank Jens Maurer and Dietmar Kühl for leading that group, and everyone who helped plan, equip, and run the meetings. Thank you very much to all those volunteers and helpers!
The current plan is that we’ve now returned to our normal cadence of having full-week meetings three times a year, as we did before the pandemic, but now those will be not only in-person but also have remote participation via Zoom. Most subgroups will additionally still continue to meet regularly via Zoom.
This week’s meetingPer our published C++23 schedule, this was our final meeting to finish technical work on C++23. No features were added or removed, we just handled fit-and-finish issues and primarily focused on finishing addressing the 137 national body comments we received in the summer’s international comment ballot (Committee Draft, or CD). You can find a list of C++23 features here, many of them already implemented in major compilers and libraries. C++23’s main theme was “completing C++20,” and some of the highlights include module “std”, “if consteval,” explicit “this” parameters, still more constexpr, still more CTAD, “[[assume]]”, simplifying implicit move, multidimensional and static “operator[]”, a bunch of Unicode improvements, and Nicolai Josuttis’ personal favorite: fixing the lifetime of temporaries in range-for loops (some would add, “finally!”… thanks again for the persistence, Nico).
In addition to C++23 work, we also had time to make progress on a number of post-C++23 proposals, including continued work on contracts, SIMD execution, and more. We also decided to send the second Concurrency TS for international comment ballot, which includes hazard pointers, read-copy-update (RCU) data structures… and as of this week we also added Anthony Williams’ P0290 “synchronized_value” type.
The contracts subgroup made further progress on refining contract semantics targeting C++26.
The concurrency and parallelism subgroup is still on track to move forward with “std::execution” and SIMD parallelism for C++26, which in the words of the subgroup chair will make C++26 a huge release for the concurrency and parallelism group.
Again, when you see “C++26” above, that doesn’t mean “three long years away”… we just closed the C++23 branch, and the C++26 branch is opening immediately and we will start approving features for C++26 at our next meeting in June, less than four months from now. Implementers interested in specific features often don’t wait for the final standard to start shipping implementations; note that C++23, which was just finished, already has many features shipping today in major implementations.
The newly-created SG23 Safety and Security subgroup met on Thursday for a well-attended session on hitting the ground running for making a targeted improvement in safety and security in C++, including that it approved the first two safety papers to progress to review next meeting by the full language evolution group.
Thank you to all the experts who worked all week in all the subgroups to achieve so much this week!
What’s nextOur next two meetings will be in Varna, Bulgaria in June and in Kona, HI, USA in November. At those two meetings we will start work on adding features into the new C++26 working draft.
Wrapping upThank you again to the approximately 160 experts who attended on-site and on-line at this week’s meeting, and the many more who participate in standardization through their national bodies!
But we’re not slowing down… we’ll continue to have subgroup Zoom meetings, and then in less than four months from now we’ll be meeting again in Bulgaria to start adding features to C++26. I look forward to seeing many of you there. Thank you again to everyone reading this for your interest and support for C++ and its standardization.
December 31, 2022
Cpp2 and cppfront: Year-end mini-update
As we close out 2022, I thought I’d write a short update on what’s been happening in Cpp2 and cppfront. If you don’t know what this personal project is, please see the CppCon 2022 talk on YouTube.
Most of this post is about improvements I’ve been making and merging over the year-end holidays, and an increasing number of contributions from others via pull requests to the cppfront repo and in companion projects. Thanks again to so many of you who expressed your interest and support for this personal experiment, including the over 3,000 comments on Reddit and YouTube and the over 200 issues and PRs on the cppfront repo!
10 design notesOn the cppfront wiki, I’ve written more design notes about specific parts of the Cpp2 language design that answer common questions. They include:
Broad strategic topics, such as addressing ABI and versioning, “unsafe” code, and aiming to eliminate the preprocessor with reflection.Specific language feature design topics, such as unified function call syntax (UFCS), const, and namespaces.Syntactic choices, such as postfix operators and capture syntax.Implementation topics, such as parsing strategies and and grammar details. 117 issues (3 open), 74 pull requests (9 open), 6 related projects, and new collaboratorsI started cppfront with “just a blank text editor and the C++ standard library.” Cppfront continues to have no dependencies on other libraries, but since I open-sourced the project in September I’ve found that people have started contributing working code — thank you! Authors of merged pull requests include:
The prolific Filip Sajdak contributed a number of improvements, probably the most important being generalizing my UFCS implementation, implementing more of is and as as described in P2392, and providing Apple-Clang regression test results. Thanks, Filip!Gabriel Gerlero contributed refinements in the Cpp2 language support library, cpp2util.h.Jarosław Głowacki contributed test improvements and ensuring all the code compiles cleanly at high warning levels on all major compilers.Konstantin Akimov contributed command-line usability improvements and more test improvements.Fernando Pelliccioni contributed improvements to the Cpp2 language support library.Jessy De Lannoit contributed improvements to the documentation.Thanks also to these six related projects, which you can find listed on the wiki:
Conan recipe for cppfront by Fernando Pelliccioni. /modern-cmake/cppfront , a modern CMake (3.23+) build for cppfront. /JohelEGP/jegp.cmake_modules/#jegpcpp2 , a CMake module to build Cpp2 source files. Meson support for cppfront by Jussi Pakkanen, creator of Meson. Sublime syntax highlighting for Cpp2 by Andrew Feldman. by Elazar Cohen. (I use this by default.)Thanks again to Matt Godbolt for hosting cppfront on Godbolt Compiler Explorer and giving feedback.
Thanks also to over 100 other people who reported bugs and made suggestions via the Issues. See below for some more details about these features and more.
Compiler/language improvementsHere are some highlights of things added to the cppfront compiler since I gave the first Cpp2 and cppfront talk in September. Most of these were implemented by me, but some were implemented by the PR authors I mentioned above.
Roughly in commit order (you can find the whole commit history here), and like everything else in cppfront some of these continue to be experimental:
Lots of bug fixes and diagnostic improvements.Everything compiles cleanly under MSVC -W4 and GCC/Clang -Wall -Wextra.Enabled implicit move-from-last-use for all local variables. As I already did for copy parameters.After repeated user requests, I turned -n and -s (null/subscript dynamic checking) on by default. Yes, you can always still opt out to disable them and get zero cost, Cpp2 will always stay a “zero-overhead don’t-pay-for-what-you-don’t-use” true-C++ environment. All I did was change the default to enable them.Support explicit forward of members/subobjects of composite types. For a parameter declared forward x: SomeType, the default continues to be that the last use of x is automatically forwarded for you; for example, if the last use is call_something( x ); then cppfront automatically emits that call as call_something( std::forward(x) ); and you never have to write out that incantation. But now you also have the option to separately forward parts of a composite variable, such as that for a forward x: pair> parameter you can write things like do_this( forward x.first ) and do_that( 1, 2, 3, forward x.second ).Support is template-name and is ValueOrPredicate: is now supports asking whether this is an instantiation of a template (e.g., x is std::vector), and it supports comparing values (e.g., x is 14) and using predicates (e.g., x is (less_than(20)) invoking a lambda) including for values inside a std::variant, std::any, and std::optional (e.g., x is 42 where x is a variant or an any).Regression test results for all major compilers: MSVC, GCC, Clang, and Apple-Clang. All are now checked in and can be conveniently compared before each commit.Finished support for >> and >>= expressions. In today’s syntax, C++ currently max-munches the >> and >>= tokens and then situationally breaks off individual > tokens, so that we can write things like vector> without putting a space between the two closing angle brackets. In Cpp2 I took the opposite choice, which was to not parse >> or >>= as a token (so max munch is not an issue), and just merge closing angles where a >> or >>= can grammatically go. I’ve now finished the latter, and this should be done.Generalized support for UFCS. In September, I had only implemented UFCS for a single call of the form x.f(y), where x could not be a qualified name or have template arguments. Thanks to Filip Sajdak for generalizing this to qualified names, templated names, and chaining multiple UFCS calls! That was a lot of work, and as far as I can tell UFCS should now be generally complete.Support declaring multi-level pointers/const.Zero-cost implementation of UFCS. The implementation of UFCS is now force-inlined on all compilers. In the tests I’ve looked at, even when calling a nonmember function f(x,y), using Cpp2’s x.f(y) unified function call syntax (which tries a member function first if there is one, else falls back to a nonmember function), the generated object code at all optimization levels is now identical, or occasionally better, compared to calling the nonmember function directly. Thanks to Pierre Renaux for pointing this out!Support today’s C++ (Cpp1) multi-token fundamental types (e.g., signed long long int). I added these mainly for compatibility because 100% seamless interoperability with today’s C++ is a core goal of Cpp2, but note that in Cpp2 these work but without any of the grammar and parsing quirks they have in today’s syntax. That’s because I decided to represent such multi-word names them as a single Cpp2 token, which happens to internally contain whitespace. Seems to work pretty elegantly so far.Support fixed-width integer type aliases (i32, u64, etc.), including optional _fast and _small (e.g., i32_fast).I think that this completes the basic implementation of Cpp2’s initial subset that I showed in my talk in September, including that support for multi-level pointers and the multi-word C/C++ fundamental type names should complete support for being able to invoke any existing C and C++ code seamlessly.
Which brings us to…
What’s nextNext, as I said in the talk, I’ll be adding support for user-defined types (classes)… I’ll post an update about that when there’s more that’s ready to see.
Again, thanks to everyone who expressed interest and support for this personal experiment, and may you all have a happy and safe 2023.
November 12, 2022
Trip report: Autumn ISO C++ standards meeting (Kona)
A few minutes ago, the ISO C++ committee completed its second-to-last meeting of C++23 in Kona, HI, USA. Our host, the Standard C++ Foundation, arranged for high-quality facilities for our six-day meeting from Monday through Saturday. We currently have 26 active subgroups, nine of which met in six parallel tracks throughout the week; some groups ran all week, and others ran for a few days or a part of a day, depending on their workloads. We had over 160 attendees, approximately two-thirds in-person and one-third remote via Zoom.
This was our first in-person meeting since Prague in February 2020 just a few weeks before the lockdowns began. It was also our first-ever hybrid meeting with remote Zoom participation for all subgroups that met.
You can find a brief summary of ISO procedures here.
From Prague, through the pandemic, to KonaDuring the pandemic, the committee’s subgroups began regularly meeting virtually, and over the past nearly three years there have been hundreds of virtual subgroup meetings and thrice-a-year virtual plenary sessions to continue approving features for C++23.
This week, we resumed in-person meetings with remote Zoom support. In the months before Kona, a group of volunteers did a lot of planning and testing: We did a trial run of a hybrid meeting with the subgroup SG14 at CppCon in September, using some of the equipment we planned to use in Kona. That initial September test was a pretty rough experience for many of the remote attendees, but it led to valuable learnings , and though we entered Kona with some trepidation, the hybrid meetings went amazingly smoothly with very few hiccups, and we got a lot of good work done in the second-to-last meeting to finalize C++23 including with remote presentations and comments.
This was only possible because of a huge amount of work by many volunteers, and I want to especially thank Jens Maurer and Dietmar Kühl for leading that group. But it was a true team effort, and so many people helped with the planning, with bringing equipment, and with running the meetings. Thank you very much to all those volunteers and helpers! We received many such appreciative comments of thanks on the committee mailing lists, and from national bodies on Saturday, from experts participating remotely who wanted to thank the volunteers for how smoothly they were able to participate.
Now that we have resumed in-person meetings, the current intent is that:
This week’s meetingPer our published C++23 schedule, this was our second-to-last meeting to finish technical work on C++23. No features were added or removed, we just handled fit-and-finish issues and primarily focused on addressing the 137 national body comments we received in the summer’s international comment ballot (Committee Draft, or CD).
Today, the committee approved final resolutions for 92 (67%) of the 137 national comments. That leaves 45 comments, some of which have already been partly worked on, still to be completed between now and early February at our last meeting for completing C++23.
An example of a comment we just approved is adopting the proposal from Nicolai Josuttis et al. to extend the lifetime all temporaries (not just the last one) for the for-range-initializer of the range-for loop (see also the more detailed earlier paper). This closes a lifetime safety hole in C++. Here’s one of the many examples that will now work correctly:
std::vector createStrings();...for (std::string s : createStrings()) ... // OKfor (char c : createStrings().at(0)) ... // use-after-free in C++20 // OK, safe in C++23In addition to C++23 work, we also had time to make progress on a number of post-C++23 proposals, including continued work on contracts, executors (std::execution), pattern matching, and more. We also decided to ship the third Library Fundamentals TS, which includes support for a number of additional experimental library features such as propagate_const, scope_exit and related scope guards, observer_ptr, resource_adapter, a helper to make getting a random numbers easier, and more. These can then be considered for C++26.
The contracts subgroup adopted a roadmap and timeline to try to get contracts into C++26. The group also had initial discussion of Gabriel Dos Reis’ proposal to control side effects in contracts, with the plan to follow up with a telecon between now and the next in-person meeting in February.
The concurrency and parallelism subgroup agreed to move forward with std::execution and SIMD parallelism for C++26, which in the words of the subgroup chair will make C++26 a huge release for the concurrency and parallelism group… and recall that C++26 is not just something distant that’s three years away, but we will start approving features for C++26 starting this June, and when specific features are early and stable in the working draft the vendors often don’t wait for the final standard to start shipping implementations.
The language evolution group considered national body comments and C++26 proposals, and approved nine papers for C++26 including to progress Jean-Heyd Meneide’s proposal for #embed for C++26.
The language evolution group also held a well-attended evening session (so that experts from all subgroups could participate) to start discussion of the long-term future of C++, with over 100 experts attending (75% on-site, 25% on-line). Nearly all of the discussion was focused on improving safety (mostly) and simplicity (secondarily), including discussion about going beyond our business-as-usual evolution to help C++ programmers with these issues. We expect this discussion to continue and lead to further concrete papers for C++ evolution.
The library evolution group addressed all its national body comments and papers, forwarded several papers for C++26 including std::execution, and for the first time in a while does not have a backlog to catch up with which was happy news for LEWG.
Thank you to all the experts who worked all week in all the subgroups to achieve so much this week!
What’s nextOur next meeting will be in Issaquah, WA, USA in February. At that meeting we will finish C++23 by resolving the remaining national body comments on the C++23 draft, and producing the final document to be sent out for its international approval ballot (Draft International Standard, or DIS) and be published later in 2023.
Wrapping upBut we’re not slowing down… we’ll continue to have subgroup Zoom meetings, and then in less than three months from now we’ll be meeting again in Issaquah, WA, USA for the final meeting of C++23 to finish and ship the C++23 international standard. I look forward to seeing many of you there. Thank you again to the over 160 experts who attended on-site and on-line at this week’s meeting, and the many more who participate in standardization through their national bodies! And thank you also to everyone reading this for your interest and support for C++ and its standardization.
October 10, 2022
Weekend update: Operator and parsing design notes
Thanks again for all the bug reports and feedback for Cpp2 and cppfront! As I mentioned last weekend, I’ve started a wiki with “Design notes” about specific aspects of the design to answer why I’ve made them they way they currently are… basic rationale, alternatives considered, in a nutshell, as quick answers to common questions I encounter repeatedly.

This weekend I wrote up three more short design notes, the first of which is the writeup on “why postfix unary operators?” that I promised in my CppCon 2022 talk.
Design note: Postfix operators. Why are unary * and & postfix? What about ++? Why does the -> operator naturally disappear?Design note: Postfix unary operators vs binary operators. What about examples like a**b?Design note: Unambiguous parsing. How does Cpp2 parse things that seem ambiguous and/or challenging, like ac,d>?October 4, 2022
Cpp2 design notes: UFCS, “const”, “unsafe”, and (yes) ABI
Thanks to everyone who has offered bug reports and constructive suggestions for Cpp2 and cppfront.
To answer common questions I encounter repeatedly, I’ve started a wiki with “Design notes” about specific aspects of the design to answer why I’ve made them they way they currently are… basic rationale, alternatives considered, in a nutshell. There are four design notes so far… pasting from the wiki:
Design note: UFCS Why does UFCS use fallback semantics (prefer a member function)? Doesn’t that mean that adding a member function later could silently change behavior of existing call sites? Design note: const objects by default Should objects be const? Mostly yes. Design note: unsafe code Yes, I intend that we should be able to write very-low-level facilities in Cpp2. No, that doesn’t mean a monolithic “unsafe” block… I think we can do better. Design note: ABI Cpp2 is ABI-neutral, but its immunity from backward compatibility constraints presents an opportunity for link-level improvements, not just source-level improvements.The wiki also contains links to related projects. There are two of those so far:
/modern-cmake/cppfront: A modern CMake (3.23+) build for cppfront, complete with automatic cpp2-to-cpp1 translation. Compatible with find_package, add_subdirectory, and FetchContent./JohelEGP/jegp.cmake_modules/#jegpcpp2: A CMake module to build Cpp2 source files.Thanks again for the feedback and interest.
Herb Sutter's Blog
- Herb Sutter's profile
- 32 followers
