cppfront: Midsummer update

Over the winter and spring I spent a bunch of time building my essay “C++ safety, in context” and the related ACCU 2024 safety keynote, and on behind-the-scenes work toward improving C++ memory safety that you’ll be hearing more about in the coming months (including a lock-free data structure that’s wait-free and constant-time for nearly … Continue reading cppfront: Midsummer update →

Jan 27, 2025 - 01:05
 0
cppfront: Midsummer update

Over the winter and spring I spent a bunch of time building my essay “C++ safety, in context” and the related ACCU 2024 safety keynote, and on behind-the-scenes work toward improving C++ memory safety that you’ll be hearing more about in the coming months (including a lock-free data structure that’s wait-free and constant-time for nearly all operations; that was fun to develop and could be useful for making certain existing C and C++ code safer, we’ll see). Safety and simplicity are the two core things I want to try to dramatically improve in C++, and are why I’m doing my cppfront experiment, so although the above absorbed some time away from cppfront coding it all contributes to the same goal. (If you don’t know what cppfront is, please see the CppCon 2022 talk for an overview, and the CppCon 2023 talk for an update and discussion of why cppfront is pursuing this evolution strategy that’s different from other projects’. “Cpp2” is the shorthand name for my experimental “C++ syntax 2,” and cppfront is the open-source compiler that compiles it.)

So now it’s time for a cppfront update with some highlights of what’s been happening since the last time I posted about it here:

  • Wrote Cpp2 and cppfront documentation & started numbered releases
  • Cppfront 0.7.0 (Mar 16, 2024), new things include:
    • A “tersest” function syntax, e.g.: :(x,y) x>y
    • Support all C++23 and draft C++26 headers that have feature test flags
    • Tracked contracts changes in WG21 (e.g., P2661R1)
    • Generate Cpp1 postfix inc/dec in term of prefix
    • Allow trailing commas in lists
    • More CI
  • Cppfront 0.7.1 (Jul 10, 2024), new things include:
    • Added .. non-UFCS members-only call syntax
    • Allow x: const = init; and x: * = init; (const and pointer deduction without explicit _ placeholder)
    • Allow concatenated string literals
    • Faster compile time when doing heavy reflection and code generation
    • Added -quiet and -cwd
  • Cppfront 0.7.2 (Jul 27, 2024), new things include:
    • Added range operators ... and ..= 
    • Added a compile-time @regex metafunction via reflection and code generation, by Max Sagebaum
    • Added support for function types (e.g., std::function< (i: inout int) -> forward std::string >)
    • Added support for C++23 (P2290) delimited hexadecimal escapes

Updated acknowledgments: 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… I think…).

Cpp2 and cppfront documentation

As of this spring, we now have Cpp2 and cppfront documentation! Last fall at CppCon 20243, the #1 cppfront request was “please write documentation!” So now that the language is stable, over the winter I got up and running with MkDocs via James Willett’s wonderful YouTube tutorial. Thanks to everyone who provided feedback and fixes!

Official releases

With documentation in place and a stable language, I felt it was time to start numbering releases. There have been three releases so far… here are each one’s highlights.

Cppfront 0.7.0 (Mar 16, 2024)

Initial feature set complete, including documentation. Here are a few highlights added since my September 2023 update blog post:

Added a “tersest” function syntax. Functions, including unnamed function expressions (aka lambdas), that do nothing but return an expression can now omit everything between ) and the expression. Here’s an example… note that this is a whole program, because compiling in -pure-cpp2 mode by default makes the entire standard library is available (efficiently imported as the std module if your compiler supports that):

main: () = {
    vec: std::vector = (1, 3, 5, 2, 4, 6);
    std::ranges::sort( vec, :(x,y) x>y );
    for vec do (e) std::cout << e;  // prints: 654321
}

If, like me, you’re quite used to UFCS, you can also write this to put vec. first, which means the same thing: vec.std::ranges::sort( :(x,y) x>y );

And with a using namespace std::ranges nearby, you can write just vec.sort( :(x,y) x>y ); … Now, that’s how I like to write clean C++20 code, and it is indeed (after lowering) just all-ordinary C++20 code.

Support all C++23 and draft C++26 headers that have feature test flags. When you use -import-std or -include-std to make the whole C++ standard library available, cppfront tracks the ever-growing list of standard headers so that you can always use cppfront with the latest Standard C++.

Tracked contracts changes in WG21 (e.g., P2661R1). As soon as the committee’s contracts subgroup decided to switch from notation like [[pre: expression]] to pre( expression ), I made the same change in cppfront. Note that cppfront still goes beyond the “minimum viable product” being proposed for C++23, because cppfront has always supported things like contract groups and customizable violation handlers. I also upgraded the contracts implementation to guarantee any contract’s expression will not be evaluated at all unless the contract is actually being checked, plus I provide an explicitly unevaluated group that is never evaluated to express contracts that are for static analyzers only (they can contain any grammatically valid expressions).

Generate Cpp1 postfix increment/decrement in terms of prefix. Cpp2 allows only “increment/decrement in place” operators, but when lowering to Cpp1 it generates both that and the “increment/decrement and return old value” Cpp1 operators. You can write a type in Cpp2 syntax that provides the operator once, and both versions are available to Cpp1 code that consumes the Cpp2-authored type (which is just an ordinary C++ type anyway, just written in a different syntax).

Allow trailing commas in lists. I got lots of requests for this, and Cpp2 now allows an extra redundant trailing comma at the end of any comma-delimited list. This is desirable because it lets programmers have cleaner source code diffs when adding a parameter or argument (if the source already has parameters/arguments on their own lines), and it slightly simplifies metafunctions’ code generation because they don’t have to track whether to suppress the last comma (and I think it’s important to make source code generation use cases easier). For a bit more rationale, see this Issue comment that goes with the commit. Edited to add: And the “Design note: Commas” that covers it in even more detail.

More CI: Besides build tests, added regression testing scripts and workflows. Thanks to Jaroslaw Glowacki and Johel Ernesto Guerrero Peña!

Cppfront 0.7.1 (Jul 10, 2024)

This release added several features.

Added .. non-UFCS members-only syntax. This syntax invokes only a member function, and will not fall back to free functions. The main motivation was to provide a way to experiment if the UFCS logic noticeably added to Cpp1 compile times; I haven’t been able to verify that they cause much more compile time impact than an average-complexity line of code, but this way people can try it out. Example: mystring..append("foo");

Allow x: const = init; and x: * = init; without writing the _ type wildcard, in cases where all we want to say is that the deduced type is const or a pointer.

Allow concatenated string literals. For example, writing "Hello " "(name$)\n" now works. Note in that example that one string literal doesn’t use interpolation, and one does, and that’s fine.

Faster compile time when doing heavy reflection and code generation. I haven’t had to optimize cppfront much yet because it’s been fast in all my uses, but Max Sagebaum exercised metafunctions heavily for @regex (covered below, in 0.7.2) and made me find another ~100x performance improvement (thanks Max!). — This is the second optimization I can recall in cppfront so far, after Johel Ernesto Guerrero Peña‘s ~100x improvement last year by just changing the four uses of std::regex to find_if on a sorted vector of strings.

Added some command-line options: -quiet (thanks Marek Knápek for the suggestion!) and -cwd (thanks Fred Helmesjö for the PR!) command-line options.

Cppfront 0.7.2 (Jul 27, 2024)

This release added a few features, a couple of which are major and/or long-requested.

Added range operators ... and ..= . . I deliberately chose to make the default syntax ... mean a half-open range (like Rust, unlike Swift) so that it is bounds-safe by default for iterator ranges; for a “closed” range that includes the last value, use ..= . For example: iter1 ... iter2 is a range that safely doesn’t include iter2, as we expect for STL iterators; and 1 ..= 100 is a range of integer values that includes 100. For more, see the documentation here: ... and ..= range operators.

Added a compile-time @regex metafunction by Max Sagebaum. @regex uses compile-time reflection and source code generation via Cpp2 metafunctions to achieve compile- and run-time performance comparable to Hana Dusíková‘s groundbreaking CTRE library that uses today’s templates and other metaprogramming for the compile-time work. Thanks, Max! See the last ~10 minutes of my ACCU 2024 talk for an overview of CTRE and @regex and our initial performance results… the talk was a preview of the PR that was then merged in 0.7.2.

Add support for function types. You can now write a function signature as a type, such as to use with std::function (e.g., std::function< (i: inout int) -> forward std::string >) or to write a pointer to a function (e.g., pfn: *(i: inout int) -> forward std::string). For more, see the documentation here: Using function types. Thanks to Johel Ernesto Guerrero Peña for all his contributions toward function types in particular, and for his scores of merged PRs over the past year!

Added support for C++23 (P2290) delimited hexadecimal escapes. Didn’t notice these were in C++23? Neither did I! Thanks to Max Sagebaum for the PR to add support for escapes of the form \x{62}… you can now use them in Cpp2 code and they’ll work if your Cpp1 compiler understands them. Note that cppfront itself and its generated code don’t have a dependency on them; the cppfront philosophy is to “rely only on C++20 [in cppfront and its code gen requirements] for compatibility and portability, and support C++23/26 as a ‘light-up’ [people can use those features in their own Cpp2 code and they work if their Cpp1 compiler supports them].”

What’s next

For the rest of the year, I plan to:

  • Have regular cppfront releases, and post an update here for each one.
  • Share some updates about the cppfront license (currently it’s a non-commercial-use-only CC license to emphasize that this has been a personal experiment).

But first, in just seven (7) weeks, I’ll be at CppCon to give a talk that I plan to be in three equal parts:

  1. C++26 progress, and why C++26 is shaping up to be the most important C++ release since C++11.
  2. C++ memory safety problems and solutions (update of my ACCU talk).
  3. Cppfront update.

I look forward to seeing many of you there!