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 notes

On 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 collaborators

I 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:

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 improvements

Here 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<decltype(x)>(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<string, string>> 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<int,string> 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<vector<int>> 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 next

Next, 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.

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 Kona

During 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 meeting

Per 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<std::string> createStrings();

...

for (std::string s : createStrings()) ... // OK

for (char c : createStrings().at(0)) ...
    // use-after-free in C++20
    // OK, safe in C++23

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, 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 next

Our 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 up

But 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.

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.

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:

Thanks again for the feedback and interest.

Something I implemented today: “is void”

[Edited to add pre-publication link to next draft of P2392, revision 2, and correct iterator comparison]

Brief background

As I presented at CppCon 2021 starting at 11:15, I’m proposing is (a general type or value query) and as (a general cast, for only the safe casts) for C++ evolution. The talk, and the ISO C++ evolution paper P2392 it’s based on, explained why I hope that is and as can provide a general mechanism to power pattern matching with inspect, while conversely also liberating the power of pattern matching beyond just inspect for use generally in the language (e.g., in requires clauses, in general code). Here’s the key slide from last year, that I cited again in this year’s talk:

Note this isn’t about “making it look pretty.” is and as do lead to simpler and prettier code, but whereas human programmers love clear and consistent spellings, generic code demands that consistency. Here’s the key 1-min clip from last year, summarizing the argument in a nutshell:

Today: Divergent emptiness

In that vein, today I was catching up with some cppfront PRs, and Drew Gross pointed out that as I’ve begun implementing is and as in Cpp2 syntax, one thing didn’t work as Drew expected:

is std::nullopt_t doesn’t appear to match an empty optional

Drew Gross in cppfront PR #5

That got me thinking.

First, that this wasn’t supported in the current P2392 was intentional, because nullopt_t isn’t a “real type”… it’s a signal for “empty / no value” which until now wasn’t covered in P2392. Recall that is and as are related, including that if is T succeeds it means that a dynamic as T cast to the same type will succeed. But that’s not true for nullopt_t, which is optional‘s way of signaling an empty state; you can’t cast to “no type.”

Still, this got me thinking that testing for “empty” could be useful. And if we do provide an is test for an empty optional, it makes sense for there not to be an as cast for that, which simplifies how we think about it. And it should be spelled generically in a way that works equally for other kind of empty things, so we wouldn’t want to spell it is nullopt_t because that “empty state” name is specific to optional only. It is one of many existing divergent ad-hoc spellings we’ve added for “empty state” (just like we had lots of divergent spellings of type queries and type casts):

  • nullopt_t is the empty state for std::optional
  • nullptr_t is the empty state for raw/smart pointers
  • More generally, the default-constructed state T() is the empty state for all Pointer-like things including iterators, and this is already the way the Lifetime profile handles it: a Pointer is any type that can be dereferenced, and a default-constructed Pointer is considered its null/empty value (including that an STL iterator is treated identically to a default-constructed (null) raw or smart pointer) and you can already see this in cppfront’s cpp2util.h null test (currently at line 298). [Edited to add: Note that it turns out the Standard doesn’t make this usefully testable for STL iterators, because it says that a default-constructed STL iterator can only be reliably compared to another default-constructed one. So while the default-constructed state is indeed an “empty” state for the iterator, as far as I know there is no way to portably test whether a given STL iterator object is actually in that state. Equality testing against a default-constructed iterator may work or it may not.]
  • monostate (and arguably valueless_by_exception) is the empty state for std::variant
  • !has_value is the empty state for std::any
  • !is_ready (which has a longer spelling in today’s standard library) is the empty state for std::*future

And so we have an opportunity to unify these too, which goes beyond what I showed last year and in my previous revision of paper P2392.

But wait, on top of all that, the language itself has already had a way to spell “no type” since the 1970s: void. And even though void is not a regular type (it doesn’t work as a type in some places in the C++ type system) it works in enough of the places we need to implement is void as the generic spelling of “is empty.”

A possible convergence: is void

So today I implemented is void as a generic “empty state” test in Cpp2 syntax in cppfront. I also checked in the following Cpp2-syntax test case, which now works as self-documented — and I couldn’t resist the nod to William Tyndale:

main: () -> int = {
    p: std::unique_ptr<int> = ();
    i: std::vector<int>::iterator = ();  // see "edited to add" note above
    v: std::variant<std::monostate, int, std::string> = ();
    a: std::any = ();
    o: std::optional<std::string> = ();

    std::cout << "\nAll these cases satisfy \"VOYDE AND EMPTIE\"\n";

    test_generic(p);
    test_generic(i);
    test_generic(v);
    test_generic(a);
    test_generic(o);
}

test_generic: ( x: _ ) = {
    std::cout
        << "\n" << typeid(x).name() << "\n    ..."
        << inspect x -> std::string {
            is void = " VOYDE AND EMPTIE";
            is _    = " no match";
           }
        << "\n";
}

Note that this generic function would be impossible to write without some kind of is void unification to eliminate all of today’s non-generic divergent “empty state” queries.

Here’s the result on my machine in Ubuntu using GCC and libstdc++… I’m glad to show GCC here after having my machine’s WSL 2 subsystem quit on me on-stage so that I couldn’t show the GCC and Clang live demos in the CppCon 2022 talk (sigh!):

Implementing it ensured the implementation worked, including that it exposed where an if constexpr is needed for std::variant‘s is void test (see cpp2util.h, currently lines 610-614; note that empty is an alias for void). Once I got it working in cppfront (prototypes matter! they help us debug our proposals) I added it to the next draft of my ISO C++ proposal paper P2392 for is/as/inspect in today’s Cpp1 syntax, including the suggested implementation.

Thanks, Drew!

My CppCon 2022 talk is online: “Can C++ be 10x simpler & safer … ?”

It was great to see many of you at CppCon, in person and online! It was a really fun conference this year, and the exhibitor hall felt crowded again which was a good feeling as we all start traveling more again.

The talk I gave on Friday is now on YouTube. In it I describe my experimental work on a potential alternate syntax for C++ (aka ‘syntax 2’ or Cpp2 for short) and my cppfront compiler that I’ve begun writing to implement it.

I hope you enjoy the talk. You can find cppfront at the GitHub repo here: