From 84f1841addedea8bd0132ba76c6ba4a37d0a8455 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sat, 10 Feb 2024 15:55:47 -1000 Subject: [PATCH 01/64] Initial files to test MkDocs with Material --- .github/workflows/docs.yml | 21 ++++ .gitignore | 1 + docs/docs/index.md | 216 +++++++++++++++++++++++++++++++++++++ docs/index.md | 11 +- docs/mkdocs.yml | 48 +++++++++ 5 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/docs.yml create mode 100644 docs/docs/index.md create mode 100644 docs/mkdocs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000000..2e86b61884 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,21 @@ +name: docs +on: + push: + branches: + - mkdocs +permissions: + contents: write +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 + with: + python-version: 3.x + - uses: actions/cache@v2 + with: + key: ${{ github.ref }} + path: .cache + - run: pip install mkdocs-material + - run: mkdocs gh-deploy --force \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0616ce2a6d..7344eca1f6 100644 --- a/.gitignore +++ b/.gitignore @@ -28,6 +28,7 @@ *.exe source/gen_version.bat build*/ +/docs/venv* *.ifc # Visual Studio cache directory diff --git a/docs/docs/index.md b/docs/docs/index.md new file mode 100644 index 0000000000..03d0bda8bd --- /dev/null +++ b/docs/docs/index.md @@ -0,0 +1,216 @@ + +# Overview & getting started + +## What are Cpp2 and cppfront? + +### What is Cpp2? + +"Cpp2" is shorthand name for an experimental alternate "syntax 2" for C++ itself. The goal is to help us evolve C++ to make it simpler and safer, while maintaining full source and link compatibility — but to only pay for perfect backward source compatibility when we actually use it. + +Having an unambiguous alternative "syntax 2" gives us: + +- **Full freedom to evolve, without breaking any of today's code.** All code written in a distinct syntax is automatically a "bubble of new code that doesn't exist today," so we can make it mean whatever we think is best and know we aren't breaking any of today's existing code. For example, this approach lets us change language defaults, which would be impossible to do directly in an existing syntax. + +- **Power to make C++ simpler and safer, by making today's existing best practices the default.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default. For example, writing unsafe type casts is just not possible in Cpp2 syntax; the code won't compile. A programmer can always "break the glass" when needed to violate the guidance, but has to opt out explicitly by writing "unsafe," so if the program has a bug you can grep for those places to look at first. + +- **Perfect source compatibility always available, and you pay for it only if you use it.** You can write a "mixed" source files that has both Cpp2 and Cpp1 code, and get full backward C++ source compatibility. Or you can write a "pure" Cpp2 source file to use just the simpler syntax standalone, and get to write in a 10x simpler C++. This applies C++'s zero-overhead principle also to backward source compatibility: 100% of today's C++ is always available, but you pay for it only if you use it. + +- **Perfect link interoperability always, it's always still pure C++.** Any type/function/object written in either syntax is always still just a normal C++ type/function/object. Cpp2 code can invoke any existing C++ code or library (and vice versa) directly, with no wrapping/marshaling/thunking. There is no 'C++ compatibility bridge'... Cpp2 code *is* C++ code, just written more conveniently. + +Bjarne Stroustrup said it best: + +> "Inside C++, there is a much smaller and cleaner language struggling to get out."
— Bjarne Stroustrup, _The Design and Evolution of C++_ (D&E), 1994 +> +> "Say 10% of the size of C++ in definition and similar in front-end compiler size. ... most of the simplification would come from generalization."
— Bjarne Stroustrup, _ACM History of Programming Languages III_, 2007 + +My goal for this project is to try to prove that Bjarne Stroustrup has long been right: That it's possible and desirable to have true C++ with all its expressive power and control and with full backward compatibility, but in a C++ that's **10x simpler** with fewer warts and special cases, and **50x safer** where it's far easier to not write security bugs by accident. + + +### What is cppfront? + +[**Cppfront**](https://github.com/hsutter/cppfront) is a compiler that compiles Cpp2 syntax to today's C++ syntax (aka Cpp1). This lets you start trying out Cpp2 syntax in any existing C++ project just by [adding a build step](#adding-cppfront-in-your-ide-build-system) to translate the Cpp2 to Cpp1 syntax, and the result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). + +This deliberately follows Bjarne Stroustrup's wise approach with [**cfront**](https://en.wikipedia.org/wiki/Cfront), the original C++ compiler: In the 1980s and 1990s, Stroustrup created cfront to translate C++ to pure C, Stroustrup similarly ensured that C++ could be interleaved with C in the same source file, and C++ could always call any C code with no wrapping/marshaling/thunking. By providing a C++ compiler that emitted pure C, Stroustrup ensured full compatibility with the C ecosystems that already existed, and made it easy for people to start trying out C++ code in any existing C project by adding just another build step to translate the C++ to C first, and the result Just Worked with existing C tools. + +### How do I get and build cppfront? + +The full source code for cppfront is at the [**Cppfront repo**](https://github.com/hsutter/cppfront). + +Cppfront builds with any major C++20 compiler. Go to the `/cppfront/source` directory, and run one of the following: + + + +``` bash title="MSVC build instructions" +cl cppfront.cpp -std:c++20 -EHsc +``` + +``` bash title="GCC build instructions" +g++-10 cppfront.cpp -std=c++20 -o cppfront +``` + +``` bash title="Clang build instructions" +clang++-12 cppfront.cpp -std=c++20 -o cppfront +``` + +That's it! + + +## **Hello, world!** + +### A `hello.cpp2` program + +Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program... even without any `#include`s: + +``` cpp title="hello.cpp2, on one line" +main: () = std::cout << "Hello, world!\n"; +``` + +But let's add a little more, just to show a few things: + +``` cpp title="hello.cpp2, slightly more interesting" +main: () = { + words: std::vector = ( "Abigail", "Benjamin" ); + hello( words[0] ); + hello( words[1] ); + std::cout << "... and goodnight\n"; +} + +hello: (msg: std::string_view) = + std::cout << "Hello, (msg)$!\n"; +``` + +This short program code already illustrates a few Cpp2 essentials. + +**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. It's a main goal to avoid special cases, and avoid features that only work in certain parts of the language. All Cpp2 declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a."** + +- `main` **is a** function that takes no arguments and returns nothing, and has the body shown. +- `words` **is a** `std::vector`, whose initial contents are `"Abigail"` and `"Benjamin"`. +- `hello` **is a** function that takes a `std::string_view` that it will only read from, that returns nothing, and has a body that prints the string to `cout` the usual C++ way. + +All grammar is context-free. In particular, we (the human reading the code, or the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. + +**Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. The above `hello` function uses string interpolation to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. In `main`, the declaration of `words` takes advantage of C++'s normal constructor template argument deduction (aka CTAD). Also, the calls to `words[0]` and `words[1]` are automatically bounds-checked by default — `vector` subscript accesses from Cpp2 code are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. + +**Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: + - We can omit writing the `-> void` return type for a function that doesn't return anything, as both of these functions do. + - We can omit the `{` `}` around single-statement function bodies, as `hello` does. + - We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), `inout` for read-write, and also `copy`, `out`, `move`, and `forward`. + +**Order-independent by default.** Did you notice that `main` called `hello`, which was defined later? Cpp2 code is order-independent by default — there are no forward declarations. + +**Seamless compatibility and interop.** We can just use `std::cout` and `std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using direct calls without any wrapping/marshaling/thunking. + +**C++ standard library always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`). Cppfront is regularly updated to be compatible with C++23 and draft C++26 library additions, so as soon as you have a C++ implementation that has the new library feature, you'll be able to fully use it in Cpp2 code. + + +### Building `hello.cpp2` from the command line + +Now use `cppfront` to compile `hello.cpp2` to a standard C++ file `hello.cpp`: + +``` bash title="Call cppfront to produce hello.cpp" +cppfront hello.cpp2 -p +``` + +The result is an ordinary C++ file that looks like this: [^clean-cpp1] + +``` cpp title="hello.cpp" +#define CPP2_IMPORT_STD Yes + +#include "cpp2util.h" + + +auto main() -> int; + +auto hello(cpp2::in msg) -> void; +auto main() -> int{ + std::vector words {"Abigail", "Benjamin"}; + hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(words, 0)); + hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(std::move(words), 1)); + std::cout << "... and goodnight\n"; +} + +auto hello(cpp2::in msg) -> void { + std::cout << ("Hello, " + cpp2::to_string(msg) + "!\n"); } +``` + +Here we can see more of how Cpp2 makes it features work. + +**How: Consistent context-free syntax.** Cpp2's context-free syntax converts directly to today's Cpp1 syntax. All the types/functions/objects are ordinary C++ types/functions/objects, but can be written and read without wrestling with context and ambiguity. + +**How: Simple and safe by default.** Cpp2's contracts, `inspect`, string interpolation, and more are lowered to regular C++ code. Here, the body of `hello` shows how string interpolation is implemented to perform the string capture of `msg`'s current value. In `main`, we see why CTAD just works — because it turns into ordinary C++ CTAD-aware code. And we can see that cppfront injected calls to bounds-check the accesses of `words[0]` and `words[1]`, which it does by ensuring the index is betweeen `0` and `std::size(words)` — and, again, this works out of the box with your favorite standard library (or `std::ssize`-aware custom in-house container) you already have, when you use it from safe Cpp2 code. + +**How: Simplicity through generality + defaults.** The default `in` parameter passing convention is implemented using `cpp2::in<>`, which is smart enough to pass by `const` value or by `const&` so you don't have to do it by hand as today. + +**How: Order-independent by default.** The way cppfront achieves order independence is by generating all the type and function forward declarations, so that you don't have to. (Maybe you're already doing that by hand, which is a good but laborious workaround today. In Cpp2, this is automated so that the programmer doesn't have to.) So there's really no magic about how `main` can call `hello`: it's forward-declared. + +**How: Seamless compatibility and interop.** When you see the bodies of `main` and `hello`, it's clear why the uses of `std::cout` and `std::operator<<` and `std::string_view` are all direct without any wrapping/marshaling/thunking: It's just ordinary C++ code, as if you'd written it yourself by hand. Any type/function/object written in either syntax is always still just an ordinary C++ type/function/object. Cpp2 code can invoke any existing C++ code or library (and vice versa) directly, with no wrapping/marshaling/thunking. There is no 'C++ compatibility bridge'... Cpp2 code *is* C++ code, just written more conveniently. + +**How: C++ standard library always available.** Because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet), the generated code tells `cpp2util.h` to `import` the entire standard library as a module (or do the equivalent via headers). Cpp2 is a modules-first design, fully compatible with today's header files but attemping to "skate where the puck is going" by having first-class support for standard library modules. + +### Building and running `hello.cpp` from the command line + +Finally, just build `hello.cpp` using your favorite C++20 compiler, where `CPPFRONT_INCLUDE` is the path to `/cppfront/include`: + + + +``` title="MSVC" +> cl hello.cpp -std:c++20 -EHsc -I CPPFRONT_INCLUDE +> hello.exe +Hello, world! +``` + +``` bash title="GCC" +$ g++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello +$ ./hello.exe +Hello, world! +``` + +``` bash title="Clang" +$ clang++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello +$ ./hello.exe +Hello, world! +``` + +## Adding cppfront in your IDE / build system + +To start trying out Cpp2 syntax in any existing C++ project, just add a build step to translate the Cpp2 to Cpp1 syntax: + +- Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode. +- Tell the IDE to build that file using a custom build tool to invoke cppfront. + +That's it... The result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). The IDE build should just pick up the `.cpp2` file source locations for any error messages, and the debugger should just step through the `.cpp2` file. + +The following uses Visual Studio as an example, but others have done the same in Xcode, Qt Creator, CMake, and other IDEs. + +#### 1. Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode + +For Visual Studio: In the Solution Explorer, right-click on Source Files and pick Add to add the file to the project. + +

+ +Also in Solution Explorer, right-click on the `.cpp` file Properties and make sure it's in C++20 (or C++latest) mode. + +

+ + +#### 2. Tell the project system to build that file using a custom build tool to invoke cppfront, and add `cppfront/include` to the include path + +For Visual Studio: In Solution Explorer, right-click on the `.cpp2` file and select Properties, and add the custom build tool. Remember to also tell it that the custom build tool produces the `.cpp` file, so that it knows about the build dependency: + +

+ +Finally, put the `/cppfront/include` directory on your `INCLUDE` path. In Solution Explorer, right-click the app and select Properties, and add it to the VC++ Directories > Include Directories: + +

+ +#### That's it: Error message outputs, debuggers, visualizers, and other tools should just work + +That's enough to enable builds, and the IDE just picks up the rest from the `.cpp` file that cppfront generated: + +- **The cppfront error messages in `filename(line, col)` format.** Most C++ IDEs recognize these, and usually automatically merge any diagnostic output wherever compiler error output normally appears. If your IDE prefers `filename:line:col`, just use the cppfront `-format-colon-errors` command line option. +- **The `#line` directives cppfront emits in the generated `.cpp` file.** Most C++ debuggers recognize these and will know to step through the `.cpp2` file. Note that `#line` emission is on by default, but if you choose `-c` (short for `-clean-cpp1`) these will be suppressed and then the debugger will step through the generated C++ code instead. +- **Regardless of syntax, every type/function/object is still just an ordinary C++ type/function/object.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. + + + +[^clean-cpp1]: For presentation purposes, this documentation generally shows the Cpp1 code generated when using cppfront's `-c` (short for `-clean-cpp1`), which eliminates extra output including `#line` directives. In normal use, you won't need `-c`, because you want the `#line` information which enables accurate error messages, debuggers, and other tools. diff --git a/docs/index.md b/docs/index.md index 398beded14..7365265098 100644 --- a/docs/index.md +++ b/docs/index.md @@ -59,18 +59,17 @@ cppfront hello.cpp2 -p # produces hello.cpp and then build `hello.cpp` using your favorite C++20 compiler, where `CPPFRONT_INCLUDE` is the path to `/cppfront/include`: -``` -# --- MSVC ----------------------------------------------- +``` title="MSVC" > cl hello.cpp -std:c++20 -EHsc -I CPPFRONT_INCLUDE > hello.exe Hello, world! - -# --- GCC ------------------------------------------------ +``` +``` title="GCC" $ g++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello $ ./hello.exe Hello, world! - -# --- Clang ---------------------------------------------- +``` +``` title="Clang" $ clang++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello $ ./hello.exe Hello, world! diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml new file mode 100644 index 0000000000..37a451069e --- /dev/null +++ b/docs/mkdocs.yml @@ -0,0 +1,48 @@ +site_name: "Cpp2 and cppfront — An experimental 'C++ syntax 2', and its first compiler" +theme: + name: material + features: + - navigation.tabs + - navigation.sections + - toc.integrate + - navigation.top + - search.suggest + - search.highlight + - content.tabs.link + - content.code.annotation + - content.code.copy + - content.footnote.tooltips + language: en + palette: + - scheme: default + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + primary: teal + accent: purple + - scheme: slate + toggle: + icon: material/toggle-switch + name: switch to light mode + primary: teal + accent: lime + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - admonition + - pymdownx.arithmatex: + generic: true + - footnotes + - pymdownx.details + - pymdownx.superfences + - pymdownx.mark + - attr_list + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + +copyright: | + © Herb Sutter \ No newline at end of file From 3126adb808c3dc471b31afce76d65d2cc5330218 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sat, 10 Feb 2024 16:06:36 -1000 Subject: [PATCH 02/64] Update checkout version --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2e86b61884..b7fb6ba402 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,7 +9,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@4 - uses: actions/setup-python@v4 with: python-version: 3.x From f6812accc245cacde45df86f77c185e372758127 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sat, 10 Feb 2024 16:07:26 -1000 Subject: [PATCH 03/64] Trying checkout with no version --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index b7fb6ba402..852402cf6e 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,7 +9,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@4 + - uses: actions/checkout - uses: actions/setup-python@v4 with: python-version: 3.x From 53a3279e059eec3d9a845eb83b9ae16837f86de5 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sat, 10 Feb 2024 16:08:24 -1000 Subject: [PATCH 04/64] Back to v3 --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 852402cf6e..2e86b61884 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -9,7 +9,7 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout + - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.x From a708e37abc947c4cfbcf343e39138510886bd40a Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sat, 10 Feb 2024 16:11:40 -1000 Subject: [PATCH 05/64] Try renaming to docs --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 2e86b61884..6dc93f06d9 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -2,7 +2,7 @@ name: docs on: push: branches: - - mkdocs + - docs permissions: contents: write jobs: From 9fcc9220dab9fd351404ba5a61ffd0648fcabd76 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sat, 10 Feb 2024 16:20:25 -1000 Subject: [PATCH 06/64] Test push to renamed docs branch --- docs/docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/index.md b/docs/docs/index.md index 03d0bda8bd..cf55a919d1 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -213,4 +213,4 @@ That's enough to enable builds, and the IDE just picks up the rest from the `.cp -[^clean-cpp1]: For presentation purposes, this documentation generally shows the Cpp1 code generated when using cppfront's `-c` (short for `-clean-cpp1`), which eliminates extra output including `#line` directives. In normal use, you won't need `-c`, because you want the `#line` information which enables accurate error messages, debuggers, and other tools. +[^clean-cpp1]: For presentation purposes, this documentation generally shows the Cpp1 code generated when using cppfront's `-c` (short for `-clean-cpp1`), which eliminates extra output including `#line` directives. In normal use, you won't need `-c`, because you want the `#line` information which enables accurate error messages, debuggers, and other tools. From 4eaa3062076b703864afd7315d56a87ac4424b69 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sun, 11 Feb 2024 07:36:17 -1000 Subject: [PATCH 07/64] Try moving mkdocs.yml to root to unblock CI --- mkdocs.yml | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 mkdocs.yml diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000000..37a451069e --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,48 @@ +site_name: "Cpp2 and cppfront — An experimental 'C++ syntax 2', and its first compiler" +theme: + name: material + features: + - navigation.tabs + - navigation.sections + - toc.integrate + - navigation.top + - search.suggest + - search.highlight + - content.tabs.link + - content.code.annotation + - content.code.copy + - content.footnote.tooltips + language: en + palette: + - scheme: default + toggle: + icon: material/toggle-switch-off-outline + name: Switch to dark mode + primary: teal + accent: purple + - scheme: slate + toggle: + icon: material/toggle-switch + name: switch to light mode + primary: teal + accent: lime + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + - pymdownx.inlinehilite + - pymdownx.snippets + - admonition + - pymdownx.arithmatex: + generic: true + - footnotes + - pymdownx.details + - pymdownx.superfences + - pymdownx.mark + - attr_list + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg + +copyright: | + © Herb Sutter \ No newline at end of file From e3d71c7277c2043b87363bdd43219760cf1b2855 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sun, 11 Feb 2024 08:16:19 -1000 Subject: [PATCH 08/64] Further cleanup to get things in the right directories --- docs/{mkdocs.yml => XXXmkdocs.yml} | 0 docs/index.md | 197 ++++++++++++++++++++++++----- mkdocs.yml | 3 - 3 files changed, 168 insertions(+), 32 deletions(-) rename docs/{mkdocs.yml => XXXmkdocs.yml} (100%) diff --git a/docs/mkdocs.yml b/docs/XXXmkdocs.yml similarity index 100% rename from docs/mkdocs.yml rename to docs/XXXmkdocs.yml diff --git a/docs/index.md b/docs/index.md index 7365265098..8efb1de319 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,77 +1,216 @@ -# A tour of Cpp2 ('C++ alt syntax 2') and the `cppfront` compiler +# Overview & getting started -## Preface: What is this? +## What are Cpp2 and cppfront? -### Goal in a nutshell: 100% pure C++... just nicer +### What is Cpp2? -My goal for this project is to try to prove that Bjarne Stroustrup has long been right: That it's possible and desirable to have true C++ with all its expressive power and control and with full backward compatibility, but in a C++ that's **10x simpler** with fewer warts and special cases, and **50x safer** where it's far easier to not write vulnerability bugs by accident. +"Cpp2" is shorthand name for an experimental alternate "syntax 2" for C++ itself. The goal is to help us evolve C++ to make it simpler and safer, while maintaining full source and link compatibility — but to only pay for perfect backward source compatibility when we actually use it. -Stroustrup said it best: +Having an unambiguous alternative "syntax 2" gives us: + +- **Full freedom to evolve, without breaking any of today's code.** All code written in a distinct syntax is automatically a "bubble of new code that doesn't exist today," so we can make it mean whatever we think is best and know we aren't breaking any of today's existing code. For example, this approach lets us change language defaults, which would be impossible to do directly in an existing syntax. + +- **Power to make C++ simpler and safer, by making today's existing best practices the default.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default. For example, writing unsafe type casts is just not possible in Cpp2 syntax; the code won't compile. A programmer can always "break the glass" when needed to violate the guidance, but has to opt out explicitly by writing "unsafe," so if the program has a bug you can grep for those places to look at first. + +- **Perfect source compatibility always available, and you pay for it only if you use it.** You can write a "mixed" source files that has both Cpp2 and Cpp1 code, and get full backward C++ source compatibility. Or you can write a "pure" Cpp2 source file to use just the simpler syntax standalone, and get to write in a 10x simpler C++. This applies C++'s zero-overhead principle also to backward source compatibility: 100% of today's C++ is always available, but you pay for it only if you use it. + +- **Perfect link interoperability always, it's always still pure C++.** Any type/function/object written in either syntax is always still just a normal C++ type/function/object. Cpp2 code can invoke any existing C++ code or library (and vice versa) directly, with no wrapping/marshaling/thunking. There is no 'C++ compatibility bridge'... Cpp2 code *is* C++ code, just written more conveniently. + +Bjarne Stroustrup said it best: > "Inside C++, there is a much smaller and cleaner language struggling to get out."
— Bjarne Stroustrup, _The Design and Evolution of C++_ (D&E), 1994 > > "Say 10% of the size of C++ in definition and similar in front-end compiler size. ... most of the simplification would come from generalization."
— Bjarne Stroustrup, _ACM History of Programming Languages III_, 2007 -But how? +My goal for this project is to try to prove that Bjarne Stroustrup has long been right: That it's possible and desirable to have true C++ with all its expressive power and control and with full backward compatibility, but in a C++ that's **10x simpler** with fewer warts and special cases, and **50x safer** where it's far easier to not write security bugs by accident. -### Approach in a nutshell: Alternative syntax + perfect compatibility -This project explores creating an alternate "syntax 2" (Cpp2 for short) for C++ itself, that's unambiguous with today's syntax (Cpp1 for short). That gives us: +### What is cppfront? -- a "bubble of new code" that doesn't exist today, where we can make any change we want in a fully compatible way, without worrying about breaking existing code; -- a way to make any improvement, including to fix language defaults and make all the C++ best-practices guidance we already teach be the default; -- the power to freely use both syntaxes in the same file if we want full backward C++ source compatibility, or to freely use just the simpler syntax standalone if we want to write in a 10x simpler C++ (i.e., pay for source compatibility only if you use it); and -- perfect interoperability, because any type/function/object written in either Cpp1 or Cpp2 syntax is always still just a normal C++ type/function/object. +[**Cppfront**](https://github.com/hsutter/cppfront) is a compiler that compiles Cpp2 syntax to today's C++ syntax (aka Cpp1). This lets you start trying out Cpp2 syntax in any existing C++ project just by [adding a build step](#adding-cppfront-in-your-ide-build-system) to translate the Cpp2 to Cpp1 syntax, and the result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). -In the 1980s and 1990s, Stroustrup similarly ensured that C++ could be interleaved with C in the same source file, and C++ could always call any C code with no wrapping/marshaling/thunking. Stroustrup accomplished this and more by writing **cfront**, the original C++ compiler, to translate C++ to pure C. That way, people could start trying out C++ code in any existing C project with just another build step to translate the C++ to C, and the result Just Worked with existing C tools. +This deliberately follows Bjarne Stroustrup's wise approach with [**cfront**](https://en.wikipedia.org/wiki/Cfront), the original C++ compiler: In the 1980s and 1990s, Stroustrup created cfront to translate C++ to pure C, Stroustrup similarly ensured that C++ could be interleaved with C in the same source file, and C++ could always call any C code with no wrapping/marshaling/thunking. By providing a C++ compiler that emitted pure C, Stroustrup ensured full compatibility with the C ecosystems that already existed, and made it easy for people to start trying out C++ code in any existing C project by adding just another build step to translate the C++ to C first, and the result Just Worked with existing C tools. -This project aims to follow Stroustrup's implementation approach, with a **cppfront** compiler that compiles Cpp2 syntax to Cpp1 syntax. You can start trying out Cpp2 syntax in any existing C++ project just by adding a build step to translate the Cpp2 to Cpp1 syntax, and the result Just Works with existing C++ tools. +### How do I get and build cppfront? -What does it look like? +The full source code for cppfront is at the [**Cppfront repo**](https://github.com/hsutter/cppfront). -## Hello, world! +Cppfront builds with any major C++20 compiler. Go to the `/cppfront/source` directory, and run one of the following: -Here is the usual starter program that prints "Hello, world!": + + +``` bash title="MSVC build instructions" +cl cppfront.cpp -std:c++20 -EHsc +``` + +``` bash title="GCC build instructions" +g++-10 cppfront.cpp -std=c++20 -o cppfront +``` -```cpp -// hello.cpp2 +``` bash title="Clang build instructions" +clang++-12 cppfront.cpp -std=c++20 -o cppfront +``` + +That's it! + + +## **Hello, world!** + +### A `hello.cpp2` program + +Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program... even without any `#include`s: + +``` cpp title="hello.cpp2, on one line" +main: () = std::cout << "Hello, world!\n"; +``` + +But let's add a little more, just to show a few things: + +``` cpp title="hello.cpp2, slightly more interesting" main: () = { - std::cout << "Hello, world!"; + words: std::vector = ( "Abigail", "Benjamin" ); + hello( words[0] ); + hello( words[1] ); + std::cout << "... and goodnight\n"; } + +hello: (msg: std::string_view) = + std::cout << "Hello, (msg)$!\n"; ``` -This is a complete program that prints `Hello, world!`. +This short program code already illustrates a few Cpp2 essentials. + +**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. It's a main goal to avoid special cases, and avoid features that only work in certain parts of the language. All Cpp2 declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a."** + +- `main` **is a** function that takes no arguments and returns nothing, and has the body shown. +- `words` **is a** `std::vector`, whose initial contents are `"Abigail"` and `"Benjamin"`. +- `hello` **is a** function that takes a `std::string_view` that it will only read from, that returns nothing, and has a body that prints the string to `cout` the usual C++ way. + +All grammar is context-free. In particular, we (the human reading the code, or the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. + +**Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. The above `hello` function uses string interpolation to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. In `main`, the declaration of `words` takes advantage of C++'s normal constructor template argument deduction (aka CTAD). Also, the calls to `words[0]` and `words[1]` are automatically bounds-checked by default — `vector` subscript accesses from Cpp2 code are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. + +**Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: + - We can omit writing the `-> void` return type for a function that doesn't return anything, as both of these functions do. + - We can omit the `{` `}` around single-statement function bodies, as `hello` does. + - We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), `inout` for read-write, and also `copy`, `out`, `move`, and `forward`. -Everything in Cpp2 is declared using the syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced "is a." Here, `main` is a function that takes no arguments, and has a body that prints the string to `cout`. +**Order-independent by default.** Did you notice that `main` called `hello`, which was defined later? Cpp2 code is order-independent by default — there are no forward declarations. -We can just use `std::cout` and `std::operator<<` directly. Cpp2 code works with any C++ code or library, using direct calls without any wrapping/marshaling/thunking. +**Seamless compatibility and interop.** We can just use `std::cout` and `std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using direct calls without any wrapping/marshaling/thunking. -We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile with it `cppfront -p` (short for `-pure-cpp2`). +**C++ standard library always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`). Cppfront is regularly updated to be compatible with C++23 and draft C++26 library additions, so as soon as you have a C++ implementation that has the new library feature, you'll be able to fully use it in Cpp2 code. -### Building and running the program + +### Building `hello.cpp2` from the command line Now use `cppfront` to compile `hello.cpp2` to a standard C++ file `hello.cpp`: +``` bash title="Call cppfront to produce hello.cpp" +cppfront hello.cpp2 -p ``` -cppfront hello.cpp2 -p # produces hello.cpp + +The result is an ordinary C++ file that looks like this: [^clean-cpp1] + +``` cpp title="hello.cpp" +#define CPP2_IMPORT_STD Yes + +#include "cpp2util.h" + + +auto main() -> int; + +auto hello(cpp2::in msg) -> void; +auto main() -> int{ + std::vector words {"Abigail", "Benjamin"}; + hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(words, 0)); + hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(std::move(words), 1)); + std::cout << "... and goodnight\n"; +} + +auto hello(cpp2::in msg) -> void { + std::cout << ("Hello, " + cpp2::to_string(msg) + "!\n"); } ``` -and then build `hello.cpp` using your favorite C++20 compiler, where `CPPFRONT_INCLUDE` is the path to `/cppfront/include`: +Here we can see more of how Cpp2 makes it features work. + +**How: Consistent context-free syntax.** Cpp2's context-free syntax converts directly to today's Cpp1 syntax. All the types/functions/objects are ordinary C++ types/functions/objects, but can be written and read without wrestling with context and ambiguity. + +**How: Simple and safe by default.** Cpp2's contracts, `inspect`, string interpolation, and more are lowered to regular C++ code. Here, the body of `hello` shows how string interpolation is implemented to perform the string capture of `msg`'s current value. In `main`, we see why CTAD just works — because it turns into ordinary C++ CTAD-aware code. And we can see that cppfront injected calls to bounds-check the accesses of `words[0]` and `words[1]`, which it does by ensuring the index is betweeen `0` and `std::size(words)` — and, again, this works out of the box with your favorite standard library (or `std::ssize`-aware custom in-house container) you already have, when you use it from safe Cpp2 code. + +**How: Simplicity through generality + defaults.** The default `in` parameter passing convention is implemented using `cpp2::in<>`, which is smart enough to pass by `const` value or by `const&` so you don't have to do it by hand as today. + +**How: Order-independent by default.** The way cppfront achieves order independence is by generating all the type and function forward declarations, so that you don't have to. (Maybe you're already doing that by hand, which is a good but laborious workaround today. In Cpp2, this is automated so that the programmer doesn't have to.) So there's really no magic about how `main` can call `hello`: it's forward-declared. + +**How: Seamless compatibility and interop.** When you see the bodies of `main` and `hello`, it's clear why the uses of `std::cout` and `std::operator<<` and `std::string_view` are all direct without any wrapping/marshaling/thunking: It's just ordinary C++ code, as if you'd written it yourself by hand. Any type/function/object written in either syntax is always still just an ordinary C++ type/function/object. Cpp2 code can invoke any existing C++ code or library (and vice versa) directly, with no wrapping/marshaling/thunking. There is no 'C++ compatibility bridge'... Cpp2 code *is* C++ code, just written more conveniently. + +**How: C++ standard library always available.** Because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet), the generated code tells `cpp2util.h` to `import` the entire standard library as a module (or do the equivalent via headers). Cpp2 is a modules-first design, fully compatible with today's header files but attemping to "skate where the puck is going" by having first-class support for standard library modules. + +### Building and running `hello.cpp` from the command line + +Finally, just build `hello.cpp` using your favorite C++20 compiler, where `CPPFRONT_INCLUDE` is the path to `/cppfront/include`: + + ``` title="MSVC" > cl hello.cpp -std:c++20 -EHsc -I CPPFRONT_INCLUDE > hello.exe Hello, world! ``` -``` title="GCC" + +``` bash title="GCC" $ g++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello $ ./hello.exe Hello, world! ``` -``` title="Clang" + +``` bash title="Clang" $ clang++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello $ ./hello.exe Hello, world! ``` +## Adding cppfront in your IDE / build system + +To start trying out Cpp2 syntax in any existing C++ project, just add a build step to translate the Cpp2 to Cpp1 syntax: + +- Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode. +- Tell the IDE to build that file using a custom build tool to invoke cppfront. + +That's it... The result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). The IDE build should just pick up the `.cpp2` file source locations for any error messages, and the debugger should just step through the `.cpp2` file. + +The following uses Visual Studio as an example, but others have done the same in Xcode, Qt Creator, CMake, and other IDEs. + +#### 1. Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode + +For Visual Studio: In the Solution Explorer, right-click on Source Files and pick Add to add the file to the project. + +

+ +Also in Solution Explorer, right-click on the `.cpp` file Properties and make sure it's in C++20 (or C++latest) mode. + +

+ + +#### 2. Tell the project system to build that file using a custom build tool to invoke cppfront, and add `cppfront/include` to the include path + +For Visual Studio: In Solution Explorer, right-click on the `.cpp2` file and select Properties, and add the custom build tool. Remember to also tell it that the custom build tool produces the `.cpp` file, so that it knows about the build dependency: + +

+ +Finally, put the `/cppfront/include` directory on your `INCLUDE` path. In Solution Explorer, right-click the app and select Properties, and add it to the VC++ Directories > Include Directories: + +

+ +#### That's it: Error message outputs, debuggers, visualizers, and other tools should just work + +That's enough to enable builds, and the IDE just picks up the rest from the `.cpp` file that cppfront generated: + +- **The cppfront error messages in `filename(line, col)` format.** Most C++ IDEs recognize these, and usually automatically merge any diagnostic output wherever compiler error output normally appears. If your IDE prefers `filename:line:col`, just use the cppfront `-format-colon-errors` command line option. +- **The `#line` directives cppfront emits in the generated `.cpp` file.** Most C++ debuggers recognize these and will know to step through the `.cpp2` file. Note that `#line` emission is on by default, but if you choose `-c` (short for `-clean-cpp1`) these will be suppressed and then the debugger will step through the generated C++ code instead. +- **Regardless of syntax, every type/function/object is still just an ordinary C++ type/function/object.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. + + + +[^clean-cpp1]: For presentation purposes, this documentation generally shows the Cpp1 code generated when using cppfront's `-c` (short for `-clean-cpp1`), which opts out of extra output in the `.cpp` file to work well out-of-the-box with C++ tools (e.g., enable IDEs to integrate cppfront error message output, debuggers to step to the right lines in Cpp2 source code, and other tool light-up). In normal use, you won't need `-c`. diff --git a/mkdocs.yml b/mkdocs.yml index 37a451069e..e5d7a30a48 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,9 +40,6 @@ markdown_extensions: - pymdownx.superfences - pymdownx.mark - attr_list - - pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg copyright: | © Herb Sutter \ No newline at end of file From 5bc176e5327abd70105e494053e15658ae781815 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sun, 11 Feb 2024 13:23:19 -1000 Subject: [PATCH 09/64] Finish moving from `/docs/docs` to `/docs` Added local hosting instructions in the YAML file This renders beautifully locally, but for some reason it doesn't render properly on GitHub Pages yet. One possible reason is the `pages-build-deployment` workflow that GitHub Pages created on my initial attempt to use Pages (before trying MkDocs) is still running (and I can't figure out how to get rid of it) and it appears to be interfering with the `docs` workflow I created for MkDocs If someone has ideas on how to debug this, please open an issue or email me - thx! --- .gitignore | 2 +- docs/XXXmkdocs.yml | 48 ---------- docs/docs/index.md | 216 --------------------------------------------- docs/index.md | 65 ++++++++------ mkdocs.yml | 19 +++- 5 files changed, 59 insertions(+), 291 deletions(-) delete mode 100644 docs/XXXmkdocs.yml delete mode 100644 docs/docs/index.md diff --git a/.gitignore b/.gitignore index 7344eca1f6..f6f69d8cb9 100644 --- a/.gitignore +++ b/.gitignore @@ -28,7 +28,7 @@ *.exe source/gen_version.bat build*/ -/docs/venv* +/venv* *.ifc # Visual Studio cache directory diff --git a/docs/XXXmkdocs.yml b/docs/XXXmkdocs.yml deleted file mode 100644 index 37a451069e..0000000000 --- a/docs/XXXmkdocs.yml +++ /dev/null @@ -1,48 +0,0 @@ -site_name: "Cpp2 and cppfront — An experimental 'C++ syntax 2', and its first compiler" -theme: - name: material - features: - - navigation.tabs - - navigation.sections - - toc.integrate - - navigation.top - - search.suggest - - search.highlight - - content.tabs.link - - content.code.annotation - - content.code.copy - - content.footnote.tooltips - language: en - palette: - - scheme: default - toggle: - icon: material/toggle-switch-off-outline - name: Switch to dark mode - primary: teal - accent: purple - - scheme: slate - toggle: - icon: material/toggle-switch - name: switch to light mode - primary: teal - accent: lime - -markdown_extensions: - - pymdownx.highlight: - anchor_linenums: true - - pymdownx.inlinehilite - - pymdownx.snippets - - admonition - - pymdownx.arithmatex: - generic: true - - footnotes - - pymdownx.details - - pymdownx.superfences - - pymdownx.mark - - attr_list - - pymdownx.emoji: - emoji_index: !!python/name:materialx.emoji.twemoji - emoji_generator: !!python/name:materialx.emoji.to_svg - -copyright: | - © Herb Sutter \ No newline at end of file diff --git a/docs/docs/index.md b/docs/docs/index.md deleted file mode 100644 index cf55a919d1..0000000000 --- a/docs/docs/index.md +++ /dev/null @@ -1,216 +0,0 @@ - -# Overview & getting started - -## What are Cpp2 and cppfront? - -### What is Cpp2? - -"Cpp2" is shorthand name for an experimental alternate "syntax 2" for C++ itself. The goal is to help us evolve C++ to make it simpler and safer, while maintaining full source and link compatibility — but to only pay for perfect backward source compatibility when we actually use it. - -Having an unambiguous alternative "syntax 2" gives us: - -- **Full freedom to evolve, without breaking any of today's code.** All code written in a distinct syntax is automatically a "bubble of new code that doesn't exist today," so we can make it mean whatever we think is best and know we aren't breaking any of today's existing code. For example, this approach lets us change language defaults, which would be impossible to do directly in an existing syntax. - -- **Power to make C++ simpler and safer, by making today's existing best practices the default.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default. For example, writing unsafe type casts is just not possible in Cpp2 syntax; the code won't compile. A programmer can always "break the glass" when needed to violate the guidance, but has to opt out explicitly by writing "unsafe," so if the program has a bug you can grep for those places to look at first. - -- **Perfect source compatibility always available, and you pay for it only if you use it.** You can write a "mixed" source files that has both Cpp2 and Cpp1 code, and get full backward C++ source compatibility. Or you can write a "pure" Cpp2 source file to use just the simpler syntax standalone, and get to write in a 10x simpler C++. This applies C++'s zero-overhead principle also to backward source compatibility: 100% of today's C++ is always available, but you pay for it only if you use it. - -- **Perfect link interoperability always, it's always still pure C++.** Any type/function/object written in either syntax is always still just a normal C++ type/function/object. Cpp2 code can invoke any existing C++ code or library (and vice versa) directly, with no wrapping/marshaling/thunking. There is no 'C++ compatibility bridge'... Cpp2 code *is* C++ code, just written more conveniently. - -Bjarne Stroustrup said it best: - -> "Inside C++, there is a much smaller and cleaner language struggling to get out."
— Bjarne Stroustrup, _The Design and Evolution of C++_ (D&E), 1994 -> -> "Say 10% of the size of C++ in definition and similar in front-end compiler size. ... most of the simplification would come from generalization."
— Bjarne Stroustrup, _ACM History of Programming Languages III_, 2007 - -My goal for this project is to try to prove that Bjarne Stroustrup has long been right: That it's possible and desirable to have true C++ with all its expressive power and control and with full backward compatibility, but in a C++ that's **10x simpler** with fewer warts and special cases, and **50x safer** where it's far easier to not write security bugs by accident. - - -### What is cppfront? - -[**Cppfront**](https://github.com/hsutter/cppfront) is a compiler that compiles Cpp2 syntax to today's C++ syntax (aka Cpp1). This lets you start trying out Cpp2 syntax in any existing C++ project just by [adding a build step](#adding-cppfront-in-your-ide-build-system) to translate the Cpp2 to Cpp1 syntax, and the result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). - -This deliberately follows Bjarne Stroustrup's wise approach with [**cfront**](https://en.wikipedia.org/wiki/Cfront), the original C++ compiler: In the 1980s and 1990s, Stroustrup created cfront to translate C++ to pure C, Stroustrup similarly ensured that C++ could be interleaved with C in the same source file, and C++ could always call any C code with no wrapping/marshaling/thunking. By providing a C++ compiler that emitted pure C, Stroustrup ensured full compatibility with the C ecosystems that already existed, and made it easy for people to start trying out C++ code in any existing C project by adding just another build step to translate the C++ to C first, and the result Just Worked with existing C tools. - -### How do I get and build cppfront? - -The full source code for cppfront is at the [**Cppfront repo**](https://github.com/hsutter/cppfront). - -Cppfront builds with any major C++20 compiler. Go to the `/cppfront/source` directory, and run one of the following: - - - -``` bash title="MSVC build instructions" -cl cppfront.cpp -std:c++20 -EHsc -``` - -``` bash title="GCC build instructions" -g++-10 cppfront.cpp -std=c++20 -o cppfront -``` - -``` bash title="Clang build instructions" -clang++-12 cppfront.cpp -std=c++20 -o cppfront -``` - -That's it! - - -## **Hello, world!** - -### A `hello.cpp2` program - -Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program... even without any `#include`s: - -``` cpp title="hello.cpp2, on one line" -main: () = std::cout << "Hello, world!\n"; -``` - -But let's add a little more, just to show a few things: - -``` cpp title="hello.cpp2, slightly more interesting" -main: () = { - words: std::vector = ( "Abigail", "Benjamin" ); - hello( words[0] ); - hello( words[1] ); - std::cout << "... and goodnight\n"; -} - -hello: (msg: std::string_view) = - std::cout << "Hello, (msg)$!\n"; -``` - -This short program code already illustrates a few Cpp2 essentials. - -**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. It's a main goal to avoid special cases, and avoid features that only work in certain parts of the language. All Cpp2 declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a."** - -- `main` **is a** function that takes no arguments and returns nothing, and has the body shown. -- `words` **is a** `std::vector`, whose initial contents are `"Abigail"` and `"Benjamin"`. -- `hello` **is a** function that takes a `std::string_view` that it will only read from, that returns nothing, and has a body that prints the string to `cout` the usual C++ way. - -All grammar is context-free. In particular, we (the human reading the code, or the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. - -**Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. The above `hello` function uses string interpolation to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. In `main`, the declaration of `words` takes advantage of C++'s normal constructor template argument deduction (aka CTAD). Also, the calls to `words[0]` and `words[1]` are automatically bounds-checked by default — `vector` subscript accesses from Cpp2 code are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. - -**Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: - - We can omit writing the `-> void` return type for a function that doesn't return anything, as both of these functions do. - - We can omit the `{` `}` around single-statement function bodies, as `hello` does. - - We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), `inout` for read-write, and also `copy`, `out`, `move`, and `forward`. - -**Order-independent by default.** Did you notice that `main` called `hello`, which was defined later? Cpp2 code is order-independent by default — there are no forward declarations. - -**Seamless compatibility and interop.** We can just use `std::cout` and `std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using direct calls without any wrapping/marshaling/thunking. - -**C++ standard library always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`). Cppfront is regularly updated to be compatible with C++23 and draft C++26 library additions, so as soon as you have a C++ implementation that has the new library feature, you'll be able to fully use it in Cpp2 code. - - -### Building `hello.cpp2` from the command line - -Now use `cppfront` to compile `hello.cpp2` to a standard C++ file `hello.cpp`: - -``` bash title="Call cppfront to produce hello.cpp" -cppfront hello.cpp2 -p -``` - -The result is an ordinary C++ file that looks like this: [^clean-cpp1] - -``` cpp title="hello.cpp" -#define CPP2_IMPORT_STD Yes - -#include "cpp2util.h" - - -auto main() -> int; - -auto hello(cpp2::in msg) -> void; -auto main() -> int{ - std::vector words {"Abigail", "Benjamin"}; - hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(words, 0)); - hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(std::move(words), 1)); - std::cout << "... and goodnight\n"; -} - -auto hello(cpp2::in msg) -> void { - std::cout << ("Hello, " + cpp2::to_string(msg) + "!\n"); } -``` - -Here we can see more of how Cpp2 makes it features work. - -**How: Consistent context-free syntax.** Cpp2's context-free syntax converts directly to today's Cpp1 syntax. All the types/functions/objects are ordinary C++ types/functions/objects, but can be written and read without wrestling with context and ambiguity. - -**How: Simple and safe by default.** Cpp2's contracts, `inspect`, string interpolation, and more are lowered to regular C++ code. Here, the body of `hello` shows how string interpolation is implemented to perform the string capture of `msg`'s current value. In `main`, we see why CTAD just works — because it turns into ordinary C++ CTAD-aware code. And we can see that cppfront injected calls to bounds-check the accesses of `words[0]` and `words[1]`, which it does by ensuring the index is betweeen `0` and `std::size(words)` — and, again, this works out of the box with your favorite standard library (or `std::ssize`-aware custom in-house container) you already have, when you use it from safe Cpp2 code. - -**How: Simplicity through generality + defaults.** The default `in` parameter passing convention is implemented using `cpp2::in<>`, which is smart enough to pass by `const` value or by `const&` so you don't have to do it by hand as today. - -**How: Order-independent by default.** The way cppfront achieves order independence is by generating all the type and function forward declarations, so that you don't have to. (Maybe you're already doing that by hand, which is a good but laborious workaround today. In Cpp2, this is automated so that the programmer doesn't have to.) So there's really no magic about how `main` can call `hello`: it's forward-declared. - -**How: Seamless compatibility and interop.** When you see the bodies of `main` and `hello`, it's clear why the uses of `std::cout` and `std::operator<<` and `std::string_view` are all direct without any wrapping/marshaling/thunking: It's just ordinary C++ code, as if you'd written it yourself by hand. Any type/function/object written in either syntax is always still just an ordinary C++ type/function/object. Cpp2 code can invoke any existing C++ code or library (and vice versa) directly, with no wrapping/marshaling/thunking. There is no 'C++ compatibility bridge'... Cpp2 code *is* C++ code, just written more conveniently. - -**How: C++ standard library always available.** Because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet), the generated code tells `cpp2util.h` to `import` the entire standard library as a module (or do the equivalent via headers). Cpp2 is a modules-first design, fully compatible with today's header files but attemping to "skate where the puck is going" by having first-class support for standard library modules. - -### Building and running `hello.cpp` from the command line - -Finally, just build `hello.cpp` using your favorite C++20 compiler, where `CPPFRONT_INCLUDE` is the path to `/cppfront/include`: - - - -``` title="MSVC" -> cl hello.cpp -std:c++20 -EHsc -I CPPFRONT_INCLUDE -> hello.exe -Hello, world! -``` - -``` bash title="GCC" -$ g++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello -$ ./hello.exe -Hello, world! -``` - -``` bash title="Clang" -$ clang++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello -$ ./hello.exe -Hello, world! -``` - -## Adding cppfront in your IDE / build system - -To start trying out Cpp2 syntax in any existing C++ project, just add a build step to translate the Cpp2 to Cpp1 syntax: - -- Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode. -- Tell the IDE to build that file using a custom build tool to invoke cppfront. - -That's it... The result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). The IDE build should just pick up the `.cpp2` file source locations for any error messages, and the debugger should just step through the `.cpp2` file. - -The following uses Visual Studio as an example, but others have done the same in Xcode, Qt Creator, CMake, and other IDEs. - -#### 1. Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode - -For Visual Studio: In the Solution Explorer, right-click on Source Files and pick Add to add the file to the project. - -

- -Also in Solution Explorer, right-click on the `.cpp` file Properties and make sure it's in C++20 (or C++latest) mode. - -

- - -#### 2. Tell the project system to build that file using a custom build tool to invoke cppfront, and add `cppfront/include` to the include path - -For Visual Studio: In Solution Explorer, right-click on the `.cpp2` file and select Properties, and add the custom build tool. Remember to also tell it that the custom build tool produces the `.cpp` file, so that it knows about the build dependency: - -

- -Finally, put the `/cppfront/include` directory on your `INCLUDE` path. In Solution Explorer, right-click the app and select Properties, and add it to the VC++ Directories > Include Directories: - -

- -#### That's it: Error message outputs, debuggers, visualizers, and other tools should just work - -That's enough to enable builds, and the IDE just picks up the rest from the `.cpp` file that cppfront generated: - -- **The cppfront error messages in `filename(line, col)` format.** Most C++ IDEs recognize these, and usually automatically merge any diagnostic output wherever compiler error output normally appears. If your IDE prefers `filename:line:col`, just use the cppfront `-format-colon-errors` command line option. -- **The `#line` directives cppfront emits in the generated `.cpp` file.** Most C++ debuggers recognize these and will know to step through the `.cpp2` file. Note that `#line` emission is on by default, but if you choose `-c` (short for `-clean-cpp1`) these will be suppressed and then the debugger will step through the generated C++ code instead. -- **Regardless of syntax, every type/function/object is still just an ordinary C++ type/function/object.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. - - - -[^clean-cpp1]: For presentation purposes, this documentation generally shows the Cpp1 code generated when using cppfront's `-c` (short for `-clean-cpp1`), which eliminates extra output including `#line` directives. In normal use, you won't need `-c`, because you want the `#line` information which enables accurate error messages, debuggers, and other tools. diff --git a/docs/index.md b/docs/index.md index 8efb1de319..48b6a279b1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,13 +9,9 @@ Having an unambiguous alternative "syntax 2" gives us: -- **Full freedom to evolve, without breaking any of today's code.** All code written in a distinct syntax is automatically a "bubble of new code that doesn't exist today," so we can make it mean whatever we think is best and know we aren't breaking any of today's existing code. For example, this approach lets us change language defaults, which would be impossible to do directly in an existing syntax. +- **Freedom to make C++ simpler and safer, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default. Examples: writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer, which would be impossible to do directly in an existing syntax without breaking lots of existing code. You can always "break the glass" when needed to violate the guidance, but has to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. -- **Power to make C++ simpler and safer, by making today's existing best practices the default.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default. For example, writing unsafe type casts is just not possible in Cpp2 syntax; the code won't compile. A programmer can always "break the glass" when needed to violate the guidance, but has to opt out explicitly by writing "unsafe," so if the program has a bug you can grep for those places to look at first. - -- **Perfect source compatibility always available, and you pay for it only if you use it.** You can write a "mixed" source files that has both Cpp2 and Cpp1 code, and get full backward C++ source compatibility. Or you can write a "pure" Cpp2 source file to use just the simpler syntax standalone, and get to write in a 10x simpler C++. This applies C++'s zero-overhead principle also to backward source compatibility: 100% of today's C++ is always available, but you pay for it only if you use it. - -- **Perfect link interoperability always, it's always still pure C++.** Any type/function/object written in either syntax is always still just a normal C++ type/function/object. Cpp2 code can invoke any existing C++ code or library (and vice versa) directly, with no wrapping/marshaling/thunking. There is no 'C++ compatibility bridge'... Cpp2 code *is* C++ code, just written more conveniently. +- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object written in either syntax is always still just a normal C++ type/function/object, so Cpp2 code can directly use any existing C++ code or library (and vice versa), with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get full backward C++ source compatibility including with SFINAE and macros and header files, or you can write a "pure" Cpp2 source file to use only the simpler syntax and get to write in a 10x simpler C++. Bjarne Stroustrup said it best: @@ -69,7 +65,7 @@ But let's add a little more, just to show a few things: ``` cpp title="hello.cpp2, slightly more interesting" main: () = { - words: std::vector = ( "Abigail", "Benjamin" ); + words: std::vector = ( "Alice", "Bob" ); hello( words[0] ); hello( words[1] ); std::cout << "... and goodnight\n"; @@ -81,20 +77,25 @@ hello: (msg: std::string_view) = This short program code already illustrates a few Cpp2 essentials. -**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. It's a main goal to avoid special cases, and avoid features that only work in certain parts of the language. All Cpp2 declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a."** +**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced is pronounced **"defined as."** -- `main` **is a** function that takes no arguments and returns nothing, and has the body shown. -- `words` **is a** `std::vector`, whose initial contents are `"Abigail"` and `"Benjamin"`. -- `hello` **is a** function that takes a `std::string_view` that it will only read from, that returns nothing, and has a body that prints the string to `cout` the usual C++ way. +- `main` **is a** function that takes no arguments and returns nothing, and is **defined as** the code body shown. +- `words` **is a** `std::vector`, initially **defined as** holding `"Alice"` and `"Bob"`. +- `hello` **is a** function that takes a `std::string_view` it will only read from and that returns nothing, and is **defined as** code that prints the string to `cout` the usual C++ way. All grammar is context-free. In particular, we (the human reading the code, or the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. -**Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. The above `hello` function uses string interpolation to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. In `main`, the declaration of `words` takes advantage of C++'s normal constructor template argument deduction (aka CTAD). Also, the calls to `words[0]` and `words[1]` are automatically bounds-checked by default — `vector` subscript accesses from Cpp2 code are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. +**Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. + +- `hello` uses **string interpolation** to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. +- `main` uses **"CTAD"** (C++'s normal constructor template argument deduction) to declare its `words` variable. +- `words[0]` and `words[1]` are **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. **Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: - - We can omit writing the `-> void` return type for a function that doesn't return anything, as both of these functions do. - - We can omit the `{` `}` around single-statement function bodies, as `hello` does. - - We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), `inout` for read-write, and also `copy`, `out`, `move`, and `forward`. + +- We can omit writing the `-> void` return type for a function that doesn't return anything, as both of these functions do. +- We can omit the `{` `}` around single-statement function bodies, as `hello` does. +- We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), `inout` for read-write, and also `copy`, `out`, `move`, and `forward`. **Order-independent by default.** Did you notice that `main` called `hello`, which was defined later? Cpp2 code is order-independent by default — there are no forward declarations. @@ -113,17 +114,16 @@ cppfront hello.cpp2 -p The result is an ordinary C++ file that looks like this: [^clean-cpp1] -``` cpp title="hello.cpp" +``` cpp title="hello.cpp — created by cppfront" linenums="1" #define CPP2_IMPORT_STD Yes #include "cpp2util.h" - auto main() -> int; auto hello(cpp2::in msg) -> void; auto main() -> int{ - std::vector words {"Abigail", "Benjamin"}; + std::vector words {"Alice", "Bob"}; hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(words, 0)); hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(std::move(words), 1)); std::cout << "... and goodnight\n"; @@ -135,17 +135,32 @@ auto hello(cpp2::in msg) -> void { Here we can see more of how Cpp2 makes it features work. -**How: Consistent context-free syntax.** Cpp2's context-free syntax converts directly to today's Cpp1 syntax. All the types/functions/objects are ordinary C++ types/functions/objects, but can be written and read without wrestling with context and ambiguity. +**How: Consistent context-free syntax.** + +- **Lines 8, 9, and 15:** Cpp2's context-free syntax converts directly to today's Cpp1 syntax. We can write and read our C++ types/functions/objects in simpler Cpp2 syntax without wrestling with context sensitivity and ambiguity, and they're all still just ordinary types/functions/objects. + +**How: Simple and safe by default.** + +- **Line 9:** CTAD just works, because it turns into ordinary C++ code which is CTAD-aware. +- **Lines 10-11:** The accesses of `words[0]` and `words[1]` are bounds-checked nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::ssize`-aware, when you use them from safe Cpp2 code. +- **Line 16:** String interpolation performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `bool`, to print `false` and `true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). + +**How: Simplicity through generality + defaults.** + +- **Line 7:** The default `in` parameter passing convention is implemented using `cpp2::in<>`, which is smart enough to pass by `const` value when that's safe and appropriate, otherwise by `const&`, so you don't have to choose the right one by hand. + +**How: Order-independent by default.** + +- **Lines 5 and 7:** Cppfront achieves order independence is by generating all the type and function forward declarations for you, so you don't have to. That's why `main` can just call `hello`: Both are forward-declared, so they can both see each other. -**How: Simple and safe by default.** Cpp2's contracts, `inspect`, string interpolation, and more are lowered to regular C++ code. Here, the body of `hello` shows how string interpolation is implemented to perform the string capture of `msg`'s current value. In `main`, we see why CTAD just works — because it turns into ordinary C++ CTAD-aware code. And we can see that cppfront injected calls to bounds-check the accesses of `words[0]` and `words[1]`, which it does by ensuring the index is betweeen `0` and `std::size(words)` — and, again, this works out of the box with your favorite standard library (or `std::ssize`-aware custom in-house container) you already have, when you use it from safe Cpp2 code. +**How: Seamless compatibility and interop.** -**How: Simplicity through generality + defaults.** The default `in` parameter passing convention is implemented using `cpp2::in<>`, which is smart enough to pass by `const` value or by `const&` so you don't have to do it by hand as today. +- **Lines 9, 12, and 16:** Calling existing C++ code is just ordinary direct calls, so there's never a need for wrapping/marshaling/thunking. -**How: Order-independent by default.** The way cppfront achieves order independence is by generating all the type and function forward declarations, so that you don't have to. (Maybe you're already doing that by hand, which is a good but laborious workaround today. In Cpp2, this is automated so that the programmer doesn't have to.) So there's really no magic about how `main` can call `hello`: it's forward-declared. +**How: C++ standard library always available.** -**How: Seamless compatibility and interop.** When you see the bodies of `main` and `hello`, it's clear why the uses of `std::cout` and `std::operator<<` and `std::string_view` are all direct without any wrapping/marshaling/thunking: It's just ordinary C++ code, as if you'd written it yourself by hand. Any type/function/object written in either syntax is always still just an ordinary C++ type/function/object. Cpp2 code can invoke any existing C++ code or library (and vice versa) directly, with no wrapping/marshaling/thunking. There is no 'C++ compatibility bridge'... Cpp2 code *is* C++ code, just written more conveniently. +- **Lines 1 and 3:** Because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet), the generated code tells `cpp2util.h` to `import` the entire standard library as a module (or do the equivalent via headers if modules are not available). -**How: C++ standard library always available.** Because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet), the generated code tells `cpp2util.h` to `import` the entire standard library as a module (or do the equivalent via headers). Cpp2 is a modules-first design, fully compatible with today's header files but attemping to "skate where the puck is going" by having first-class support for standard library modules. ### Building and running `hello.cpp` from the command line @@ -213,4 +228,4 @@ That's enough to enable builds, and the IDE just picks up the rest from the `.cp -[^clean-cpp1]: For presentation purposes, this documentation generally shows the Cpp1 code generated when using cppfront's `-c` (short for `-clean-cpp1`), which opts out of extra output in the `.cpp` file to work well out-of-the-box with C++ tools (e.g., enable IDEs to integrate cppfront error message output, debuggers to step to the right lines in Cpp2 source code, and other tool light-up). In normal use, you won't need `-c`. +[^clean-cpp1]: For presentation purposes, this documentation generally shows the `.cpp` as generated when using cppfront's `-c` (short for `-clean-cpp1`), which suppresses extra information cppfront normally emits in the `.cpp` to light up C++ tools (e.g., to let IDEs integrate cppfront error message output, debuggers step to the right lines in Cpp2 source code, and so forth). In normal use, you won't need `-c`. diff --git a/mkdocs.yml b/mkdocs.yml index e5d7a30a48..d74c057f56 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,3 +1,20 @@ +# To view the documentation locally on your machine, use the following steps +# +# Clone the GitHub cppfront repo locally, then on the command line: +# +# cd /github/cppfront +# python -m venv venv +# source venv/bin/activate +# pip install mkdocs-material +# mkdocs new . +# mkdocs serve +# +# The last command should eventually print something like +# Serving on http://127.0.0.1:8000/ +# and you can open that URL in a local brower. If you are locally editing +# the documentation, leave the server process running and the browser +# pages will auto-reload as you save edits. +# site_name: "Cpp2 and cppfront — An experimental 'C++ syntax 2', and its first compiler" theme: name: material @@ -42,4 +59,4 @@ markdown_extensions: - attr_list copyright: | - © Herb Sutter \ No newline at end of file + © Herb Suttercppfront license From 353332c65348423a59ebe6bcbb31c1bb1f0fddc2 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sun, 11 Feb 2024 21:32:10 -1000 Subject: [PATCH 10/64] Add "Cppfront reference," and start writing "Cpp2 reference" --- docs/index.md | 10 ++- docs/reference-cpp2.md | 108 ++++++++++++++++++++++ docs/reference-cppfront.md | 178 +++++++++++++++++++++++++++++++++++++ 3 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 docs/reference-cpp2.md create mode 100644 docs/reference-cppfront.md diff --git a/docs/index.md b/docs/index.md index 48b6a279b1..6e050cccc7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -63,7 +63,7 @@ main: () = std::cout << "Hello, world!\n"; But let's add a little more, just to show a few things: -``` cpp title="hello.cpp2, slightly more interesting" +``` cpp title="hello.cpp2 — slightly more interesting" main: () = { words: std::vector = ( "Alice", "Bob" ); hello( words[0] ); @@ -80,7 +80,9 @@ This short program code already illustrates a few Cpp2 essentials. **Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced is pronounced **"defined as."** - `main` **is a** function that takes no arguments and returns nothing, and is **defined as** the code body shown. + - `words` **is a** `std::vector`, initially **defined as** holding `"Alice"` and `"Bob"`. + - `hello` **is a** function that takes a `std::string_view` it will only read from and that returns nothing, and is **defined as** code that prints the string to `cout` the usual C++ way. All grammar is context-free. In particular, we (the human reading the code, or the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. @@ -88,13 +90,17 @@ All grammar is context-free. In particular, we (the human reading the code, or t **Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. - `hello` uses **string interpolation** to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. + - `main` uses **"CTAD"** (C++'s normal constructor template argument deduction) to declare its `words` variable. + - `words[0]` and `words[1]` are **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. **Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: - We can omit writing the `-> void` return type for a function that doesn't return anything, as both of these functions do. + - We can omit the `{` `}` around single-statement function bodies, as `hello` does. + - We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), `inout` for read-write, and also `copy`, `out`, `move`, and `forward`. **Order-independent by default.** Did you notice that `main` called `hello`, which was defined later? Cpp2 code is order-independent by default — there are no forward declarations. @@ -223,7 +229,9 @@ Finally, put the `/cppfront/include` directory on your `INCLUDE` path. In Soluti That's enough to enable builds, and the IDE just picks up the rest from the `.cpp` file that cppfront generated: - **The cppfront error messages in `filename(line, col)` format.** Most C++ IDEs recognize these, and usually automatically merge any diagnostic output wherever compiler error output normally appears. If your IDE prefers `filename:line:col`, just use the cppfront `-format-colon-errors` command line option. + - **The `#line` directives cppfront emits in the generated `.cpp` file.** Most C++ debuggers recognize these and will know to step through the `.cpp2` file. Note that `#line` emission is on by default, but if you choose `-c` (short for `-clean-cpp1`) these will be suppressed and then the debugger will step through the generated C++ code instead. + - **Regardless of syntax, every type/function/object is still just an ordinary C++ type/function/object.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. diff --git a/docs/reference-cpp2.md b/docs/reference-cpp2.md new file mode 100644 index 0000000000..b2ed8c5d4d --- /dev/null +++ b/docs/reference-cpp2.md @@ -0,0 +1,108 @@ + +# Cpp2 reference + + +## Common programming concepts + +### Comments + +Cpp2 supports the same `// line comments` and `/* stream comments */` as Cpp1 (today's C++ syntax). + +### Fundamental data types + +Cpp2 supports the same fundamental types as today's Cpp1, but additionally provides the following aliases in namespace `cpp2`: + +| Known-width types | Synonym for | +|---|---| +| `i8` | `std::int8_t` | +| `i16` | `std::int16_t` | +| `i32` | `std::int32_t` | +| `i64` | `std::int64_t` | +| `u8` | `std::uint8_t` | +| `u16` | `std::uint16_t` | +| `u32` | `std::uint32_t` | +| `u64` | `std::uint64_t` | + + +| Variable-width types (Cpp2-compatible single-word names) | Synonym for (multi-word names not allowed in Cpp2) | +|---|---| +| `ushort` | `unsigned short` | +| `uint` | `unsigned int` | +| `ulong` | `unsigned long` | +| `longlong` | `long long` | +| `ulonglong` | `unsigned long long` | +| `longdouble` | `long double` | + +| For compatibility/interop only | Synonym for | Notes | +|---|---|---| +| `_schar` | `signed char` | Normally, prefer `i8` instead | +| `_uchar` | `unsigned char` | Normally, prefer `u8` instead | + +### Literals + +Cpp2 supports the same `'c'`haracter, `"string"`, binary, integer, and floating point literals as Cpp1, including most Unicode encoding prefixes and raw string literals. + +Cpp2 supports using Cpp1 user-defined literals. However, because Cpp2 has unified function call syntax (UFCS), the preferred way to author the equivalent in Cpp2 is to just write a function or type name as a `.` call suffix. For example: + +- You can create a `u8` value by writing either `u8(123)` or **`123.u8()`**. [^u8using] + +- You can write a 'constexpr' function like `nm: (value: i64) -> my_nanometer_type == { /*...*/ }` that takes an integer and returns a value of a strongly typed "nanometer" type, and then create a `nm` value by writing either `nm(123)` or **`123.nm()`**. + +Both `123.n()` and `123.u8()` are very similar to user-defined literal syntax, and more general. + + +### Captures, including interpolations + + +### Control flow + +#### `if` and `else` branches + +#### `for`, `while`, and `do` loops + +#### `inspect` expressions (pattern matching) + + +## Functions + +### Named functions + +### Unnamed function expressions + +### Design note: Unifying functions and local scopes + + +## Types and type-scope functions + +### Base types + +### Virtual functions + +### `operator=`: Unified construction and assignment + +### `operator<=>`: Unified comparisons (mostly in C++20) + + +## Metafunctions + +### Built-in metafunctions + +### Writing your own metafunctions + +### Reflection API reference + + +## Namespaces + +### `using` + + +## Modules + +### `import` + +### `export` + + + +[^u8using]: Or `123.cpp2::u8()` if you aren't `using` the namespace or that specific name. diff --git a/docs/reference-cppfront.md b/docs/reference-cppfront.md new file mode 100644 index 0000000000..c1815c63c0 --- /dev/null +++ b/docs/reference-cppfront.md @@ -0,0 +1,178 @@ + +# Cppfront reference + +## Mixing Cpp1 (today's C++) and Cpp2 + +Cppfront compiles a `.cpp2` file and produces a `.cpp` file to be compiled by your favorite C++20 or higher C++ compiler. + +The same `.cpp2` file may contain both Cpp2 syntax and today's "Cpp1" C++ syntax, **side by side but not nested**. + +For example, this is not valid because it tries to nest Cpp2 code inside Cpp1 code, and vice versa: + +``` cpp title="ERROR.cpp2 — this is NOT allowed" linenums="1" hl_lines="5 6 9 14" +#include // Cpp1 syntax +#include // Cpp1 syntax + +namespace N { // Cpp1 syntax + hello: (msg: std::string_view) = // Cpp2 syntax (not allowed inside Cpp1 code) + std::cout << "Hello, (msg)$!\n"; // Cpp2 syntax +} // Cpp1 syntax + +main: () = { // Cpp2 syntax + auto words = std::vector{ "Alice", "Bob" }; // Cpp1 syntax (not allowed inside Cpp2 code) + N::hello( words[0] ); // ? could be either + N::hello( words[1] ); // ? could be either + std::cout << "... and goodnight\n"; // ? could be either +} // Cpp2 syntax +``` + +The above nesting is not supported because it would create not just parsing problems but also semantic ambiguities. For example, if lines 11 and 12 are Cpp2, then the `words[0]` and `words[1]` subscript expressions are bounds-checked and bounds-safe by default; but if they are Cpp1 lines, they are not bounds-checked. + +This is fine, where the Cpp2 and Cpp1 code are side by side and seamlessly call each other directly as usual: + +``` cpp title="mixed.cpp2 — this is perfectly okay" linenums="1" hl_lines="4-7" +#include // Cpp1 syntax +#include // Cpp1 syntax + +N: namespace = { // Cpp2 syntax + hello: (msg: std::string_view) = // Cpp2 syntax + std::cout << "Hello, (msg)$!\n"; // Cpp2 syntax +} // Cpp2 syntax + +int main() { // Cpp1 syntax + auto words = std::vector{ "Alice", "Bob" }; // Cpp1 syntax + N::hello( words[0] ); // Cpp1 syntax + N::hello( words[1] ); // Cpp1 syntax + std::cout << "... and goodnight\n"; // Cpp1 syntax +} // Cpp1 syntax +``` + +When cppfront compiles such a mixed file, it just passes through the Cpp1 code as-is, and translates the Cpp2 code to Cpp1 in-place. This means that when a call site (call this the "caller") uses a type/function/object (call this the "callee") written in the same file: + +- **Code written in all Cpp2 is always order-independent by default.** When a caller written in Cpp2 syntax uses a callee written in Cpp2 syntax, they can appear in either order in the file. + +- **Code written in Cpp1 is order-dependent as usual.** When either the caller or the callee (or both) are written in Cpp1 syntax, the callee must be declared before the caller. + + +## Cppfront command line + +Cppfront is invoked using + + cppfront [options] file ... + +where + +- **options** is optional, and can include options described on this page + +- **file ...** is a list of one or more `.cpp2` filenames to be compiled + +Command line options are spelled starting with `-` or `/` followed by the option name. For example, `-help` prints help. + +For convenience, you can shorten the name to any unique prefix not shared with another option. For example: + +- `-help` can be equivalently written as `-hel`, `-he`, or `-h`, because no other option starts with `h`. +- `-import-std` and `-include-std` can be shortened to `-im` and `-in` respectively, but not `-i` which would be ambiguous with each other. + + +## Basic command line options + +### `-help`, `-h`, `-?` + +Prints an abbreviated version of this documentation page. + +### `-import-std`, `-im` + +Makes the entire C++ standard library (namespace `std::`) available via a module `import std.compat;` (which implies `import std;`). + +> When you use either `-import-std` or `-include-std`, your `.cpp2` program will not need to explicitly `import` any C++ standard library module or `#include` any C++ standard library header (it can still do that, but it would be redundant). + +This option is implicitly set if `-pure-cpp2` is selected. + +This option is ignored if `-include-std` is selected. If your Cpp1 compiler does not yet support standard library modules `std` and `std.compat`, this option automatically uses `-include-std` instead as a fallback. + +### `-include-std`, `-in` + +Makes the entire C++ standard library (namespace `std::`) available via an '#include" of every standard header. + +This option should always work with all standard headers, including draft-standard C++26 headers that are not yet in a published standard, because it tracks new headers as they are added and uses feature tests to not include headers that are not yet available on your Cpp1 implementation. + +### `-pure-cpp2`, `-p` + +Allow Cpp2 syntax only. + +This option also sets `-import-std`. + +### `-version`, `-vers` + +Print version, build, copyright, and license information. + + +## Additional dynamic safety checks and contract information + +### `-add-source-info`, `-a` + +Enable `source_location` information for contract checks. If this is supported by your Cpp1 compiler, the default contract failure messages will include exact file/line/function information. For example, if the default `Bounds` violation handler would print this without `-a`: + + Bounds safety violation: out of bounds access attempt detected - attempted access at index 2, [min,max] range is [0,1] + +then it would print something like this with `-a` (the exact text will vary with the Cpp1 standard library vendor's `source_location` implementation): + + demo.cpp2(4) int __cdecl main(void): Bounds safety violation: out of bounds access attempt detected - attempted access at index 2, [min,max] range is [0,1] + +### `-no-comparison-checks`, `-no-c` + +Disable mixed-sign comparison safety checks. If not disabled, mixed-sign comparisons are diagnosed by default. + +### `-no-null-checks`, `-no-n` + +Disable null safety checks. If not disabled, null dereference checks are performed by default. + +### `-no-subscript-checks`, `-no-s` + +Disable subscript bounds safety checks. If not disabled, subscript bounds safety checks are performed by default. + + +## Support for constrained target environments + +### `-fno-exceptions`, `-fno-e` + +Disable C++ exception handling. This should be used only if you must run in an environment that bans C++ exception handling, and so you are already using a similar command line option for your Cpp1 compiler. + +If this option is selected, a failed `as` for `std::variant` will assert. + +### `-fno-rtti`, `-fno-r` + +Disable C++ run-time type information (RTTI). This should be used only if you must run in an environment that bans C++ RTTI, and so you are already using a similar command line option for your Cpp1 compiler. + +If this option is selected, trying to using `as` for `*` (raw pointers) or `std::any` will assert. + + +## Other options + +### `-clean-cpp1`, `-c` + +Emit clean `.cpp` files without `#line` directives and other extra information that cppfront normally emits in the `.cpp` to light up C++ tools (e.g., to let IDEs integrate cppfront error message output, debuggers step to the right lines in Cpp2 source code, and so forth). In normal use, you won't need `-c`. + +### `-debug`, `-d` + +Emit compiler debug output. This is only useful when debugging cppfront itself. + +### `-emit-cppfront-info`, `-e` + +Emit cppfront version and build in the `.cpp` file. + +### `-format-colon-errors`, `-fo` + +Emit cppfront diagnostics using ':line:col:' format for line and column numbers, if that is the format better recognized by your IDE, so that it will pick up cppfront messages and integrate them in its normal error message output location. If not set, by default cppfront diagnostics use `(line,col)` format. + +### `-line-paths`, `-l` + +Emit absolute paths in `#line` directives. + +### `-output` _filename_, `-o` _filename_ + +Output to 'filename' (can be 'stdout'). If not set, the default output filename for is the same as the input filename without the `2` (e.g., compiling `hello.cpp2` by default writes its output to `hello.cpp`, and `header.h2` to `header.h`). + +### `-verbose`, `-verb` + +Print verbose statistics and `-debug` output. From 3648a5809757dcb4a5efa5d3dd1c99fcf5ecfac5 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 12 Feb 2024 07:51:44 -1000 Subject: [PATCH 11/64] Merged documentation from blog posts, and unary operators `_` wildcard, including in `inspect` and explicit discard Named `break` and `continue` `type` declaration syntax Explicit `this` `operator=` Chained comparisons Metafunctions overview `@enum` and `@flag_enum` `@union` --- docs/index.md | 2 +- docs/reference-cpp2.md | 407 ++++++++++++++++++++++++++++++++++++++++- 2 files changed, 401 insertions(+), 8 deletions(-) diff --git a/docs/index.md b/docs/index.md index 6e050cccc7..cab90cc28b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,7 +24,7 @@ My goal for this project is to try to prove that Bjarne Stroustrup has long been ### What is cppfront? -[**Cppfront**](https://github.com/hsutter/cppfront) is a compiler that compiles Cpp2 syntax to today's C++ syntax (aka Cpp1). This lets you start trying out Cpp2 syntax in any existing C++ project just by [adding a build step](#adding-cppfront-in-your-ide-build-system) to translate the Cpp2 to Cpp1 syntax, and the result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). +[**Cppfront**](https://github.com/hsutter/cppfront) is a compiler that compiles Cpp2 syntax to today's C++ syntax (aka Cpp1). This lets you start trying out Cpp2 syntax in any existing C++ project and build system just by [adding a build step](#adding-cppfront-in-your-ide-build-system) to translate the Cpp2 to Cpp1 syntax, and the result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). This deliberately follows Bjarne Stroustrup's wise approach with [**cfront**](https://en.wikipedia.org/wiki/Cfront), the original C++ compiler: In the 1980s and 1990s, Stroustrup created cfront to translate C++ to pure C, Stroustrup similarly ensured that C++ could be interleaved with C in the same source file, and C++ could always call any C code with no wrapping/marshaling/thunking. By providing a C++ compiler that emitted pure C, Stroustrup ensured full compatibility with the C ecosystems that already existed, and made it easy for people to start trying out C++ code in any existing C project by adding just another build step to translate the C++ to C first, and the result Just Worked with existing C tools. diff --git a/docs/reference-cpp2.md b/docs/reference-cpp2.md index b2ed8c5d4d..fc38404597 100644 --- a/docs/reference-cpp2.md +++ b/docs/reference-cpp2.md @@ -6,7 +6,48 @@ ### Comments -Cpp2 supports the same `// line comments` and `/* stream comments */` as Cpp1 (today's C++ syntax). +The usual `// line comments` and `/* stream comments */` are supported. For exmaple: + +``` cpp title="Example: Comments" +// A line comment: After //, the entire rest of the line is part of the comment + +/* + A stream comment: After /*, everything until the closing */ is part of the comment + */ +``` + +### The `_` wildcard, including explicit discard + +`_` is pronounced **"don't care"** and allowed as a wildcard in most contexts. For example: + +``` cpp title="Example: Wildcard (_)" +_ : std::lock_guard = mut; // don't care about the guard variable's name + +x : _ = 42; // don't care to write the variable's type, deduce it + // in cases like this, _ can be omitted... this is equivalent to "x := 42;" + +return inspect v -> std::string { + is std::vector = "v is a std::vector"; + is _ = "unknown"; // don't care what else, match anything +}; +``` + +All return values and results returned from `inout` and `out` parameters are treated as important and are not discarded by default. To explicitly discard such a value, assign it to `_`. For example: + +``` cpp title="Example: Using _ for explicit discard" +_ = vec.emplace_back(1,2,3); + // "_ =" is required to explicitly discard emplace_back's + // return value (non-void since C++17) + +{ + x := my_vector.begin(); + std::advance(x, 2); + _ = x; // required to explicitly discard x's new value, + // because std::advance modifies x's value +} +``` + +For details, see [Design note: Explicit discard](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Explicit-discard). In Cpp2, 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. ### Fundamental data types @@ -50,6 +91,70 @@ Cpp2 supports using Cpp1 user-defined literals. However, because Cpp2 has unifie Both `123.n()` and `123.u8()` are very similar to user-defined literal syntax, and more general. +### Operators + +Operators have the same precedence and associativity as in Cpp1, but some unary operators that are prefix (always or sometimes) in Cpp1 are postfix (always) in Cpp2. + +#### Unary operators + +The operators `!`, `+`, and `-` are prefix, as in Cpp1. For example: + +``` cpp title="Example: Prefix operators" +if !vec.empty() { + vec.emplace_back( -123.45 ); +} +``` + +| Unary operator | Cpp2 example | Cpp1 equivalent | +|---|---|---| +| `!` | `!vec.empty()` | `!vec.empty()` | +| `+` | `+100` | `+100` | +| `-` | `-100` | `-100` | + +The operators `.`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, and `$` are postfix. For example: + +``` cpp title="Example: Postfix operators" +// Cpp1 examples, from cppfront's own source code: +// address = &(*tokens)[pos + num]; +// is_void = *(*u)->identifier == "void"; +// Cpp2 equivalents: + address = tokens*[pos + num]&; + is_void = u**.identifier* == "void"; +``` + +> Note: Postfix notation lets the code read fluidly left-to-right, in the same order in which the operators will be applied, and lets declaration syntax be consistent with usage syntax. For more details, see [Design note: Postfix operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators) + +> Note: The function call syntax `f(x)` calls a namespace-scope function, or a function object, named `f`. The function call syntax `x.f()` calls a type-scope function in the type of `x` if available, otherwise calls the same as `f(x)`. + +| Unary operator | Cpp2 example | Cpp1 equivalent | +|---|---|---| +| `.` | `obj.f()` | `obj.f()` | +| `*` | `pobj*.f()` | `(*pobj).f()` or `pobj->f()` | +| `&` | `obj&` | `&obj` | +| `~` | `val~` | `~val` | +| `++` | `iter++` | `++iter` | +| `--` | `iter--` | `--iter` | +| `(` `)` | `f( 1, 2, 3)` | `f( 1, 2, 3)` | +| `[` `]` | `vec[123]` | `vec[123]` | +| `$` | `val$` | (reflection — no C++23 equivalent) | + +> Because `++` and `--` always have in-place update semantics, we never need to remember "use prefix `++`/`--` unless you need a copy of the old value." If you do need a copy of the old value, just take the copy before calling `++`/`--`. + +Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~` are used as binary operators they must be preceded by whitespace. For example: + +| Unary postfix operators that
are also binary operators | Cpp2 example | Cpp1 equivalent | +|---|---|---| +| `*` | `pobj* * 42` | `(*pobj)*42` | +| `&` | `obj& & mask`

(note: allowed in unsafe code only) | `&obj & mask` | +| `~` | `~val ~ bitcomplement` | `val~ ~ bitcomplement` | + + +#### Binary operators + + +>> +>>= + ### Captures, including interpolations @@ -60,33 +165,303 @@ Both `123.n()` and `123.u8()` are very similar to user-defined literal syntax, a #### `for`, `while`, and `do` loops +Loops can be named using the usual **name `:`** name introduction syntax, and `break` and `continue` can refer to those names. For example: + +``` cpp title="Example: Writing a simple type" +outer: while i Cpp2 syntax has no separate base 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 subobject, so that it naturally outlives the base subobject. + +> Cpp2 code doesn't need workarounds like Boost's `base_from_member`, because all of the motivating examples for that can be written directly. See my comments on [cppfront issue #334](https://github.com/hsutter/cppfront/issues/334) for details. + +### `virtual`, `override`, and `final` + +Virtual functions are written by specifying exactly one of `virtual`, `override`, or `final` on the `this` parameter. A pure virtual function is a function with a `virtual this` parameter and no body. For example: + +``` cpp title="Example: Virtual functions" +abstract_base: type = { + // ... + print: (virtual this, msg: std::string); // pure virtual (virtual + no body) + // ... +} + +derived: type = { + this: abstract_base; // derives from abstract_base + // ... + print: (override this, msg: std::string); // explicit override + // ... +} +``` + +### `operator=`: Construction, assignment, and destruction -### `operator=`: Unified construction and assignment +All value operations are spelled `operator=`, including construction, assignment, and destruction. All default to memberwise semantics and safe "explicit" by default. A special `that` parameter makes writing copy/move/conversion in particular simpler and safer. For details, see [Design note: operator=, this & that](https://github.com/hsutter/cppfront/wiki/Cpp2:-operator=,-this-&-that). 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). + +> 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" for the purpose of suppressing unwanted generated functions. + +- The most general form of `operator=` is `operator=: (out this, that)`. It 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 above for details). + +- All copy/move/comparison operator= functions are memberwise by default in Cpp2, including memberwise construction and assignment when you write them yourself. + +- 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. ### `operator<=>`: Unified comparisons (mostly in C++20) +Most of Cpp2's `operator<=>` has already been merged into ISO C++, except for allowing chained comparisons. In Cpp2, comparisons can be chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: + +- All mathematically sound and safe chains like `a <= b < c` are supported, with efficient single evaluation of each term. They are "sound" because they are transitive; these chains imply a relationship between `a` and `c` (in this case, the chain implies that `a <= c` is also true). + +- Nonsense chains like `a >= b < c` and `a != b != c` are compile time errors. They are "nonsense" because they are non-transitive; these chains do not imply any relationship between `a` and `c`. + ## Metafunctions +A metafunction is a compile-time function that can participate in interpreting the meaning of a declaration, and can: + +- apply defaults (e.g., `interface` makes functions virtual by default) +- enforce constraints (e.g., `value` enforces that the type has no virtual functions) +- generate additional functions and other code (e.g., `value` generates copy/move/comparison operations for a type if it didn't write them explicitly) + +The most important thing about metafunctions is that they are not hardwired language features — they are compile-time library code that uses the reflection and code generation API, that lets the author of an ordinary 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. + +### Applying metafunctions + +Using a metafunctionis always opt-in, by writing `@name` afer the `:` of a declaration, where `name` is the name of the metafunctions. This lets the type author declare (and the human reader see) the intent up front: "This isn't just any `type`, this is a `@value type`" which automatically gives the type default/copy/move construction and assignment, `<=>` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions: + +``` cpp title="Example: Using the value metafunction when writing a type" +point2d: @value type = { + x: i32 = 0; + y: i32 = 0; + // @value automatically generates default/copy/move + // construction/assignment and <=> strong_ordering comparison, + // and emits an error if you try to write a non-public + // destructor or any protected or virtual function +} +``` + +### Generating source code at compile time + ### Built-in metafunctions + +#### `enum` and `flag_enum` + +Cpp2 has no `enum` feature hardwired into the language. Instead you apply the `@enum` metafunction when writing an ordinary `type`: + +``` cpp title="Example: Using @enum" +// 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 here 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). + +Unlike C `enum`, this `@union` is scoped and strongly type (does not implicitly convert to the underlying type. + +Unlike C++11 `enum class`, it's "just a `type`" which means it can naturally also have member functions and other things that a type can have: + +``` cpp title="Example: An @enum type with a member function" +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: + +``` cpp title="Example: Using @flag_enum" +// 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; +} +``` + + +#### `union` + +`@union` declaratively opts into writing a safe discriminated union/variant dynamic type. For example: + +``` cpp title="Example: Using @union" +// 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; +} + +main: () = { + x: name_or_number = (); + + x.set_name("xyzzy"); // now x is a string + std::cout << x.name(); // prints the string + + // trying to use x.num() here would cause a Type safety + // contract violation, because x is currently a string + + x.set_num( 120 ); // now x is a number + std::cout << x.num() + 3; // prints 123 +} +``` + +Unlike C `union`, this `@union` is safe to use because it always ensures only the active type is accessed. + +Unlike C++11 `std::variant`, this `@union` is easier to use because its alternatives are anonymous, and safer to use because each union type is a distinct type. [^variant] + +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: + +Because a `@union type` is still a `type`, it can naturally have other things normal types can have, such as template parameter lists and member functions: + +``` cpp title="Example: A templated custom safe union type" +name_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" here, but is legal + // whichever alternative is active +} +``` + ### Writing your own metafunctions ### Reflection API reference @@ -97,6 +472,22 @@ Both `123.n()` and `123.u8()` are very similar to user-defined literal syntax, a ### `using` + +## `requires` constraints + + + +## Aliases + +### Namespace aliases + +### Type aliases + +### Function aliases + +### Object aliases + + ## Modules ### `import` @@ -106,3 +497,5 @@ Both `123.n()` and `123.u8()` are very similar to user-defined literal syntax, a [^u8using]: Or `123.cpp2::u8()` if you aren't `using` the namespace or that specific name. + +[^variant]: With `variant`, 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. From b75d2c02cba0d1ea7fddea10fd8fb9c54ab371fe Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 12 Feb 2024 16:14:23 -1000 Subject: [PATCH 12/64] Integrate links to wiki design notes --- docs/index.md | 44 +++++++++++++++++++++++------------------- docs/reference-cpp2.md | 37 +++++++++++++++++++++++------------ 2 files changed, 49 insertions(+), 32 deletions(-) diff --git a/docs/index.md b/docs/index.md index cab90cc28b..cdb03249b4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,47 +5,49 @@ ### What is Cpp2? -"Cpp2" is shorthand name for an experimental alternate "syntax 2" for C++ itself. The goal is to help us evolve C++ to make it simpler and safer, while maintaining full source and link compatibility — but to only pay for perfect backward source compatibility when we actually use it. +"Cpp2," short for "C++ syntax 2," is my ([Herb Sutter's](https://github.com/hsutter)) personal project to try to make writing ordinary C++ types/functions/objects be much simpler and safer, without breaking backward compatibility. Bjarne Stroustrup said it best: -Having an unambiguous alternative "syntax 2" gives us: +> "Inside C++, there is a much smaller and cleaner language struggling to get out."
— Bjarne Stroustrup, _The Design and Evolution of C++_ (D&E), 1994 +> +> "Say 10% of the size of C++ in definition and similar in front-end compiler size. ... most of the simplification would come from generalization."
— Bjarne Stroustrup, _ACM History of Programming Languages III_, 2007 -- **Freedom to make C++ simpler and safer, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default. Examples: writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer, which would be impossible to do directly in an existing syntax without breaking lots of existing code. You can always "break the glass" when needed to violate the guidance, but has to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. +My goal is to try to prove that Stroustrup is right: that it's possible and desirable to have true C++ with all its expressive power and control and with full backward compatibility, but in a flavor that's **10x simpler** with fewer quirks and special cases to remember, and **50x safer** where it's far easier to not write security bugs by accident. -- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object written in either syntax is always still just a normal C++ type/function/object, so Cpp2 code can directly use any existing C++ code or library (and vice versa), with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get full backward C++ source compatibility including with SFINAE and macros and header files, or you can write a "pure" Cpp2 source file to use only the simpler syntax and get to write in a 10x simpler C++. +We can't make an improvement that large to C++ via gradual evolution to today's syntax, because we can't break backward source compatibility with today's syntax. That means we can never change a language feature default in today's syntax, not even if the default creates a security vulnerability pitfall, because changing a default would break vast swathes of existing code. Instead, having an distinct alternative syntax gives us a "bubble of new code" that doesn't exist today, and: -Bjarne Stroustrup said it best: +- **Freedom to make C++ simpler and safer, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default when using "syntax 2." Examples: Writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer. You can always "break the glass" when needed to violate the guidance, but has to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. For details, see [Design note: unsafe code](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unsafe-code). -> "Inside C++, there is a much smaller and cleaner language struggling to get out."
— Bjarne Stroustrup, _The Design and Evolution of C++_ (D&E), 1994 -> -> "Say 10% of the size of C++ in definition and similar in front-end compiler size. ... most of the simplification would come from generalization."
— Bjarne Stroustrup, _ACM History of Programming Languages III_, 2007 +- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object written in either syntax is always still just a normal C++ type/function/object, so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. -My goal for this project is to try to prove that Bjarne Stroustrup has long been right: That it's possible and desirable to have true C++ with all its expressive power and control and with full backward compatibility, but in a C++ that's **10x simpler** with fewer warts and special cases, and **50x safer** where it's far easier to not write security bugs by accident. +**What it isn't.** Cpp2 is not a successor or alternate language with it own divergent or incompatible ecosystem. For example, it does not have its own nonstandard incompatible modules/concepts/etc. that compete with the Standard C++ features; and does not replace your Standard C++ compiler and other tools. + +**What it is.** Cpp2 aims to be another "skin" for C++ itself, just a simpler and safer way to write ordinary C++ types/functions/objects. It seamlessly uses Standard C++ modules and concepts requirements and other features, and it works with all existing C++20 or higher compilers and tools right out of the box with zero overhead. ### What is cppfront? -[**Cppfront**](https://github.com/hsutter/cppfront) is a compiler that compiles Cpp2 syntax to today's C++ syntax (aka Cpp1). This lets you start trying out Cpp2 syntax in any existing C++ project and build system just by [adding a build step](#adding-cppfront-in-your-ide-build-system) to translate the Cpp2 to Cpp1 syntax, and the result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). +[**Cppfront**](https://github.com/hsutter/cppfront) is a compiler that compiles Cpp2 syntax to today's Cpp1 syntax. This lets you start trying out Cpp2 syntax in any existing C++ project and build system just by renaming a source file from `.cpp` to `.cpp2` and [adding a build step](#adding-cppfront-in-your-ide-build-system), and the result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). -This deliberately follows Bjarne Stroustrup's wise approach with [**cfront**](https://en.wikipedia.org/wiki/Cfront), the original C++ compiler: In the 1980s and 1990s, Stroustrup created cfront to translate C++ to pure C, Stroustrup similarly ensured that C++ could be interleaved with C in the same source file, and C++ could always call any C code with no wrapping/marshaling/thunking. By providing a C++ compiler that emitted pure C, Stroustrup ensured full compatibility with the C ecosystems that already existed, and made it easy for people to start trying out C++ code in any existing C project by adding just another build step to translate the C++ to C first, and the result Just Worked with existing C tools. +This deliberately follows Bjarne Stroustrup's wise approach with [**cfront**](https://en.wikipedia.org/wiki/Cfront), the original C++ compiler: In the 1980s and 1990s, Stroustrup created cfront to translate C++ to pure C, and similarly ensured that C++ could be interleaved with C in the same source file, and that C++ could always call any C code with no wrapping/marshaling/thunking. By providing a C++ compiler that emitted pure C, Stroustrup ensured full compatibility with the C ecosystems that already existed, and made it easy for people to start trying out C++ code in any existing C project by adding just another build step to translate the C++ to C first, and the result Just Worked with existing C tools. ### How do I get and build cppfront? -The full source code for cppfront is at the [**Cppfront repo**](https://github.com/hsutter/cppfront). +The full source code for cppfront is at the [**Cppfront GitHub repo**](https://github.com/hsutter/cppfront). -Cppfront builds with any major C++20 compiler. Go to the `/cppfront/source` directory, and run one of the following: +Cppfront builds with any recent C++ compiler. Go to the `/cppfront/source` directory, and run one of the following: -``` bash title="MSVC build instructions" +``` bash title="MSVC build instructions (Visual Studio 2019 version 16.11 or higher)" cl cppfront.cpp -std:c++20 -EHsc ``` -``` bash title="GCC build instructions" -g++-10 cppfront.cpp -std=c++20 -o cppfront +``` bash title="GCC build instructions (g++ 10 or higher)" +g++ cppfront.cpp -std=c++20 -o cppfront ``` -``` bash title="Clang build instructions" -clang++-12 cppfront.cpp -std=c++20 -o cppfront +``` bash title="Clang build instructions (Clang 12 or higher)" +clang++ cppfront.cpp -std=c++20 -o cppfront ``` That's it! @@ -55,7 +57,7 @@ That's it! ### A `hello.cpp2` program -Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program... even without any `#include`s: +Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program, no `#include` required: ``` cpp title="hello.cpp2, on one line" main: () = std::cout << "Hello, world!\n"; @@ -85,7 +87,7 @@ This short program code already illustrates a few Cpp2 essentials. - `hello` **is a** function that takes a `std::string_view` it will only read from and that returns nothing, and is **defined as** code that prints the string to `cout` the usual C++ way. -All grammar is context-free. In particular, we (the human reading the code, or the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. +All grammar is context-free. In particular, we (the human reading the code, or the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. For details, see [Design note: Unambiguous parsing](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unambiguous-parsing). **Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. @@ -103,6 +105,8 @@ All grammar is context-free. In particular, we (the human reading the code, or t - We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), `inout` for read-write, and also `copy`, `out`, `move`, and `forward`. +For details, see [Design note: Defaults are one way to say the same thing](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Defaults-are-one-way-to-say-the-same-thing). + **Order-independent by default.** Did you notice that `main` called `hello`, which was defined later? Cpp2 code is order-independent by default — there are no forward declarations. **Seamless compatibility and interop.** We can just use `std::cout` and `std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using direct calls without any wrapping/marshaling/thunking. diff --git a/docs/reference-cpp2.md b/docs/reference-cpp2.md index fc38404597..84d78c597e 100644 --- a/docs/reference-cpp2.md +++ b/docs/reference-cpp2.md @@ -12,7 +12,8 @@ The usual `// line comments` and `/* stream comments */` are supported. For exma // A line comment: After //, the entire rest of the line is part of the comment /* - A stream comment: After /*, everything until the closing */ is part of the comment + A stream comment: After /*, everything until the next * / (without a space between) + is part of the comment. Note that stream comments do not nest. */ ``` @@ -20,7 +21,7 @@ The usual `// line comments` and `/* stream comments */` are supported. For exma `_` is pronounced **"don't care"** and allowed as a wildcard in most contexts. For example: -``` cpp title="Example: Wildcard (_)" +``` cpp title="Example: _ wildcard" _ : std::lock_guard = mut; // don't care about the guard variable's name x : _ = 42; // don't care to write the variable's type, deduce it @@ -32,12 +33,12 @@ return inspect v -> std::string { }; ``` -All return values and results returned from `inout` and `out` parameters are treated as important and are not discarded by default. To explicitly discard such a value, assign it to `_`. For example: +Cpp2 treats all function outputs (return values, and results produced via `inout` and `out` parameters) as important, and does not let them be silently discarded by default. To explicitly discard such a value, assign it to `_`. For example: ``` cpp title="Example: Using _ for explicit discard" _ = vec.emplace_back(1,2,3); // "_ =" is required to explicitly discard emplace_back's - // return value (non-void since C++17) + // return value (which is non-void since C++17) { x := my_vector.begin(); @@ -53,7 +54,7 @@ For details, see [Design note: Explicit discard](https://github.com/hsutter/cppf Cpp2 supports the same fundamental types as today's Cpp1, but additionally provides the following aliases in namespace `cpp2`: -| Known-width types | Synonym for | +| Fixed-width types | Synonym for | |---|---| | `i8` | `std::int8_t` | | `i16` | `std::int16_t` | @@ -64,8 +65,7 @@ Cpp2 supports the same fundamental types as today's Cpp1, but additionally provi | `u32` | `std::uint32_t` | | `u64` | `std::uint64_t` | - -| Variable-width types (Cpp2-compatible single-word names) | Synonym for (multi-word names not allowed in Cpp2) | +| Variable-width types
(Cpp2-compatible single-word names) | Synonym for (these multi-word
names are not allowed in Cpp2) | |---|---| | `ushort` | `unsigned short` | | `uint` | `unsigned int` | @@ -74,7 +74,7 @@ Cpp2 supports the same fundamental types as today's Cpp1, but additionally provi | `ulonglong` | `unsigned long long` | | `longdouble` | `long double` | -| For compatibility/interop only | Synonym for | Notes | +| For compatibility/interop only,
so deliberately ugly names | Synonym for | Notes | |---|---|---| | `_schar` | `signed char` | Normally, prefer `i8` instead | | `_uchar` | `unsigned char` | Normally, prefer `u8` instead | @@ -83,13 +83,13 @@ Cpp2 supports the same fundamental types as today's Cpp1, but additionally provi Cpp2 supports the same `'c'`haracter, `"string"`, binary, integer, and floating point literals as Cpp1, including most Unicode encoding prefixes and raw string literals. -Cpp2 supports using Cpp1 user-defined literals. However, because Cpp2 has unified function call syntax (UFCS), the preferred way to author the equivalent in Cpp2 is to just write a function or type name as a `.` call suffix. For example: +Cpp2 supports using Cpp1 user-defined literals for compatibility, to support seamlessly using existing libraries. However, because Cpp2 has unified function call syntax (UFCS), the preferred way to author the equivalent in Cpp2 is to just write a function or type name as a `.` call suffix. For example: - You can create a `u8` value by writing either `u8(123)` or **`123.u8()`**. [^u8using] - You can write a 'constexpr' function like `nm: (value: i64) -> my_nanometer_type == { /*...*/ }` that takes an integer and returns a value of a strongly typed "nanometer" type, and then create a `nm` value by writing either `nm(123)` or **`123.nm()`**. -Both `123.n()` and `123.u8()` are very similar to user-defined literal syntax, and more general. +Both **`123.n()`** and **`123.u8()`** are very similar to user-defined literal syntax, and more general. ### Operators @@ -122,9 +122,9 @@ The operators `.`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, and `$` are postfix. F is_void = u**.identifier* == "void"; ``` -> Note: Postfix notation lets the code read fluidly left-to-right, in the same order in which the operators will be applied, and lets declaration syntax be consistent with usage syntax. For more details, see [Design note: Postfix operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators) +Postfix notation lets the code read fluidly left-to-right, in the same order in which the operators will be applied, and lets declaration syntax be consistent with usage syntax. For more details, see [Design note: Postfix operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators). -> Note: The function call syntax `f(x)` calls a namespace-scope function, or a function object, named `f`. The function call syntax `x.f()` calls a type-scope function in the type of `x` if available, otherwise calls the same as `f(x)`. +> Note: The function call syntax `f(x)` calls a namespace-scope function, or a function object, named `f`. The function call syntax `x.f()` is a unified function call syntax (aka UFCS) that calls a type-scope function in the type of `x` if available, otherwise calls the same as `f(x)`. For details, see [Design note: UFCS](https://github.com/hsutter/cppfront/wiki/Design-note%3A-UFCS). | Unary operator | Cpp2 example | Cpp1 equivalent | |---|---|---| @@ -148,6 +148,7 @@ Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~ | `&` | `obj& & mask`

(note: allowed in unsafe code only) | `&obj & mask` | | `~` | `~val ~ bitcomplement` | `val~ ~ bitcomplement` | +For more details, see [Design note: Postfix unary operators vs binary operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-unary-operators-vs-binary-operators). #### Binary operators @@ -159,6 +160,9 @@ Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~ ### Captures, including interpolations +For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). + + ### Control flow #### `if` and `else` branches @@ -194,6 +198,10 @@ outer: while i Date: Mon, 12 Feb 2024 19:31:10 -1000 Subject: [PATCH 13/64] Add more sections Type qualifiers Binary operators `is` and update chained comparisons --- docs/index.md | 4 +-- docs/reference-cpp2.md | 72 +++++++++++++++++++++++++++++++++++--- docs/reference-cppfront.md | 10 +++--- 3 files changed, 74 insertions(+), 12 deletions(-) diff --git a/docs/index.md b/docs/index.md index cdb03249b4..0b1b2ced98 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,9 +13,9 @@ My goal is to try to prove that Stroustrup is right: that it's possible and desirable to have true C++ with all its expressive power and control and with full backward compatibility, but in a flavor that's **10x simpler** with fewer quirks and special cases to remember, and **50x safer** where it's far easier to not write security bugs by accident. -We can't make an improvement that large to C++ via gradual evolution to today's syntax, because we can't break backward source compatibility with today's syntax. That means we can never change a language feature default in today's syntax, not even if the default creates a security vulnerability pitfall, because changing a default would break vast swathes of existing code. Instead, having an distinct alternative syntax gives us a "bubble of new code" that doesn't exist today, and: +We can't make an improvement that large to C++ via gradual evolution to today's syntax, because some important changes would require changing the meaning of code written in today's syntax. For example, we can never change a language feature default in today's syntax, not even if the default creates a security vulnerability pitfall, because changing a default would break vast swathes of existing code. Having a distinct alternative syntax gives us a "bubble of new code" that doesn't exist today, and have: -- **Freedom to make C++ simpler and safer, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default when using "syntax 2." Examples: Writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer. You can always "break the glass" when needed to violate the guidance, but has to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. For details, see [Design note: unsafe code](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unsafe-code). +- **Freedom to make any desired improvement, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default when using "syntax 2." Examples: Writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer. You can always "break the glass" when needed to violate the guidance, but has to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. For details, see [Design note: unsafe code](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unsafe-code). - **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object written in either syntax is always still just a normal C++ type/function/object, so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. diff --git a/docs/reference-cpp2.md b/docs/reference-cpp2.md index 84d78c597e..951590ea22 100644 --- a/docs/reference-cpp2.md +++ b/docs/reference-cpp2.md @@ -79,6 +79,15 @@ Cpp2 supports the same fundamental types as today's Cpp1, but additionally provi | `_schar` | `signed char` | Normally, prefer `i8` instead | | `_uchar` | `unsigned char` | Normally, prefer `u8` instead | +### Type qualifiers + +Types can be qualified with `const` and `*`. Types are written left-to-right, so a qualifier always applies to what immediately follows it. For example, to declare a `const` pointer to a non-`const` pointer to a `const i32` object, write: + +``` cpp title="Example: Type qualifiers" +// A const pointer to a non-const pointer to a const i32 object +p: const * * const i32; // +``` + ### Literals Cpp2 supports the same `'c'`haracter, `"string"`, binary, integer, and floating point literals as Cpp1, including most Unicode encoding prefixes and raw string literals. @@ -152,9 +161,60 @@ For more details, see [Design note: Postfix unary operators vs binary operators] #### Binary operators +Binary operators are the same as in Cpp1. From lowest to highest precedence: + +| Binary operators grouped by precedence | +|---| +| `*`, `/`, `%` | +| `+`, `-` | +| `<<`, `>>` | +| `<=>` | +| `<`, `>`, `<=`, `>=` | +| `==`, `!=` | +| `&` | +| `^` | +| `|` | +| `&&` | +| `||` | +| `=` and compound assignment | + +### `is` — safe type/value queries + +An `x is C` expression allows safe type and value queries, and evaluates to `true` if `x` matches constraint `C`. + +There are two kinds of `is`: + +- A **type query**, where `C` is a type constraint: A type, a template name, a concept, or a type predicate. Here `x` may be a type or an object; if it is an object, the query refers to `x`'s type. + +| Type constraint kind | Example | Notes | +|---|---|---| +| Static type query | `x is int` | +| Dynamic type query | `ptr* is Shape` | +| Static template type query | `x is std::vector` | +| Static concept query | `x is std::integral` | + +- A **value query**, where `C` is a value constraint: A value, or a value predicate. Here `x` must be an object. + +| Value constraint kind | Example | +|---|---| +| Value | `x is 0` | +| Value predicate | `x is (in(10, 20))` | + +`is` is useful throughout the language, including in `inspect` pattern matching alternatives. `is` is extensible, and works out of the box with `std::variant`, `std::optional`, and `std::any`. For examples, see: + +- [`mixed-inspect-templates.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-templates.cpp2) +- [`mixed-inspect-values.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-values.cpp2) +- [`mixed-inspect-values-2.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-values-2.cpp2) +- [`mixed-type-safety-1.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-type-safety-1.cpp2) +- [`pure2-enum.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-enum.cpp2) +- [`pure2-inspect-expression-in-generic-function-multiple-types.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-inspect-expression-in-generic-function-multiple-types.cpp2) +- [`pure2-inspect-fallback-with-variant-any-optional.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-inspect-fallback-with-variant-any-optional.cpp2) +- [`pure2-type-safety-1.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-type-safety-1.cpp2) +- [`pure2-type-safety-2-with-inspect-expression.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-type-safety-2-with-inspect-expression.cpp2) + + +### `as` — safe casts and conversions ->> ->>= ### Captures, including interpolations @@ -302,11 +362,13 @@ All value operations are spelled `operator=`, including construction, assignment ### `operator<=>`: Unified comparisons (mostly in C++20) -Most of Cpp2's `operator<=>` has already been merged into ISO C++, except for allowing chained comparisons. In Cpp2, comparisons can be chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: +Most of Cpp2's `operator<=>` has already been merged into ISO C++, except for allowing chained comparisons. In Cpp2, comparisons can be safely chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: + +- **Valid chains: All `<`/`<=`, all `>`/`>=`, or all `==`.** All mathematically sound and safe chains like `a <= b < c` are supported, with efficient single evaluation of each term. They are "sound" because they are transitive; these chains imply a relationship between `a` and `c` (in this case, the chain implies that `a <= c` is also true). -- All mathematically sound and safe chains like `a <= b < c` are supported, with efficient single evaluation of each term. They are "sound" because they are transitive; these chains imply a relationship between `a` and `c` (in this case, the chain implies that `a <= c` is also true). +- **Invalid chains: Everything else.** Nonsense chains like `a >= b < c` and `a != b != c` are compile time errors. They are "nonsense" because they are non-transitive; these chains do not imply any relationship between `a` and `c`. -- Nonsense chains like `a >= b < c` and `a != b != c` are compile time errors. They are "nonsense" because they are non-transitive; these chains do not imply any relationship between `a` and `c`. +For details, see [P0515 "Consistent comparison" section 3.3](https://wg21.link/p0515) and [P0893 "Chaining comparisons"](https://wg21.link/p0893). ## Metafunctions diff --git a/docs/reference-cppfront.md b/docs/reference-cppfront.md index c1815c63c0..54ee203677 100644 --- a/docs/reference-cppfront.md +++ b/docs/reference-cppfront.md @@ -34,10 +34,10 @@ This is fine, where the Cpp2 and Cpp1 code are side by side and seamlessly call #include // Cpp1 syntax #include // Cpp1 syntax -N: namespace = { // Cpp2 syntax - hello: (msg: std::string_view) = // Cpp2 syntax - std::cout << "Hello, (msg)$!\n"; // Cpp2 syntax -} // Cpp2 syntax +N: namespace = { // Cpp2 syntax + hello: (msg: std::string_view) = // Cpp2 syntax + std::cout << "Hello, (msg)$!\n"; // Cpp2 syntax +} // Cpp2 syntax int main() { // Cpp1 syntax auto words = std::vector{ "Alice", "Bob" }; // Cpp1 syntax @@ -163,7 +163,7 @@ Emit cppfront version and build in the `.cpp` file. ### `-format-colon-errors`, `-fo` -Emit cppfront diagnostics using ':line:col:' format for line and column numbers, if that is the format better recognized by your IDE, so that it will pick up cppfront messages and integrate them in its normal error message output location. If not set, by default cppfront diagnostics use `(line,col)` format. +Emit cppfront diagnostics using `:line:col:` format for line and column numbers, if that is the format better recognized by your IDE, so that it will pick up cppfront messages and integrate them in its normal error message output location. If not set, by default cppfront diagnostics use `(line,col)` format. ### `-line-paths`, `-l` From 1f8ac8d81312e8514a63b985c7c780f2a2953443 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 12 Feb 2024 20:45:21 -1000 Subject: [PATCH 14/64] Add `as` and `inspect` --- docs/reference-cpp2.md | 71 +++++++++++++++++++++++++++++++++--------- 1 file changed, 56 insertions(+), 15 deletions(-) diff --git a/docs/reference-cpp2.md b/docs/reference-cpp2.md index 951590ea22..c2eb78f1f6 100644 --- a/docs/reference-cpp2.md +++ b/docs/reference-cpp2.md @@ -180,11 +180,11 @@ Binary operators are the same as in Cpp1. From lowest to highest precedence: ### `is` — safe type/value queries -An `x is C` expression allows safe type and value queries, and evaluates to `true` if `x` matches constraint `C`. +An `x is C` expression allows safe type and value queries, and evaluates to `true` if `x` matches constraint `C`. It supports both static and dynamic queries, including customization with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. There are two kinds of `is`: -- A **type query**, where `C` is a type constraint: A type, a template name, a concept, or a type predicate. Here `x` may be a type or an object; if it is an object, the query refers to `x`'s type. +- A **type query**, where `C` is a type constraint: A type, a template name, a concept, or a type predicate. Here `x` may be a type, or an object or expression; if it is an object or expression, the query refers to `x`'s type. | Type constraint kind | Example | Notes | |---|---|---| @@ -193,7 +193,7 @@ There are two kinds of `is`: | Static template type query | `x is std::vector` | | Static concept query | `x is std::integral` | -- A **value query**, where `C` is a value constraint: A value, or a value predicate. Here `x` must be an object. +- A **value query**, where `C` is a value constraint: A value, or a value predicate. Here `x` must be an object or expression. | Value constraint kind | Example | |---|---| @@ -215,6 +215,49 @@ There are two kinds of `is`: ### `as` — safe casts and conversions +An `x as T` expression allows safe type casts. `x` must be an object or expression, and `T` must be a type. It supports both static and dynamic typing, including customization with support for the standard dynamically tyuped libraries `std::variant`, `std::optional`, and `std::any` provided in the box. For example: + +``` cpp title="Example: Using as" +main: () = { + a: std::any = 0; // a's type is now int, value 0 + test(a); // prints "zero" + a = "plugh" as std::string; // a's type is now std::string, value "plugh" + test(a); // prints "plugh" + test("xyzzy" as std::string); // prints "xyzzy" +} + +// A generic function that takes an argument 'x' of any type, +// same as "void test( auto const& x )" in C++20 syntax +test: (x) = { + std::cout << inspect x -> std::string { + is 0 = "zero"; + is std::string = x as std::string; + is _ = "(no match)"; + } << "\n"; +} +``` + +### `inspect` expressions — pattern matching + +An `inspect` expression allows pattern matching using `is`. For example: + +``` cpp title="Example: Using inspect" +// A generic function that takes an argument 'x' of any type +// and inspects various things about `x` +test: (x) = { + forty_two := 42; + std::cout << inspect x -> std::string { + is 0 = "zero"; // == 0 + is (forty_two) = "the answer"; // == forty_two + is int = "integer"; // is an int with some other value + is std::string = x as std::string; // is a std::string + is std::vector = "a std::vector"; // is a vector + is _ = "(no match)"; // something else + } << "\n"; +} +``` + +For more examples, see also the examples in the previous two sections on `is` and `as`, many of which use `inspect`. ### Captures, including interpolations @@ -223,6 +266,15 @@ There are two kinds of `is`: For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). + +## Functions and variables + +### Overview + +### Parameter passing + +All parameters and other objects in Cpp2 are `const` by default, except for local variables. For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). + ### Control flow #### `if` and `else` branches @@ -249,20 +301,9 @@ outer: while i Date: Tue, 13 Feb 2024 11:33:58 -1000 Subject: [PATCH 15/64] Document declarations, improve `inspect` discussion --- docs/index.md | 6 +-- docs/reference-cpp2.md | 83 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 78 insertions(+), 11 deletions(-) diff --git a/docs/index.md b/docs/index.md index 0b1b2ced98..6326abf699 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,7 +17,7 @@ We can't make an improvement that large to C++ via gradual evolution to today's - **Freedom to make any desired improvement, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default when using "syntax 2." Examples: Writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer. You can always "break the glass" when needed to violate the guidance, but has to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. For details, see [Design note: unsafe code](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unsafe-code). -- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object written in either syntax is always still just a normal C++ type/function/object, so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. +- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object/namespace/module/etc. written in either syntax is always still just a normal C++ type/function/object/namespace/module/etc., so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. **What it isn't.** Cpp2 is not a successor or alternate language with it own divergent or incompatible ecosystem. For example, it does not have its own nonstandard incompatible modules/concepts/etc. that compete with the Standard C++ features; and does not replace your Standard C++ compiler and other tools. @@ -79,7 +79,7 @@ hello: (msg: std::string_view) = This short program code already illustrates a few Cpp2 essentials. -**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced is pronounced **"defined as."** +**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object/namespace/module/etc. declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced is pronounced **"defined as."** - `main` **is a** function that takes no arguments and returns nothing, and is **defined as** the code body shown. @@ -236,7 +236,7 @@ That's enough to enable builds, and the IDE just picks up the rest from the `.cp - **The `#line` directives cppfront emits in the generated `.cpp` file.** Most C++ debuggers recognize these and will know to step through the `.cpp2` file. Note that `#line` emission is on by default, but if you choose `-c` (short for `-clean-cpp1`) these will be suppressed and then the debugger will step through the generated C++ code instead. -- **Regardless of syntax, every type/function/object is still just an ordinary C++ type/function/object.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. +- **Regardless of syntax, every type/function/object/namespace/module/etc. is still just an ordinary C++ type/function/object/namespace/module/etc.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. diff --git a/docs/reference-cpp2.md b/docs/reference-cpp2.md index c2eb78f1f6..9af29d4539 100644 --- a/docs/reference-cpp2.md +++ b/docs/reference-cpp2.md @@ -17,6 +17,60 @@ The usual `// line comments` and `/* stream comments */` are supported. For exma */ ``` +### Declarations + +All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. + +- The `:` is pronounced **"is a."** +- The `=` is pronounced is pronounced **"defined as."** +- The _statement_ is typically an expression statement (e.g., `a + b();`) or a compound statement (e.g., `{ /*...*/ return c(d) / e; }`). + +For example: + +``` cpp title="Example: Consistent declartions — name : kind = statement" +// n is a namespace defined as the following scope +n: namespace += { + // shape is a type defined as the following scope + shape: type + = { + // points is an object of type std::vector, + // defined as having an empty default value + // (type-scope objects are private by default) + points: std::vector = (); + + // draw is a function taking 'this' and 'canvas' parameters + // and returning bool, defined as the following body + // (type-scope functions are public by default) + // - this is as if 'this: shape', an object of type shape + // - where is an object of type canvas + draw: (this, where: canvas) -> bool + = { + // pen is an object of deduced (omitted) type 'color', + // defined as having initial value 'color::red' + pen := color::red; + + // success is an object of deduced (omitted) type bool, + // defined as having initial value 'false' + success := false; + + // ... + + return success; + } + + // count is a function taking 'this' and returning a type + // deduced from its body, defined as a single-expression body + count: (this) = points.ssize(); + + // ... + } + + // color is an @enum type (described later) + color: @enum type = { red; green; blue; } +} +``` + ### The `_` wildcard, including explicit discard `_` is pronounced **"don't care"** and allowed as a wildcard in most contexts. For example: @@ -237,9 +291,16 @@ test: (x) = { } ``` -### `inspect` expressions — pattern matching +### `inspect` — pattern matching + +An `inspect expr -> Type` expression allows pattern matching using `is`. -An `inspect` expression allows pattern matching using `is`. For example: +- `expr` is evaluated once. +- Each alternative spelled `is C` is evaluated in order as if called with `expr is C`. +- If an alternative evaluates to `true`, then its `= alternative;` body is used as the value of the entire `inspect` expression, and the meaning is the same as if the entire `inspect` expression had been written as just `:Type = alternative;` — i.e., an unnamed object expression (aka 'temporary object') of type `Type` initialized with `alternative`. +- A catchall `is _` is required. + +For example: ``` cpp title="Example: Using inspect" // A generic function that takes an argument 'x' of any type @@ -248,19 +309,25 @@ test: (x) = { forty_two := 42; std::cout << inspect x -> std::string { is 0 = "zero"; // == 0 - is (forty_two) = "the answer"; // == forty_two - is int = "integer"; // is an int with some other value - is std::string = x as std::string; // is a std::string - is std::vector = "a std::vector"; // is a vector - is _ = "(no match)"; // something else + is (forty_two) = "the answer"; // == 42 + is int = "integer"; // is type int (value other than 0 or 42) + is std::string = x as std::string; // is type std::string + is std::vector = "a std::vector"; // is a vector + is _ = "(no match)"; // is something else } << "\n"; } + +// Sample call site +test(42); + // Behaves as if the following function were called: + // test: (x) = { std::cout << (:std::string = "the answer") << "\n";; } + // (and that's why inspect alternatives are introduced with '=') ``` For more examples, see also the examples in the previous two sections on `is` and `as`, many of which use `inspect`. -### Captures, including interpolations +### `$` — captures, including interpolations For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). From 3b7a9d51c528fad93c1f6a6810a431c302809200 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Tue, 13 Feb 2024 11:35:54 -1000 Subject: [PATCH 16/64] Fix a few typos --- docs/index.md | 2 +- docs/reference-cpp2.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index 6326abf699..9a778dfef6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -79,7 +79,7 @@ hello: (msg: std::string_view) = This short program code already illustrates a few Cpp2 essentials. -**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object/namespace/module/etc. declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced is pronounced **"defined as."** +**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object/namespace/module/etc. declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced **"defined as."** - `main` **is a** function that takes no arguments and returns nothing, and is **defined as** the code body shown. diff --git a/docs/reference-cpp2.md b/docs/reference-cpp2.md index 9af29d4539..5e0a5f5ebb 100644 --- a/docs/reference-cpp2.md +++ b/docs/reference-cpp2.md @@ -22,12 +22,12 @@ The usual `// line comments` and `/* stream comments */` are supported. For exma All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. - The `:` is pronounced **"is a."** -- The `=` is pronounced is pronounced **"defined as."** +- The `=` is pronounced **"defined as."** - The _statement_ is typically an expression statement (e.g., `a + b();`) or a compound statement (e.g., `{ /*...*/ return c(d) / e; }`). For example: -``` cpp title="Example: Consistent declartions — name : kind = statement" +``` cpp title="Example: Consistent declarations — name : kind = statement" // n is a namespace defined as the following scope n: namespace = { From 64deec14a221f771844a46a18d0a690c5e49e04d Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Tue, 13 Feb 2024 16:33:29 -1000 Subject: [PATCH 17/64] Add Hello-world xref --- docs/reference-cpp2.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/reference-cpp2.md b/docs/reference-cpp2.md index 5e0a5f5ebb..b3c3af60a9 100644 --- a/docs/reference-cpp2.md +++ b/docs/reference-cpp2.md @@ -1,6 +1,8 @@ # Cpp2 reference +### See also: **[Hello, world!](index.md/#hello-world)** + ## Common programming concepts @@ -24,6 +26,7 @@ All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. - The `:` is pronounced **"is a."** - The `=` is pronounced **"defined as."** - The _statement_ is typically an expression statement (e.g., `a + b();`) or a compound statement (e.g., `{ /*...*/ return c(d) / e; }`). +- Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted entirely to accept a default (e.g., `x: int = 0;` can be equivalently written `x: _ = 0;` or `x := 0;` both of which deduce the type). For example: @@ -344,9 +347,9 @@ All parameters and other objects in Cpp2 are `const` by default, except for loca ### Control flow -#### `if` and `else` branches +#### `if`, `else` — branches -#### `for`, `while`, and `do` loops +#### `for`, `while`, `do` — loops Loops can be named using the usual **name `:`** name introduction syntax, and `break` and `continue` can refer to those names. For example: From eb085c5f52026243e0193f812716c48b2cc72ff5 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Tue, 13 Feb 2024 17:04:17 -1000 Subject: [PATCH 18/64] Update mkdocs.yml --- mkdocs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index d74c057f56..5f61d6c669 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,7 +20,11 @@ theme: name: material features: - navigation.tabs + - navigation.tabs.sticky - navigation.sections + - navigation.expand + - navigation.instant + - navigation.instant.preview - toc.integrate - navigation.top - search.suggest From 8f7d2652ac42ca58249e3d6e7efe84951856be00 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Tue, 13 Feb 2024 17:16:07 -1000 Subject: [PATCH 19/64] Improve mixed files examples and flow --- docs/reference-cppfront.md | 70 +++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/docs/reference-cppfront.md b/docs/reference-cppfront.md index 54ee203677..e0a50bd795 100644 --- a/docs/reference-cppfront.md +++ b/docs/reference-cppfront.md @@ -7,51 +7,51 @@ Cppfront compiles a `.cpp2` file and produces a `.cpp` file to be compiled by yo The same `.cpp2` file may contain both Cpp2 syntax and today's "Cpp1" C++ syntax, **side by side but not nested**. -For example, this is not valid because it tries to nest Cpp2 code inside Cpp1 code, and vice versa: - -``` cpp title="ERROR.cpp2 — this is NOT allowed" linenums="1" hl_lines="5 6 9 14" -#include // Cpp1 syntax -#include // Cpp1 syntax - -namespace N { // Cpp1 syntax - hello: (msg: std::string_view) = // Cpp2 syntax (not allowed inside Cpp1 code) - std::cout << "Hello, (msg)$!\n"; // Cpp2 syntax -} // Cpp1 syntax - -main: () = { // Cpp2 syntax - auto words = std::vector{ "Alice", "Bob" }; // Cpp1 syntax (not allowed inside Cpp2 code) - N::hello( words[0] ); // ? could be either - N::hello( words[1] ); // ? could be either - std::cout << "... and goodnight\n"; // ? could be either -} // Cpp2 syntax +For example, this source file is fine, where the Cpp2 and Cpp1 code are side by side and seamlessly call each other directly as usual: + +``` cpp title="mixed.cpp2 — Mixing Cpp1 and Cpp2 code side by side in the same source file is okay" linenums="1" hl_lines="4-7" +#include // Cpp1 syntax +#include // Cpp1 syntax + +N: namespace = { // Cpp2 syntax + hello: (msg: std::string_view) = // Cpp2 syntax + std::cout << "Hello, (msg)$!\n"; // Cpp2 syntax +} // Cpp2 syntax + +int main() { // Cpp1 syntax + auto words = std::vector{ "Alice", "Bob" }; // Cpp1 syntax + N::hello( words[0] ); // Cpp1 syntax + N::hello( words[1] ); // Cpp1 syntax + std::cout << "... and goodnight\n"; // Cpp1 syntax +} // Cpp1 syntax ``` -The above nesting is not supported because it would create not just parsing problems but also semantic ambiguities. For example, if lines 11 and 12 are Cpp2, then the `words[0]` and `words[1]` subscript expressions are bounds-checked and bounds-safe by default; but if they are Cpp1 lines, they are not bounds-checked. +When cppfront compiles such a mixed file, it just passes through the Cpp1 code as-is, and translates the Cpp2 code to Cpp1 in-place. This means that when a call site (call this the "caller") uses a type/function/object (call this the "callee") written in the same file: + +- **Code written in all Cpp2 is always order-independent by default.** When a caller written in Cpp2 syntax uses a callee written in Cpp2 syntax, they can appear in either order in the file. + +- **Code written in Cpp1 is order-dependent as usual.** When either the caller or the callee (or both) are written in Cpp1 syntax, the callee must be declared before the caller. -This is fine, where the Cpp2 and Cpp1 code are side by side and seamlessly call each other directly as usual: +However, this source file is not valid, because it tries to nest Cpp2 code inside Cpp1 code, and vice versa: -``` cpp title="mixed.cpp2 — this is perfectly okay" linenums="1" hl_lines="4-7" +``` cpp title="ERROR.cpp2 — this is NOT allowed" linenums="1" hl_lines="5 6 9 14" #include // Cpp1 syntax #include // Cpp1 syntax -N: namespace = { // Cpp2 syntax - hello: (msg: std::string_view) = // Cpp2 syntax - std::cout << "Hello, (msg)$!\n"; // Cpp2 syntax -} // Cpp2 syntax - -int main() { // Cpp1 syntax - auto words = std::vector{ "Alice", "Bob" }; // Cpp1 syntax - N::hello( words[0] ); // Cpp1 syntax - N::hello( words[1] ); // Cpp1 syntax - std::cout << "... and goodnight\n"; // Cpp1 syntax +namespace N { // Cpp1 syntax + hello: (msg: std::string_view) = // Cpp2 syntax (NOT allowed here) + std::cout << "Hello, (msg)$!\n"; // Cpp2 syntax (NOT allowed here) } // Cpp1 syntax -``` - -When cppfront compiles such a mixed file, it just passes through the Cpp1 code as-is, and translates the Cpp2 code to Cpp1 in-place. This means that when a call site (call this the "caller") uses a type/function/object (call this the "callee") written in the same file: -- **Code written in all Cpp2 is always order-independent by default.** When a caller written in Cpp2 syntax uses a callee written in Cpp2 syntax, they can appear in either order in the file. +main: () = { // Cpp2 syntax + auto words = std::vector{ "Alice", "Bob" }; // Cpp1 syntax (NOT allowed here) + N::hello( words[0] ); // ? + N::hello( words[1] ); // ? + std::cout << "... and goodnight\n"; // ? +} // Cpp2 syntax +``` -- **Code written in Cpp1 is order-dependent as usual.** When either the caller or the callee (or both) are written in Cpp1 syntax, the callee must be declared before the caller. +> The above nesting is not supported because it would create not just parsing problems but also semantic ambiguities. For example, lines 11-13 are syntactically valid as Cpp1 or as Cpp2, but if they are treated as Cpp2 then the `words[0]` and `words[1]` expressions' `std::vector::operator[]` calls are bounds-checked and bounds-safe by default, whereas if they are treated as Cpp1 then they are not bounds-checked. And that's a pretty important difference to be sure about! ## Cppfront command line From 58e5ec671e69d998c60d834c2131b8595f476afd Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Tue, 13 Feb 2024 17:28:39 -1000 Subject: [PATCH 20/64] Overview editorial improvements --- docs/index.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/docs/index.md b/docs/index.md index 9a778dfef6..4848ac9b3d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -19,7 +19,7 @@ We can't make an improvement that large to C++ via gradual evolution to today's - **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object/namespace/module/etc. written in either syntax is always still just a normal C++ type/function/object/namespace/module/etc., so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. -**What it isn't.** Cpp2 is not a successor or alternate language with it own divergent or incompatible ecosystem. For example, it does not have its own nonstandard incompatible modules/concepts/etc. that compete with the Standard C++ features; and does not replace your Standard C++ compiler and other tools. +**What it isn't.** Cpp2 is not a successor or alternate language with its own divergent or incompatible ecosystem. For example, it does not have its own nonstandard incompatible modules/concepts/etc. that compete with the Standard C++ features; and it does not replace your Standard C++ compiler and other tools. **What it is.** Cpp2 aims to be another "skin" for C++ itself, just a simpler and safer way to write ordinary C++ types/functions/objects. It seamlessly uses Standard C++ modules and concepts requirements and other features, and it works with all existing C++20 or higher compilers and tools right out of the box with zero overhead. @@ -42,7 +42,7 @@ Cppfront builds with any recent C++ compiler. Go to the `/cppfront/source` direc cl cppfront.cpp -std:c++20 -EHsc ``` -``` bash title="GCC build instructions (g++ 10 or higher)" +``` bash title="GCC build instructions (GCC 10 or higher)" g++ cppfront.cpp -std=c++20 -o cppfront ``` @@ -87,15 +87,15 @@ This short program code already illustrates a few Cpp2 essentials. - `hello` **is a** function that takes a `std::string_view` it will only read from and that returns nothing, and is **defined as** code that prints the string to `cout` the usual C++ way. -All grammar is context-free. In particular, we (the human reading the code, or the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. For details, see [Design note: Unambiguous parsing](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unambiguous-parsing). +All grammar is context-free. In particular, we (the human reading the code, and the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. For details, see [Design note: Unambiguous parsing](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unambiguous-parsing). **Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. -- `hello` uses **string interpolation** to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. +- Declaring `words` uses **"CTAD"** (C++'s normal constructor template argument deduction) to declare its `words` variable. -- `main` uses **"CTAD"** (C++'s normal constructor template argument deduction) to declare its `words` variable. +- Calling `words[0]` and `words[1]` is **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. -- `words[0]` and `words[1]` are **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. +- `hello` uses **string interpolation** to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. **Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: @@ -109,9 +109,9 @@ For details, see [Design note: Defaults are one way to say the same thing](https **Order-independent by default.** Did you notice that `main` called `hello`, which was defined later? Cpp2 code is order-independent by default — there are no forward declarations. -**Seamless compatibility and interop.** We can just use `std::cout` and `std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using direct calls without any wrapping/marshaling/thunking. +**Seamless compatibility and interop.** We can just use `std::cout` and `std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using ordinary direct calls without any wrapping/marshaling/thunking. -**C++ standard library always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`). Cppfront is regularly updated to be compatible with C++23 and draft C++26 library additions, so as soon as you have a C++ implementation that has the new library feature, you'll be able to fully use it in Cpp2 code. +**C++ standard library always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`). Cppfront is regularly updated to be compatible with C++23 and the latest draft C++26 library additions as soon as the ISO C++ committee votes them into the C++26 working draft, so as soon as you have a C++ implementation that has a new standard (or bleeding-edge draft standard!) C++ library feature, you'll be able to fully use it in Cpp2 code. ### Building `hello.cpp2` from the command line From 335d3a73428e849604efd0fb140e11731e589941 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Tue, 13 Feb 2024 18:20:08 -1000 Subject: [PATCH 21/64] Minor cleanup --- docs/index.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/index.md b/docs/index.md index 4848ac9b3d..d9a2c1b4e5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -5,13 +5,13 @@ ### What is Cpp2? -"Cpp2," short for "C++ syntax 2," is my ([Herb Sutter's](https://github.com/hsutter)) personal project to try to make writing ordinary C++ types/functions/objects be much simpler and safer, without breaking backward compatibility. Bjarne Stroustrup said it best: +"Cpp2," short for "C++ syntax 2," is my ([Herb Sutter's](https://github.com/hsutter)) personal project to try to make writing ordinary C++ types/functions/objects be much **simpler and safer**, without breaking backward compatibility. Bjarne Stroustrup said it best: -> "Inside C++, there is a much smaller and cleaner language struggling to get out."
— Bjarne Stroustrup, _The Design and Evolution of C++_ (D&E), 1994 +> "Inside C++, there is a much smaller and cleaner language struggling to get out."
  — Bjarne Stroustrup, _The Design and Evolution of C++_ (D&E), 1994 > -> "Say 10% of the size of C++ in definition and similar in front-end compiler size. ... most of the simplification would come from generalization."
— Bjarne Stroustrup, _ACM History of Programming Languages III_, 2007 +> "Say 10% of the size of C++ in definition and similar in front-end compiler size. ... most of the simplification would come from generalization."
  — Bjarne Stroustrup, _ACM History of Programming Languages III_, 2007 -My goal is to try to prove that Stroustrup is right: that it's possible and desirable to have true C++ with all its expressive power and control and with full backward compatibility, but in a flavor that's **10x simpler** with fewer quirks and special cases to remember, and **50x safer** where it's far easier to not write security bugs by accident. +My goal is to try to prove that Stroustrup is right: that it's possible and desirable to have true C++ with all its expressive power and control and with full backward compatibility, but in a flavor that's 10x simpler with fewer quirks and special cases to remember, [^simpler] and 50x safer where it's far easier to not write security bugs by accident. We can't make an improvement that large to C++ via gradual evolution to today's syntax, because some important changes would require changing the meaning of code written in today's syntax. For example, we can never change a language feature default in today's syntax, not even if the default creates a security vulnerability pitfall, because changing a default would break vast swathes of existing code. Having a distinct alternative syntax gives us a "bubble of new code" that doesn't exist today, and have: @@ -59,7 +59,7 @@ That's it! Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program, no `#include` required: -``` cpp title="hello.cpp2, on one line" +``` cpp title="hello.cpp2 — on one line" main: () = std::cout << "Hello, world!\n"; ``` @@ -240,4 +240,6 @@ That's enough to enable builds, and the IDE just picks up the rest from the `.cp +[^simpler]: I'd ideally love to obsolete ~90% of the C++ guidance I've personally had to write and teach over the past quarter century, by removing inconsistencies and pitfalls and gotchas. I love writing C++ code... I just want it to be easier and safer by default. + [^clean-cpp1]: For presentation purposes, this documentation generally shows the `.cpp` as generated when using cppfront's `-c` (short for `-clean-cpp1`), which suppresses extra information cppfront normally emits in the `.cpp` to light up C++ tools (e.g., to let IDEs integrate cppfront error message output, debuggers step to the right lines in Cpp2 source code, and so forth). In normal use, you won't need `-c`. From 91f193e318f5658039d787fca7685e4cf5409fdf Mon Sep 17 00:00:00 2001 From: Dylam De La Torre Date: Wed, 14 Feb 2024 19:31:54 +0100 Subject: [PATCH 22/64] docs: fix some typos and confusing wording (#976) * docs: fix some typos and confusing wording * restore std::ssize --- docs/reference-cpp2.md | 12 ++++++------ docs/reference-cppfront.md | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/reference-cpp2.md b/docs/reference-cpp2.md index b3c3af60a9..4a943796f8 100644 --- a/docs/reference-cpp2.md +++ b/docs/reference-cpp2.md @@ -8,7 +8,7 @@ ### Comments -The usual `// line comments` and `/* stream comments */` are supported. For exmaple: +The usual `// line comments` and `/* stream comments */` are supported. For example: ``` cpp title="Example: Comments" // A line comment: After //, the entire rest of the line is part of the comment @@ -142,7 +142,7 @@ Types can be qualified with `const` and `*`. Types are written left-to-right, so ``` cpp title="Example: Type qualifiers" // A const pointer to a non-const pointer to a const i32 object -p: const * * const i32; // +p: const * * const i32; ``` ### Literals @@ -272,7 +272,7 @@ There are two kinds of `is`: ### `as` — safe casts and conversions -An `x as T` expression allows safe type casts. `x` must be an object or expression, and `T` must be a type. It supports both static and dynamic typing, including customization with support for the standard dynamically tyuped libraries `std::variant`, `std::optional`, and `std::any` provided in the box. For example: +An `x as T` expression allows safe type casts. `x` must be an object or expression, and `T` must be a type. It supports both static and dynamic typing, including customization with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. For example: ``` cpp title="Example: Using as" main: () = { @@ -323,7 +323,7 @@ test: (x) = { // Sample call site test(42); // Behaves as if the following function were called: - // test: (x) = { std::cout << (:std::string = "the answer") << "\n";; } + // test: (x) = { std::cout << (:std::string = "the answer") << "\n"; } // (and that's why inspect alternatives are introduced with '=') ``` @@ -542,7 +542,7 @@ skat_game: @enum type = { Consider `hearts`: It's a member object declaration, but it doesn't have a type (or a default value) which is normally illegal, but here 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). -Unlike C `enum`, this `@union` is scoped and strongly type (does not implicitly convert to the underlying type. +Unlike C `enum`, this `@enum` is scoped and strongly typed (does not implicitly convert to the underlying type. Unlike C++11 `enum class`, it's "just a `type`" which means it can naturally also have member functions and other things that a type can have: @@ -616,7 +616,7 @@ Unlike C `union`, this `@union` is safe to use because it always ensures only th Unlike C++11 `std::variant`, this `@union` is easier to use because its alternatives are anonymous, and safer to use because each union type is a distinct type. [^variant] -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: +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. Because a `@union type` is still a `type`, it can naturally have other things normal types can have, such as template parameter lists and member functions: diff --git a/docs/reference-cppfront.md b/docs/reference-cppfront.md index e0a50bd795..2e5cd39f32 100644 --- a/docs/reference-cppfront.md +++ b/docs/reference-cppfront.md @@ -32,7 +32,7 @@ When cppfront compiles such a mixed file, it just passes through the Cpp1 code a - **Code written in Cpp1 is order-dependent as usual.** When either the caller or the callee (or both) are written in Cpp1 syntax, the callee must be declared before the caller. -However, this source file is not valid, because it tries to nest Cpp2 code inside Cpp1 code, and vice versa: +However, the following source file is not valid, because it tries to nest Cpp2 code inside Cpp1 code, and vice versa: ``` cpp title="ERROR.cpp2 — this is NOT allowed" linenums="1" hl_lines="5 6 9 14" #include // Cpp1 syntax From d3299fcc8499b1d45c737b882391559c5478c75f Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Wed, 14 Feb 2024 08:56:12 -1000 Subject: [PATCH 23/64] Fix table of content display -> rhs of page --- docs/index.md | 6 +++--- mkdocs.yml | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/index.md b/docs/index.md index d9a2c1b4e5..b3b42c6272 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,7 +17,7 @@ We can't make an improvement that large to C++ via gradual evolution to today's - **Freedom to make any desired improvement, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default when using "syntax 2." Examples: Writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer. You can always "break the glass" when needed to violate the guidance, but has to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. For details, see [Design note: unsafe code](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unsafe-code). -- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object/namespace/module/etc. written in either syntax is always still just a normal C++ type/function/object/namespace/module/etc., so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. +- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object/namespace/etc. written in either syntax is always still just a normal C++ type/function/object/namespace/etc., so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. **What it isn't.** Cpp2 is not a successor or alternate language with its own divergent or incompatible ecosystem. For example, it does not have its own nonstandard incompatible modules/concepts/etc. that compete with the Standard C++ features; and it does not replace your Standard C++ compiler and other tools. @@ -79,7 +79,7 @@ hello: (msg: std::string_view) = This short program code already illustrates a few Cpp2 essentials. -**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object/namespace/module/etc. declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced **"defined as."** +**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object/namespace/etc. declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced **"defined as."** - `main` **is a** function that takes no arguments and returns nothing, and is **defined as** the code body shown. @@ -236,7 +236,7 @@ That's enough to enable builds, and the IDE just picks up the rest from the `.cp - **The `#line` directives cppfront emits in the generated `.cpp` file.** Most C++ debuggers recognize these and will know to step through the `.cpp2` file. Note that `#line` emission is on by default, but if you choose `-c` (short for `-clean-cpp1`) these will be suppressed and then the debugger will step through the generated C++ code instead. -- **Regardless of syntax, every type/function/object/namespace/module/etc. is still just an ordinary C++ type/function/object/namespace/module/etc.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. +- **Regardless of syntax, every type/function/object/namespace/etc. is still just an ordinary C++ type/function/object/namespace/etc.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. diff --git a/mkdocs.yml b/mkdocs.yml index 5f61d6c669..21d1fb13a3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -25,7 +25,6 @@ theme: - navigation.expand - navigation.instant - navigation.instant.preview - - toc.integrate - navigation.top - search.suggest - search.highlight From 37b34e3558617a7ab4be1ea8cf93be57824c753d Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Wed, 14 Feb 2024 10:29:56 -1000 Subject: [PATCH 24/64] Add section navigation Reorganize sources Fix long code lines to avoid horizontal scroll bars --- docs/cpp2.md | 6 + docs/cpp2/aliases.md | 19 + docs/cpp2/common.md | 147 ++++ docs/cpp2/declarations.md | 63 ++ docs/cpp2/expressions.md | 140 ++++ docs/cpp2/functions-and-variables.md | 55 ++ docs/cpp2/metafunctions.md | 216 ++++++ docs/cpp2/modules.md | 16 + docs/cpp2/namespaces.md | 13 + docs/cpp2/types.md | 110 +++ docs/cppfront.md | 5 + docs/cppfront/mixed.md | 59 ++ .../options.md} | 102 +-- docs/index.md | 2 +- docs/reference-cpp2.md | 687 ------------------ mkdocs.yml | 20 +- 16 files changed, 891 insertions(+), 769 deletions(-) create mode 100644 docs/cpp2.md create mode 100644 docs/cpp2/aliases.md create mode 100644 docs/cpp2/common.md create mode 100644 docs/cpp2/declarations.md create mode 100644 docs/cpp2/expressions.md create mode 100644 docs/cpp2/functions-and-variables.md create mode 100644 docs/cpp2/metafunctions.md create mode 100644 docs/cpp2/modules.md create mode 100644 docs/cpp2/namespaces.md create mode 100644 docs/cpp2/types.md create mode 100644 docs/cppfront.md create mode 100644 docs/cppfront/mixed.md rename docs/{reference-cppfront.md => cppfront/options.md} (51%) delete mode 100644 docs/reference-cpp2.md diff --git a/docs/cpp2.md b/docs/cpp2.md new file mode 100644 index 0000000000..f182facee7 --- /dev/null +++ b/docs/cpp2.md @@ -0,0 +1,6 @@ + +# Cpp2 reference + +### See also: **[Hello, world!](index.md/#hello-world)** + +TODO diff --git a/docs/cpp2/aliases.md b/docs/cpp2/aliases.md new file mode 100644 index 0000000000..b81d2d7fca --- /dev/null +++ b/docs/cpp2/aliases.md @@ -0,0 +1,19 @@ +# Aliases + +## Namespace aliases + +TODO + +## Type aliases + +TODO + +## Function aliases + +TODO + +## Object aliases + +TODO + + diff --git a/docs/cpp2/common.md b/docs/cpp2/common.md new file mode 100644 index 0000000000..7a976e9be7 --- /dev/null +++ b/docs/cpp2/common.md @@ -0,0 +1,147 @@ +# Common programming concepts + +## Comments + +The usual `// line comments` and `/* stream comments */` are supported. For example: + +``` cpp title="Example: Comments" +// A line comment: After //, the entire +// rest of the line is part of the comment + +/* + A stream comment: After /*, everything until the + next * / (without a space between) is part of the + comment. Note that stream comments do not nest. + */ +``` + +## Fundamental data types + +Cpp2 supports the same fundamental types as today's Cpp1, but additionally provides the following aliases in namespace `cpp2`: + +| Fixed-width types | Synonym for | +|---|---| +| `i8` | `std::int8_t` | +| `i16` | `std::int16_t` | +| `i32` | `std::int32_t` | +| `i64` | `std::int64_t` | +| `u8` | `std::uint8_t` | +| `u16` | `std::uint16_t` | +| `u32` | `std::uint32_t` | +| `u64` | `std::uint64_t` | + +| Variable-width types
(Cpp2-compatible single-word names) | Synonym for (these multi-word
names are not allowed in Cpp2) | +|---|---| +| `ushort` | `unsigned short` | +| `uint` | `unsigned int` | +| `ulong` | `unsigned long` | +| `longlong` | `long long` | +| `ulonglong` | `unsigned long long` | +| `longdouble` | `long double` | + +| For compatibility/interop only,
so deliberately ugly names | Synonym for | Notes | +|---|---|---| +| `_schar` | `signed char` | Normally, prefer `i8` instead | +| `_uchar` | `unsigned char` | Normally, prefer `u8` instead | + +## Type qualifiers + +Types can be qualified with `const` and `*`. Types are written left-to-right, so a qualifier always applies to what immediately follows it. For example, to declare a `const` pointer to a non-`const` pointer to a `const i32` object, write: + +``` cpp title="Example: Type qualifiers" +// A const pointer to a non-const pointer to a const i32 object +p: const * * const i32; +``` + +## Literals + +Cpp2 supports the same `'c'`haracter, `"string"`, binary, integer, and floating point literals as Cpp1, including most Unicode encoding prefixes and raw string literals. + +Cpp2 supports using Cpp1 user-defined literals for compatibility, to support seamlessly using existing libraries. However, because Cpp2 has unified function call syntax (UFCS), the preferred way to author the equivalent in Cpp2 is to just write a function or type name as a `.` call suffix. For example: + +- You can create a `u8` value by writing either `u8(123)` or **`123.u8()`**. [^u8using] + +- You can write a 'constexpr' function like `nm: (value: i64) -> my_nanometer_type == { /*...*/ }` that takes an integer and returns a value of a strongly typed "nanometer" type, and then create a `nm` value by writing either `nm(123)` or **`123.nm()`**. + +Both **`123.n()`** and **`123.u8()`** are very similar to user-defined literal syntax, and more general. + +## Operators + +Operators have the same precedence and associativity as in Cpp1, but some unary operators that are prefix (always or sometimes) in Cpp1 are postfix (always) in Cpp2. + +## Unary operators + +The operators `!`, `+`, and `-` are prefix, as in Cpp1. For example: + +``` cpp title="Example: Prefix operators" +if !vec.empty() { + vec.emplace_back( -123.45 ); +} +``` + +| Unary operator | Cpp2 example | Cpp1 equivalent | +|---|---|---| +| `!` | `!vec.empty()` | `!vec.empty()` | +| `+` | `+100` | `+100` | +| `-` | `-100` | `-100` | + +The operators `.`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, and `$` are postfix. For example: + +``` cpp title="Example: Postfix operators" +// Cpp1 examples, from cppfront's own source code: +// address = &(*tokens)[pos + num]; +// is_void = *(*u)->identifier == "void"; +// Cpp2 equivalents: + address = tokens*[pos + num]&; + is_void = u**.identifier* == "void"; +``` + +Postfix notation lets the code read fluidly left-to-right, in the same order in which the operators will be applied, and lets declaration syntax be consistent with usage syntax. For more details, see [Design note: Postfix operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators). + +> Note: The function call syntax `f(x)` calls a namespace-scope function, or a function object, named `f`. The function call syntax `x.f()` is a unified function call syntax (aka UFCS) that calls a type-scope function in the type of `x` if available, otherwise calls the same as `f(x)`. For details, see [Design note: UFCS](https://github.com/hsutter/cppfront/wiki/Design-note%3A-UFCS). + +| Unary operator | Cpp2 example | Cpp1 equivalent | +|---|---|---| +| `.` | `obj.f()` | `obj.f()` | +| `*` | `pobj*.f()` | `(*pobj).f()` or `pobj->f()` | +| `&` | `obj&` | `&obj` | +| `~` | `val~` | `~val` | +| `++` | `iter++` | `++iter` | +| `--` | `iter--` | `--iter` | +| `(` `)` | `f( 1, 2, 3)` | `f( 1, 2, 3)` | +| `[` `]` | `vec[123]` | `vec[123]` | +| `$` | `val$` | (reflection — no C++23 equivalent) | + +> Because `++` and `--` always have in-place update semantics, we never need to remember "use prefix `++`/`--` unless you need a copy of the old value." If you do need a copy of the old value, just take the copy before calling `++`/`--`. + +Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~` are used as binary operators they must be preceded by whitespace. For example: + +| Unary postfix operators that
are also binary operators | Cpp2 example | Cpp1 equivalent | +|---|---|---| +| `*` | `pobj* * 42` | `(*pobj)*42` | +| `&` | `obj& & mask`

(note: allowed in unsafe code only) | `&obj & mask` | +| `~` | `~val ~ bitcomplement` | `val~ ~ bitcomplement` | + +For more details, see [Design note: Postfix unary operators vs binary operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-unary-operators-vs-binary-operators). + +## Binary operators + +Binary operators are the same as in Cpp1. From highest to lowest precedence: + +| Binary operators grouped by precedence | +|---| +| `*`, `/`, `%` | +| `+`, `-` | +| `<<`, `>>` | +| `<=>` | +| `<`, `>`, `<=`, `>=` | +| `==`, `!=` | +| `&` | +| `^` | +| `|` | +| `&&` | +| `||` | +| `=` and compound assignment | + + +[^u8using]: Or `123.cpp2::u8()` if you aren't `using` the namespace or that specific name. diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md new file mode 100644 index 0000000000..ff3b57a889 --- /dev/null +++ b/docs/cpp2/declarations.md @@ -0,0 +1,63 @@ +# Declaration syntax + +## Unified declaration syntax + +All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. + +- The `:` is pronounced **"is a."** +- The `=` is pronounced **"defined as."** +- The _statement_ is typically an expression statement (e.g., `a + b();`) or a compound statement (e.g., `{ /*...*/ return c(d) / e; }`). +- Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted entirely to accept a default (e.g., `x: int = 0;` can be equivalently written `x: _ = 0;` or `x := 0;` both of which deduce the type). + + +## Examples + +``` cpp title="Example: Consistent declarations — name : kind = statement" +// n is a namespace defined as the following scope +n: namespace += { + // shape is a type defined as the following scope + shape: type + = { + // points is an object of type std::vector, + // defined as having an empty default value + // (type-scope objects are private by default) + points: std::vector = (); + + // draw is a function taking 'this' and 'canvas' parameters + // and returning bool, defined as the following body + // (type-scope functions are public by default) + // - this is as if 'this: shape', an object of type shape + // - where is an object of type canvas + draw: (this, where: canvas) -> bool + = { + // pen is an object of deduced (omitted) type 'color', + // defined as having initial value 'color::red' + pen := color::red; + + // success is an object of deduced (omitted) type bool, + // defined as having initial value 'false' + success := false; + + // ... + + return success; + } + + // count is a function taking 'this' and returning a type + // deduced from its body, defined as a single-expression body + count: (this) = points.ssize(); + + // ... + } + + // color is an @enum type (described later) + color: @enum type = { red; green; blue; } +} +``` + + +## `requires` constraints + +TODO + diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md new file mode 100644 index 0000000000..4363722076 --- /dev/null +++ b/docs/cpp2/expressions.md @@ -0,0 +1,140 @@ + +# Common expressions + +## `_` — the "don't care" wildcard, including explicit discard + +`_` is pronounced **"don't care"** and allowed as a wildcard in most contexts. For example: + +``` cpp title="Example: _ wildcard" +// We don't care about the guard variable's name +_ : std::lock_guard = mut; + +// If we don't care to write the variable's type, deduce it +x : _ = 42; + // in cases like this, _ can be omitted... + // this is equivalent to "x := 42;" + +return inspect v -> std::string { + is std::vector = "v is a std::vector"; + is _ = "unknown"; // don't care what else, match anything +}; +``` + +Cpp2 treats all function outputs (return values, and results produced via `inout` and `out` parameters) as important, and does not let them be silently discarded by default. To explicitly discard such a value, assign it to `_`. For example: + +``` cpp title="Example: Using _ for explicit discard" +_ = vec.emplace_back(1,2,3); + // "_ =" is required to explicitly discard emplace_back's + // return value (which is non-void since C++17) + +{ + x := my_vector.begin(); + std::advance(x, 2); + _ = x; // required to explicitly discard x's new value, + // because std::advance modifies x's value +} +``` + +For details, see [Design note: Explicit discard](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Explicit-discard). In Cpp2, 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. + + +## `is` — safe type/value queries + +An `x is C` expression allows safe type and value queries, and evaluates to `true` if `x` matches constraint `C`. It supports both static and dynamic queries, including customization with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. + +There are two kinds of `is`: + +- A **type query**, where `C` is a type constraint: A type, a template name, a concept, or a type predicate. Here `x` may be a type, or an object or expression; if it is an object or expression, the query refers to `x`'s type. + +| Type constraint kind | Example | +|---|---| +| Static type query | `x is int` | +| Dynamic type query | `ptr* is Shape` | +| Static template type query | `x is std::vector` | +| Static concept query | `x is std::integral` | + +- A **value query**, where `C` is a value constraint: A value, or a value predicate. Here `x` must be an object or expression. + +| Value constraint kind | Example | +|---|---| +| Value | `x is 0` | +| Value predicate | `x is (in(10, 20))` | + +`is` is useful throughout the language, including in `inspect` pattern matching alternatives. `is` is extensible, and works out of the box with `std::variant`, `std::optional`, and `std::any`. For examples, see: + +- [`mixed-inspect-templates.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-templates.cpp2) +- [`mixed-inspect-values.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-values.cpp2) +- [`mixed-inspect-values-2.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-values-2.cpp2) +- [`mixed-type-safety-1.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-type-safety-1.cpp2) +- [`pure2-enum.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-enum.cpp2) +- [`pure2-inspect-expression-in-generic-function-multiple-types.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-inspect-expression-in-generic-function-multiple-types.cpp2) +- [`pure2-inspect-fallback-with-variant-any-optional.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-inspect-fallback-with-variant-any-optional.cpp2) +- [`pure2-type-safety-1.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-type-safety-1.cpp2) +- [`pure2-type-safety-2-with-inspect-expression.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-type-safety-2-with-inspect-expression.cpp2) + + +## `as` — safe casts and conversions + +An `x as T` expression allows safe type casts. `x` must be an object or expression, and `T` must be a type. It supports both static and dynamic typing, including customization with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. For example: + +``` cpp title="Example: Using as" +main: () = { + a: std::any = 0; // a's type is now int, value 0 + test(a); // prints "zero" + a = "plugh" as std::string; // a's type is now std::string, value "plugh" + test(a); // prints "plugh" + test("xyzzy" as std::string); // prints "xyzzy" +} + +// A generic function that takes an argument 'x' of any type, +// same as "void test( auto const& x )" in C++20 syntax +test: (x) = { + std::cout << inspect x -> std::string { + is 0 = "zero"; + is std::string = x as std::string; + is _ = "(no match)"; + } << "\n"; +} +``` + +## `inspect` — pattern matching + +An `inspect expr -> Type` expression allows pattern matching using `is`. + +- `expr` is evaluated once. +- Each alternative spelled `is C` is evaluated in order as if called with `expr is C`. +- If an alternative evaluates to `true`, then its `= alternative;` body is used as the value of the entire `inspect` expression, and the meaning is the same as if the entire `inspect` expression had been written as just `:Type = alternative;` — i.e., an unnamed object expression (aka 'temporary object') of type `Type` initialized with `alternative`. +- A catchall `is _` is required. + +For example: + +``` cpp title="Example: Using inspect" +// A generic function that takes an argument 'x' of any type +// and inspects various things about `x` +test: (x) = { + forty_two := 42; + std::cout << inspect x -> std::string { + is 0 = "zero"; // == 0 + is (forty_two) = "the answer"; // == 42 + is int = "integer"; // is type int (and not 0 or 42) + is std::string = x as std::string; // is type std::string + is std::vector = "a std::vector"; // is a vector + is _ = "(no match)"; // is something else + } << "\n"; +} + +// Sample call site +test(42); + // Behaves as if the following function were called: + // test: (x) = { std::cout << (:std::string = "the answer") << "\n"; } + // (and that's why inspect alternatives are introduced with '=') +``` + +For more examples, see also the examples in the previous two sections on `is` and `as`, many of which use `inspect`. + + +## `$` — captures, including interpolations + +TODO + +For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). diff --git a/docs/cpp2/functions-and-variables.md b/docs/cpp2/functions-and-variables.md new file mode 100644 index 0000000000..da8ac5076e --- /dev/null +++ b/docs/cpp2/functions-and-variables.md @@ -0,0 +1,55 @@ + +# Functions and variables + +## Overview + +TODO + +## Parameter passing + +TODO + +All parameters and other objects in Cpp2 are `const` by default, except for local variables. For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). + +## Control flow + +## `if`, `else` — Branches + +TODO + +## `for`, `while`, `do` — Loops + +TODO + +Loops can be named using the usual **name `:`** name introduction syntax, and `break` and `continue` can refer to those names. For example: + +``` cpp title="Example: Writing a simple type" +outer: while i` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions: + +``` cpp title="Example: Using the value metafunction when writing a type" +point2d: @value type = { + x: i32 = 0; + y: i32 = 0; + // @value automatically generates default/copy/move + // construction/assignment and <=> strong_ordering comparison, + // and emits an error if you try to write a non-public + // destructor or any protected or virtual function +} +``` + +## Generating source code at compile time + +TODO + + +## Built-in metafunctions + +The following metafunctions are provided in the box with cppfront. + +### interface + +TODO + + +### polymorphic_base + +TODO + + +### ordered, weakly_ordered, partially_ordered + +TODO + + +### copyable + +TODO + + +### basic_value, value, weakly_ordered_value, partially_ordered_value + +TODO + + +### struct + +TODO + + +### `enum`, `flag_enum` + +Cpp2 has no `enum` feature hardwired into the language. Instead you apply the `@enum` metafunction when writing an ordinary `type`: + +``` cpp title="Example: Using @enum" +// 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 here 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). + +Unlike C `enum`, this `@enum` is scoped and strongly typed (does not implicitly convert to the underlying type. + +Unlike C++11 `enum class`, it's "just a `type`" which means it can naturally also have member functions and other things that a type can have: + +``` cpp title="Example: An @enum type with a member function" +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: + +``` cpp title="Example: Using @flag_enum" +// 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; +} +``` + + +### `union` + +`@union` declaratively opts into writing a safe discriminated union/variant dynamic type. For example: + +``` cpp title="Example: Using @union" +// 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; +} + +main: () = { + x: name_or_number = (); + + x.set_name("xyzzy"); // now x is a string + std::cout << x.name(); // prints the string + + // trying to use x.num() here would cause a Type safety + // contract violation, because x is currently a string + + x.set_num( 120 ); // now x is a number + std::cout << x.num() + 3; // prints 123 +} +``` + +Unlike C `union`, this `@union` is safe to use because it always ensures only the active type is accessed. + +Unlike C++11 `std::variant`, this `@union` is easier to use because its alternatives are anonymous, and safer to use because each union type is a distinct type. [^variant] + +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. + +Because a `@union type` is still a `type`, it can naturally have other things normal types can have, such as template parameter lists and member functions: + +``` cpp title="Example: A templated custom safe union type" +name_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" here, but is legal + // whichever alternative is active +} +``` + +### cpp1_rule_of_zero + +TODO + +### print + +TODO + + +## Writing your own metafunctions + +TODO + + +## Reflection API reference + +TODO + + +[^variant]: With `variant`, 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. diff --git a/docs/cpp2/modules.md b/docs/cpp2/modules.md new file mode 100644 index 0000000000..9d91848ca6 --- /dev/null +++ b/docs/cpp2/modules.md @@ -0,0 +1,16 @@ + +# Modules + +## Overview + +TODO + +## `import` + +TODO + +## `export` + +TODO + + diff --git a/docs/cpp2/namespaces.md b/docs/cpp2/namespaces.md new file mode 100644 index 0000000000..8058cacc81 --- /dev/null +++ b/docs/cpp2/namespaces.md @@ -0,0 +1,13 @@ + +# Namespaces + +## Overview + +TODO + +For details, see [Design note: Namespaces](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Namespaces). + + +## `using` + +TODO diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md new file mode 100644 index 0000000000..2b650ed807 --- /dev/null +++ b/docs/cpp2/types.md @@ -0,0 +1,110 @@ + +# Types + +## Overview + +A user-defined `type` is written using the same **name `:` kind `=` value** syntax as everything in Cpp2. The type's "value" is a `{}`-enclosed body containing more declarations. + +In a `type`, data members are private by default, and functions and nested types are public by default. To explicitly declare a type scope declaration `public`, `protected`, or `private`, write that keyword at the beginning of the declaration. + +``` cpp title="Example: Writing a simple type" +mytype: type = +{ + // data members are private by default + x: std::string; + + // functions are public by default + protected f: (this) = { do_something_with(x); } + + // ... +} +``` + +## `this` — Parameters + +The `this` parameter is explicit, and has special sauce: + +- `this` is a synonym for the current object (not a pointer). +- Inside a type scope function, writing `this.` before a member name is optional. +- `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` always implies 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 read-only member function named `print` that takes a read-only string value and prints this object's data value and the string message: + +``` cpp title="Example: this" +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" + } + + // ... +} +``` + +## `this` — Inheritance + +Base types are written as members named this. For example, just as a type could write a data member as `data: string = "xyzzy";`, which is pronounced "`data` is a `string` defined as having the default value `"xyzzy"`, a base type is written as `this: Shape = (default, values);`, which is pronounced "`this` is a `Shape` defined as having these default values." + +> Cpp2 syntax has no separate base 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 subobject, so that it naturally outlives the base subobject. + +> Cpp2 code doesn't need workarounds like Boost's `base_from_member`, because all of the motivating examples for that can be written directly. See my comments on [cppfront issue #334](https://github.com/hsutter/cppfront/issues/334) for details. + +## `virtual`, `override`, and `final` — Virtual functions + +Virtual functions are written by specifying exactly one of `virtual`, `override`, or `final` on the `this` parameter. A pure virtual function is a function with a `virtual this` parameter and no body. For example: + +``` cpp title="Example: Virtual functions" +abstract_base: type += { + // A pure virtual function: virtual + no body + print: (virtual this, msg: std::string); + + // ... +} + +derived: type += { + // 'this' is-an 'abstract_base' + this: abstract_base; + + // Explicit override + print: (override this, msg: std::string); + + // ... +} +``` + +## `operator=` — Construction, assignment, and destruction + +All value operations are spelled `operator=`, including construction, assignment, and destruction. All default to memberwise semantics and safe "explicit" by default. A special `that` parameter makes writing copy/move/conversion in particular simpler and safer. For details, see [Design note: operator=, this & that](https://github.com/hsutter/cppfront/wiki/Cpp2:-operator=,-this-&-that). 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). + +> 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" for the purpose of suppressing unwanted generated functions. + +- The most general form of `operator=` is `operator=: (out this, that)`. It 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 above for details). + +- All copy/move/comparison operator= functions are memberwise by default in Cpp2, including memberwise construction and assignment when you write them yourself. + +- 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. + +## `operator<=>` — Unified comparisons + +Most of Cpp2's `operator<=>` has already been merged into ISO C++, except for allowing chained comparisons. In Cpp2, comparisons can be safely chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: + +- **Valid chains: All `<`/`<=`, all `>`/`>=`, or all `==`.** All mathematically sound and safe chains like `a <= b < c` are supported, with efficient single evaluation of each term. They are "sound" because they are transitive; these chains imply a relationship between `a` and `c` (in this case, the chain implies that `a <= c` is also true). + +- **Invalid chains: Everything else.** Nonsense chains like `a >= b < c` and `a != b != c` are compile time errors. They are "nonsense" because they are non-transitive; these chains do not imply any relationship between `a` and `c`. + +For details, see [P0515 "Consistent comparison" section 3.3](https://wg21.link/p0515) and [P0893 "Chaining comparisons"](https://wg21.link/p0893). + diff --git a/docs/cppfront.md b/docs/cppfront.md new file mode 100644 index 0000000000..8553d0dc58 --- /dev/null +++ b/docs/cppfront.md @@ -0,0 +1,5 @@ + +# Cppfront reference + +TODO + diff --git a/docs/cppfront/mixed.md b/docs/cppfront/mixed.md new file mode 100644 index 0000000000..9aac7e674e --- /dev/null +++ b/docs/cppfront/mixed.md @@ -0,0 +1,59 @@ +# Mixing Cpp1 (today's C++) and Cpp2 in the same source file + +## Compiling a source file that contains both Cpp1 and Cpp2 code + +Cppfront compiles a `.cpp2` file and produces a `.cpp` file to be compiled by your favorite C++20 or higher C++ compiler. + +The same `.cpp2` file may contain both Cpp2 syntax and today's "Cpp1" C++ syntax, **side by side but not nested**. + +When cppfront compiles such a mixed file, it just passes through the Cpp1 code as-is, and translates the Cpp2 code to Cpp1 in-place. This means that when a call site (call this the "caller") uses a type/function/object (call this the "callee") written in the same file: + +- **Code written in all Cpp2 is always order-independent by default.** When a caller written in Cpp2 syntax uses a callee written in Cpp2 syntax, they can appear in either order in the file. + +- **Code written in Cpp1 is order-dependent as usual.** When either the caller or the callee (or both) are written in Cpp1 syntax, the callee must be declared before the caller. + + +## Okay: Cpp1 and Cpp2 side by side, interleaved + +For example, this source file is fine, where the Cpp2 and Cpp1 code are side by side and seamlessly call each other directly as usual: + +``` cpp title="mixed.cpp2 — Mixing Cpp1 and Cpp2 code side by side in the same source file is okay" linenums="1" hl_lines="4-7" +#include // Cpp1 +#include // Cpp1 + +N: namespace = { // Cpp2 + hello: (msg: std::string_view) = // Cpp2 + std::cout << "Hello, (msg)$!\n"; // Cpp2 +} // Cpp2 + +int main() { // Cpp1 + auto words = std::vector{ "Alice", "Bob" }; // Cpp1 + N::hello( words[0] ); // Cpp1 + N::hello( words[1] ); // Cpp1 + std::cout << "... and goodnight\n"; // Cpp1 +} // Cpp1 +``` + +## Not allowed: Nesting Cpp1 inside Cpp2 (and vice versa) + +However, the following source file is not valid, because it tries to nest Cpp2 code inside Cpp1 code, and vice versa: + +``` cpp title="ERROR.cpp2 — this is NOT allowed" linenums="1" hl_lines="5 6 9 14" +#include // Cpp1 +#include // Cpp1 + +namespace N { // Cpp1 + hello: (msg: std::string_view) = // Cpp2 (ERROR here) + std::cout << "Hello, (msg)$!\n"; // Cpp2 (ERROR here) +} // Cpp1 + +main: () = { // Cpp2 + auto words = std::vector{ "Alice", "Bob" }; // Cpp1 (ERROR here) + N::hello( words[0] ); // ? + N::hello( words[1] ); // ? + std::cout << "... and goodnight\n"; // ? +} // Cpp2 +``` + +The above nesting is not supported because it would create not just parsing problems but also semantic ambiguities. For example, lines 11-13 are syntactically valid as Cpp1 or as Cpp2, but if they are treated as Cpp2 then the `words[0]` and `words[1]` expressions' `std::vector::operator[]` calls are bounds-checked and bounds-safe by default, whereas if they are treated as Cpp1 then they are not bounds-checked. And that's a pretty important difference to be sure about! + diff --git a/docs/reference-cppfront.md b/docs/cppfront/options.md similarity index 51% rename from docs/reference-cppfront.md rename to docs/cppfront/options.md index 2e5cd39f32..d3cd967aeb 100644 --- a/docs/reference-cppfront.md +++ b/docs/cppfront/options.md @@ -1,60 +1,4 @@ - -# Cppfront reference - -## Mixing Cpp1 (today's C++) and Cpp2 - -Cppfront compiles a `.cpp2` file and produces a `.cpp` file to be compiled by your favorite C++20 or higher C++ compiler. - -The same `.cpp2` file may contain both Cpp2 syntax and today's "Cpp1" C++ syntax, **side by side but not nested**. - -For example, this source file is fine, where the Cpp2 and Cpp1 code are side by side and seamlessly call each other directly as usual: - -``` cpp title="mixed.cpp2 — Mixing Cpp1 and Cpp2 code side by side in the same source file is okay" linenums="1" hl_lines="4-7" -#include // Cpp1 syntax -#include // Cpp1 syntax - -N: namespace = { // Cpp2 syntax - hello: (msg: std::string_view) = // Cpp2 syntax - std::cout << "Hello, (msg)$!\n"; // Cpp2 syntax -} // Cpp2 syntax - -int main() { // Cpp1 syntax - auto words = std::vector{ "Alice", "Bob" }; // Cpp1 syntax - N::hello( words[0] ); // Cpp1 syntax - N::hello( words[1] ); // Cpp1 syntax - std::cout << "... and goodnight\n"; // Cpp1 syntax -} // Cpp1 syntax -``` - -When cppfront compiles such a mixed file, it just passes through the Cpp1 code as-is, and translates the Cpp2 code to Cpp1 in-place. This means that when a call site (call this the "caller") uses a type/function/object (call this the "callee") written in the same file: - -- **Code written in all Cpp2 is always order-independent by default.** When a caller written in Cpp2 syntax uses a callee written in Cpp2 syntax, they can appear in either order in the file. - -- **Code written in Cpp1 is order-dependent as usual.** When either the caller or the callee (or both) are written in Cpp1 syntax, the callee must be declared before the caller. - -However, the following source file is not valid, because it tries to nest Cpp2 code inside Cpp1 code, and vice versa: - -``` cpp title="ERROR.cpp2 — this is NOT allowed" linenums="1" hl_lines="5 6 9 14" -#include // Cpp1 syntax -#include // Cpp1 syntax - -namespace N { // Cpp1 syntax - hello: (msg: std::string_view) = // Cpp2 syntax (NOT allowed here) - std::cout << "Hello, (msg)$!\n"; // Cpp2 syntax (NOT allowed here) -} // Cpp1 syntax - -main: () = { // Cpp2 syntax - auto words = std::vector{ "Alice", "Bob" }; // Cpp1 syntax (NOT allowed here) - N::hello( words[0] ); // ? - N::hello( words[1] ); // ? - std::cout << "... and goodnight\n"; // ? -} // Cpp2 syntax -``` - -> The above nesting is not supported because it would create not just parsing problems but also semantic ambiguities. For example, lines 11-13 are syntactically valid as Cpp1 or as Cpp2, but if they are treated as Cpp2 then the `words[0]` and `words[1]` expressions' `std::vector::operator[]` calls are bounds-checked and bounds-safe by default, whereas if they are treated as Cpp1 then they are not bounds-checked. And that's a pretty important difference to be sure about! - - -## Cppfront command line +# Cppfront command line options Cppfront is invoked using @@ -74,13 +18,13 @@ For convenience, you can shorten the name to any unique prefix not shared with a - `-import-std` and `-include-std` can be shortened to `-im` and `-in` respectively, but not `-i` which would be ambiguous with each other. -## Basic command line options +# Basic command line options -### `-help`, `-h`, `-?` +## `-help`, `-h`, `-?` Prints an abbreviated version of this documentation page. -### `-import-std`, `-im` +## `-import-std`, `-im` Makes the entire C++ standard library (namespace `std::`) available via a module `import std.compat;` (which implies `import std;`). @@ -90,26 +34,26 @@ This option is implicitly set if `-pure-cpp2` is selected. This option is ignored if `-include-std` is selected. If your Cpp1 compiler does not yet support standard library modules `std` and `std.compat`, this option automatically uses `-include-std` instead as a fallback. -### `-include-std`, `-in` +## `-include-std`, `-in` Makes the entire C++ standard library (namespace `std::`) available via an '#include" of every standard header. This option should always work with all standard headers, including draft-standard C++26 headers that are not yet in a published standard, because it tracks new headers as they are added and uses feature tests to not include headers that are not yet available on your Cpp1 implementation. -### `-pure-cpp2`, `-p` +## `-pure-cpp2`, `-p` Allow Cpp2 syntax only. This option also sets `-import-std`. -### `-version`, `-vers` +## `-version`, `-vers` Print version, build, copyright, and license information. -## Additional dynamic safety checks and contract information +# Additional dynamic safety checks and contract information -### `-add-source-info`, `-a` +## `-add-source-info`, `-a` Enable `source_location` information for contract checks. If this is supported by your Cpp1 compiler, the default contract failure messages will include exact file/line/function information. For example, if the default `Bounds` violation handler would print this without `-a`: @@ -119,60 +63,60 @@ then it would print something like this with `-a` (the exact text will vary with demo.cpp2(4) int __cdecl main(void): Bounds safety violation: out of bounds access attempt detected - attempted access at index 2, [min,max] range is [0,1] -### `-no-comparison-checks`, `-no-c` +## `-no-comparison-checks`, `-no-c` Disable mixed-sign comparison safety checks. If not disabled, mixed-sign comparisons are diagnosed by default. -### `-no-null-checks`, `-no-n` +## `-no-null-checks`, `-no-n` Disable null safety checks. If not disabled, null dereference checks are performed by default. -### `-no-subscript-checks`, `-no-s` +## `-no-subscript-checks`, `-no-s` Disable subscript bounds safety checks. If not disabled, subscript bounds safety checks are performed by default. -## Support for constrained target environments +# Support for constrained target environments -### `-fno-exceptions`, `-fno-e` +## `-fno-exceptions`, `-fno-e` Disable C++ exception handling. This should be used only if you must run in an environment that bans C++ exception handling, and so you are already using a similar command line option for your Cpp1 compiler. If this option is selected, a failed `as` for `std::variant` will assert. -### `-fno-rtti`, `-fno-r` +## `-fno-rtti`, `-fno-r` Disable C++ run-time type information (RTTI). This should be used only if you must run in an environment that bans C++ RTTI, and so you are already using a similar command line option for your Cpp1 compiler. If this option is selected, trying to using `as` for `*` (raw pointers) or `std::any` will assert. -## Other options +# Other options -### `-clean-cpp1`, `-c` +## `-clean-cpp1`, `-c` Emit clean `.cpp` files without `#line` directives and other extra information that cppfront normally emits in the `.cpp` to light up C++ tools (e.g., to let IDEs integrate cppfront error message output, debuggers step to the right lines in Cpp2 source code, and so forth). In normal use, you won't need `-c`. -### `-debug`, `-d` +## `-debug`, `-d` Emit compiler debug output. This is only useful when debugging cppfront itself. -### `-emit-cppfront-info`, `-e` +## `-emit-cppfront-info`, `-e` Emit cppfront version and build in the `.cpp` file. -### `-format-colon-errors`, `-fo` +## `-format-colon-errors`, `-fo` Emit cppfront diagnostics using `:line:col:` format for line and column numbers, if that is the format better recognized by your IDE, so that it will pick up cppfront messages and integrate them in its normal error message output location. If not set, by default cppfront diagnostics use `(line,col)` format. -### `-line-paths`, `-l` +## `-line-paths`, `-l` Emit absolute paths in `#line` directives. -### `-output` _filename_, `-o` _filename_ +## `-output` _filename_, `-o` _filename_ Output to 'filename' (can be 'stdout'). If not set, the default output filename for is the same as the input filename without the `2` (e.g., compiling `hello.cpp2` by default writes its output to `hello.cpp`, and `header.h2` to `header.h`). -### `-verbose`, `-verb` +## `-verbose`, `-verb` Print verbose statistics and `-debug` output. diff --git a/docs/index.md b/docs/index.md index b3b42c6272..f6760f4733 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,5 +1,5 @@ -# Overview & getting started +# Welcome & getting started ## What are Cpp2 and cppfront? diff --git a/docs/reference-cpp2.md b/docs/reference-cpp2.md deleted file mode 100644 index 4a943796f8..0000000000 --- a/docs/reference-cpp2.md +++ /dev/null @@ -1,687 +0,0 @@ - -# Cpp2 reference - -### See also: **[Hello, world!](index.md/#hello-world)** - - -## Common programming concepts - -### Comments - -The usual `// line comments` and `/* stream comments */` are supported. For example: - -``` cpp title="Example: Comments" -// A line comment: After //, the entire rest of the line is part of the comment - -/* - A stream comment: After /*, everything until the next * / (without a space between) - is part of the comment. Note that stream comments do not nest. - */ -``` - -### Declarations - -All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. - -- The `:` is pronounced **"is a."** -- The `=` is pronounced **"defined as."** -- The _statement_ is typically an expression statement (e.g., `a + b();`) or a compound statement (e.g., `{ /*...*/ return c(d) / e; }`). -- Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted entirely to accept a default (e.g., `x: int = 0;` can be equivalently written `x: _ = 0;` or `x := 0;` both of which deduce the type). - -For example: - -``` cpp title="Example: Consistent declarations — name : kind = statement" -// n is a namespace defined as the following scope -n: namespace -= { - // shape is a type defined as the following scope - shape: type - = { - // points is an object of type std::vector, - // defined as having an empty default value - // (type-scope objects are private by default) - points: std::vector = (); - - // draw is a function taking 'this' and 'canvas' parameters - // and returning bool, defined as the following body - // (type-scope functions are public by default) - // - this is as if 'this: shape', an object of type shape - // - where is an object of type canvas - draw: (this, where: canvas) -> bool - = { - // pen is an object of deduced (omitted) type 'color', - // defined as having initial value 'color::red' - pen := color::red; - - // success is an object of deduced (omitted) type bool, - // defined as having initial value 'false' - success := false; - - // ... - - return success; - } - - // count is a function taking 'this' and returning a type - // deduced from its body, defined as a single-expression body - count: (this) = points.ssize(); - - // ... - } - - // color is an @enum type (described later) - color: @enum type = { red; green; blue; } -} -``` - -### The `_` wildcard, including explicit discard - -`_` is pronounced **"don't care"** and allowed as a wildcard in most contexts. For example: - -``` cpp title="Example: _ wildcard" -_ : std::lock_guard = mut; // don't care about the guard variable's name - -x : _ = 42; // don't care to write the variable's type, deduce it - // in cases like this, _ can be omitted... this is equivalent to "x := 42;" - -return inspect v -> std::string { - is std::vector = "v is a std::vector"; - is _ = "unknown"; // don't care what else, match anything -}; -``` - -Cpp2 treats all function outputs (return values, and results produced via `inout` and `out` parameters) as important, and does not let them be silently discarded by default. To explicitly discard such a value, assign it to `_`. For example: - -``` cpp title="Example: Using _ for explicit discard" -_ = vec.emplace_back(1,2,3); - // "_ =" is required to explicitly discard emplace_back's - // return value (which is non-void since C++17) - -{ - x := my_vector.begin(); - std::advance(x, 2); - _ = x; // required to explicitly discard x's new value, - // because std::advance modifies x's value -} -``` - -For details, see [Design note: Explicit discard](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Explicit-discard). In Cpp2, 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. - -### Fundamental data types - -Cpp2 supports the same fundamental types as today's Cpp1, but additionally provides the following aliases in namespace `cpp2`: - -| Fixed-width types | Synonym for | -|---|---| -| `i8` | `std::int8_t` | -| `i16` | `std::int16_t` | -| `i32` | `std::int32_t` | -| `i64` | `std::int64_t` | -| `u8` | `std::uint8_t` | -| `u16` | `std::uint16_t` | -| `u32` | `std::uint32_t` | -| `u64` | `std::uint64_t` | - -| Variable-width types
(Cpp2-compatible single-word names) | Synonym for (these multi-word
names are not allowed in Cpp2) | -|---|---| -| `ushort` | `unsigned short` | -| `uint` | `unsigned int` | -| `ulong` | `unsigned long` | -| `longlong` | `long long` | -| `ulonglong` | `unsigned long long` | -| `longdouble` | `long double` | - -| For compatibility/interop only,
so deliberately ugly names | Synonym for | Notes | -|---|---|---| -| `_schar` | `signed char` | Normally, prefer `i8` instead | -| `_uchar` | `unsigned char` | Normally, prefer `u8` instead | - -### Type qualifiers - -Types can be qualified with `const` and `*`. Types are written left-to-right, so a qualifier always applies to what immediately follows it. For example, to declare a `const` pointer to a non-`const` pointer to a `const i32` object, write: - -``` cpp title="Example: Type qualifiers" -// A const pointer to a non-const pointer to a const i32 object -p: const * * const i32; -``` - -### Literals - -Cpp2 supports the same `'c'`haracter, `"string"`, binary, integer, and floating point literals as Cpp1, including most Unicode encoding prefixes and raw string literals. - -Cpp2 supports using Cpp1 user-defined literals for compatibility, to support seamlessly using existing libraries. However, because Cpp2 has unified function call syntax (UFCS), the preferred way to author the equivalent in Cpp2 is to just write a function or type name as a `.` call suffix. For example: - -- You can create a `u8` value by writing either `u8(123)` or **`123.u8()`**. [^u8using] - -- You can write a 'constexpr' function like `nm: (value: i64) -> my_nanometer_type == { /*...*/ }` that takes an integer and returns a value of a strongly typed "nanometer" type, and then create a `nm` value by writing either `nm(123)` or **`123.nm()`**. - -Both **`123.n()`** and **`123.u8()`** are very similar to user-defined literal syntax, and more general. - -### Operators - -Operators have the same precedence and associativity as in Cpp1, but some unary operators that are prefix (always or sometimes) in Cpp1 are postfix (always) in Cpp2. - -#### Unary operators - -The operators `!`, `+`, and `-` are prefix, as in Cpp1. For example: - -``` cpp title="Example: Prefix operators" -if !vec.empty() { - vec.emplace_back( -123.45 ); -} -``` - -| Unary operator | Cpp2 example | Cpp1 equivalent | -|---|---|---| -| `!` | `!vec.empty()` | `!vec.empty()` | -| `+` | `+100` | `+100` | -| `-` | `-100` | `-100` | - -The operators `.`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, and `$` are postfix. For example: - -``` cpp title="Example: Postfix operators" -// Cpp1 examples, from cppfront's own source code: -// address = &(*tokens)[pos + num]; -// is_void = *(*u)->identifier == "void"; -// Cpp2 equivalents: - address = tokens*[pos + num]&; - is_void = u**.identifier* == "void"; -``` - -Postfix notation lets the code read fluidly left-to-right, in the same order in which the operators will be applied, and lets declaration syntax be consistent with usage syntax. For more details, see [Design note: Postfix operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-operators). - -> Note: The function call syntax `f(x)` calls a namespace-scope function, or a function object, named `f`. The function call syntax `x.f()` is a unified function call syntax (aka UFCS) that calls a type-scope function in the type of `x` if available, otherwise calls the same as `f(x)`. For details, see [Design note: UFCS](https://github.com/hsutter/cppfront/wiki/Design-note%3A-UFCS). - -| Unary operator | Cpp2 example | Cpp1 equivalent | -|---|---|---| -| `.` | `obj.f()` | `obj.f()` | -| `*` | `pobj*.f()` | `(*pobj).f()` or `pobj->f()` | -| `&` | `obj&` | `&obj` | -| `~` | `val~` | `~val` | -| `++` | `iter++` | `++iter` | -| `--` | `iter--` | `--iter` | -| `(` `)` | `f( 1, 2, 3)` | `f( 1, 2, 3)` | -| `[` `]` | `vec[123]` | `vec[123]` | -| `$` | `val$` | (reflection — no C++23 equivalent) | - -> Because `++` and `--` always have in-place update semantics, we never need to remember "use prefix `++`/`--` unless you need a copy of the old value." If you do need a copy of the old value, just take the copy before calling `++`/`--`. - -Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~` are used as binary operators they must be preceded by whitespace. For example: - -| Unary postfix operators that
are also binary operators | Cpp2 example | Cpp1 equivalent | -|---|---|---| -| `*` | `pobj* * 42` | `(*pobj)*42` | -| `&` | `obj& & mask`

(note: allowed in unsafe code only) | `&obj & mask` | -| `~` | `~val ~ bitcomplement` | `val~ ~ bitcomplement` | - -For more details, see [Design note: Postfix unary operators vs binary operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-unary-operators-vs-binary-operators). - -#### Binary operators - -Binary operators are the same as in Cpp1. From lowest to highest precedence: - -| Binary operators grouped by precedence | -|---| -| `*`, `/`, `%` | -| `+`, `-` | -| `<<`, `>>` | -| `<=>` | -| `<`, `>`, `<=`, `>=` | -| `==`, `!=` | -| `&` | -| `^` | -| `|` | -| `&&` | -| `||` | -| `=` and compound assignment | - -### `is` — safe type/value queries - -An `x is C` expression allows safe type and value queries, and evaluates to `true` if `x` matches constraint `C`. It supports both static and dynamic queries, including customization with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. - -There are two kinds of `is`: - -- A **type query**, where `C` is a type constraint: A type, a template name, a concept, or a type predicate. Here `x` may be a type, or an object or expression; if it is an object or expression, the query refers to `x`'s type. - -| Type constraint kind | Example | Notes | -|---|---|---| -| Static type query | `x is int` | -| Dynamic type query | `ptr* is Shape` | -| Static template type query | `x is std::vector` | -| Static concept query | `x is std::integral` | - -- A **value query**, where `C` is a value constraint: A value, or a value predicate. Here `x` must be an object or expression. - -| Value constraint kind | Example | -|---|---| -| Value | `x is 0` | -| Value predicate | `x is (in(10, 20))` | - -`is` is useful throughout the language, including in `inspect` pattern matching alternatives. `is` is extensible, and works out of the box with `std::variant`, `std::optional`, and `std::any`. For examples, see: - -- [`mixed-inspect-templates.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-templates.cpp2) -- [`mixed-inspect-values.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-values.cpp2) -- [`mixed-inspect-values-2.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-values-2.cpp2) -- [`mixed-type-safety-1.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-type-safety-1.cpp2) -- [`pure2-enum.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-enum.cpp2) -- [`pure2-inspect-expression-in-generic-function-multiple-types.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-inspect-expression-in-generic-function-multiple-types.cpp2) -- [`pure2-inspect-fallback-with-variant-any-optional.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-inspect-fallback-with-variant-any-optional.cpp2) -- [`pure2-type-safety-1.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-type-safety-1.cpp2) -- [`pure2-type-safety-2-with-inspect-expression.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-type-safety-2-with-inspect-expression.cpp2) - - -### `as` — safe casts and conversions - -An `x as T` expression allows safe type casts. `x` must be an object or expression, and `T` must be a type. It supports both static and dynamic typing, including customization with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. For example: - -``` cpp title="Example: Using as" -main: () = { - a: std::any = 0; // a's type is now int, value 0 - test(a); // prints "zero" - a = "plugh" as std::string; // a's type is now std::string, value "plugh" - test(a); // prints "plugh" - test("xyzzy" as std::string); // prints "xyzzy" -} - -// A generic function that takes an argument 'x' of any type, -// same as "void test( auto const& x )" in C++20 syntax -test: (x) = { - std::cout << inspect x -> std::string { - is 0 = "zero"; - is std::string = x as std::string; - is _ = "(no match)"; - } << "\n"; -} -``` - -### `inspect` — pattern matching - -An `inspect expr -> Type` expression allows pattern matching using `is`. - -- `expr` is evaluated once. -- Each alternative spelled `is C` is evaluated in order as if called with `expr is C`. -- If an alternative evaluates to `true`, then its `= alternative;` body is used as the value of the entire `inspect` expression, and the meaning is the same as if the entire `inspect` expression had been written as just `:Type = alternative;` — i.e., an unnamed object expression (aka 'temporary object') of type `Type` initialized with `alternative`. -- A catchall `is _` is required. - -For example: - -``` cpp title="Example: Using inspect" -// A generic function that takes an argument 'x' of any type -// and inspects various things about `x` -test: (x) = { - forty_two := 42; - std::cout << inspect x -> std::string { - is 0 = "zero"; // == 0 - is (forty_two) = "the answer"; // == 42 - is int = "integer"; // is type int (value other than 0 or 42) - is std::string = x as std::string; // is type std::string - is std::vector = "a std::vector"; // is a vector - is _ = "(no match)"; // is something else - } << "\n"; -} - -// Sample call site -test(42); - // Behaves as if the following function were called: - // test: (x) = { std::cout << (:std::string = "the answer") << "\n"; } - // (and that's why inspect alternatives are introduced with '=') -``` - -For more examples, see also the examples in the previous two sections on `is` and `as`, many of which use `inspect`. - - -### `$` — captures, including interpolations - - -For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). - - - -## Functions and variables - -### Overview - -### Parameter passing - -All parameters and other objects in Cpp2 are `const` by default, except for local variables. For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). - -### Control flow - -#### `if`, `else` — branches - -#### `for`, `while`, `do` — loops - -Loops can be named using the usual **name `:`** name introduction syntax, and `break` and `continue` can refer to those names. For example: - -``` cpp title="Example: Writing a simple type" -outer: while i Cpp2 syntax has no separate base 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 subobject, so that it naturally outlives the base subobject. - -> Cpp2 code doesn't need workarounds like Boost's `base_from_member`, because all of the motivating examples for that can be written directly. See my comments on [cppfront issue #334](https://github.com/hsutter/cppfront/issues/334) for details. - -### `virtual`, `override`, and `final` - -Virtual functions are written by specifying exactly one of `virtual`, `override`, or `final` on the `this` parameter. A pure virtual function is a function with a `virtual this` parameter and no body. For example: - -``` cpp title="Example: Virtual functions" -abstract_base: type = { - // ... - print: (virtual this, msg: std::string); // pure virtual (virtual + no body) - // ... -} - -derived: type = { - this: abstract_base; // derives from abstract_base - // ... - print: (override this, msg: std::string); // explicit override - // ... -} -``` - -### `operator=`: Construction, assignment, and destruction - -All value operations are spelled `operator=`, including construction, assignment, and destruction. All default to memberwise semantics and safe "explicit" by default. A special `that` parameter makes writing copy/move/conversion in particular simpler and safer. For details, see [Design note: operator=, this & that](https://github.com/hsutter/cppfront/wiki/Cpp2:-operator=,-this-&-that). 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). - -> 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" for the purpose of suppressing unwanted generated functions. - -- The most general form of `operator=` is `operator=: (out this, that)`. It 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 above for details). - -- All copy/move/comparison operator= functions are memberwise by default in Cpp2, including memberwise construction and assignment when you write them yourself. - -- 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. - -### `operator<=>`: Unified comparisons (mostly in C++20) - -Most of Cpp2's `operator<=>` has already been merged into ISO C++, except for allowing chained comparisons. In Cpp2, comparisons can be safely chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: - -- **Valid chains: All `<`/`<=`, all `>`/`>=`, or all `==`.** All mathematically sound and safe chains like `a <= b < c` are supported, with efficient single evaluation of each term. They are "sound" because they are transitive; these chains imply a relationship between `a` and `c` (in this case, the chain implies that `a <= c` is also true). - -- **Invalid chains: Everything else.** Nonsense chains like `a >= b < c` and `a != b != c` are compile time errors. They are "nonsense" because they are non-transitive; these chains do not imply any relationship between `a` and `c`. - -For details, see [P0515 "Consistent comparison" section 3.3](https://wg21.link/p0515) and [P0893 "Chaining comparisons"](https://wg21.link/p0893). - - -## Metafunctions - -A metafunction is a compile-time function that can participate in interpreting the meaning of a declaration, and can: - -- apply defaults (e.g., `interface` makes functions virtual by default) -- enforce constraints (e.g., `value` enforces that the type has no virtual functions) -- generate additional functions and other code (e.g., `value` generates copy/move/comparison operations for a type if it didn't write them explicitly) - -The most important thing about metafunctions is that they are not hardwired language features — they are compile-time library code that uses the reflection and code generation API, that lets the author of an ordinary 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. - -### Applying metafunctions - -Using a metafunctionis always opt-in, by writing `@name` afer the `:` of a declaration, where `name` is the name of the metafunctions. This lets the type author declare (and the human reader see) the intent up front: "This isn't just any `type`, this is a `@value type`" which automatically gives the type default/copy/move construction and assignment, `<=>` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions: - -``` cpp title="Example: Using the value metafunction when writing a type" -point2d: @value type = { - x: i32 = 0; - y: i32 = 0; - // @value automatically generates default/copy/move - // construction/assignment and <=> strong_ordering comparison, - // and emits an error if you try to write a non-public - // destructor or any protected or virtual function -} -``` - -### Generating source code at compile time - -### Built-in metafunctions - - -#### `enum` and `flag_enum` - -Cpp2 has no `enum` feature hardwired into the language. Instead you apply the `@enum` metafunction when writing an ordinary `type`: - -``` cpp title="Example: Using @enum" -// 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 here 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). - -Unlike C `enum`, this `@enum` is scoped and strongly typed (does not implicitly convert to the underlying type. - -Unlike C++11 `enum class`, it's "just a `type`" which means it can naturally also have member functions and other things that a type can have: - -``` cpp title="Example: An @enum type with a member function" -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: - -``` cpp title="Example: Using @flag_enum" -// 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; -} -``` - - -#### `union` - -`@union` declaratively opts into writing a safe discriminated union/variant dynamic type. For example: - -``` cpp title="Example: Using @union" -// 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; -} - -main: () = { - x: name_or_number = (); - - x.set_name("xyzzy"); // now x is a string - std::cout << x.name(); // prints the string - - // trying to use x.num() here would cause a Type safety - // contract violation, because x is currently a string - - x.set_num( 120 ); // now x is a number - std::cout << x.num() + 3; // prints 123 -} -``` - -Unlike C `union`, this `@union` is safe to use because it always ensures only the active type is accessed. - -Unlike C++11 `std::variant`, this `@union` is easier to use because its alternatives are anonymous, and safer to use because each union type is a distinct type. [^variant] - -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. - -Because a `@union type` is still a `type`, it can naturally have other things normal types can have, such as template parameter lists and member functions: - -``` cpp title="Example: A templated custom safe union type" -name_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" here, but is legal - // whichever alternative is active -} -``` - -### Writing your own metafunctions - -### Reflection API reference - - -## Namespaces - -### Overview - -For details, see [Design note: Namespaces](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Namespaces). - - -### `using` - - - -## `requires` constraints - - - -## Aliases - -### Namespace aliases - -### Type aliases - -### Function aliases - -### Object aliases - - -## Modules - -### `import` - -### `export` - - - -[^u8using]: Or `123.cpp2::u8()` if you aren't `using` the namespace or that specific name. - -[^variant]: With `variant`, 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. diff --git a/mkdocs.yml b/mkdocs.yml index 21d1fb13a3..9ae3b7db3c 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,8 +19,6 @@ site_name: "Cpp2 and cppfront — An experimental 'C++ syntax 2', and its first theme: name: material features: - - navigation.tabs - - navigation.tabs.sticky - navigation.sections - navigation.expand - navigation.instant @@ -47,6 +45,24 @@ theme: primary: teal accent: lime +nav: + - 'Welcome & getting started': index.md + - 'Cpp2 reference': + - 'Overview': cpp2.md + - 'Common programming concepts': cpp2/common.md + - 'Common expressions': cpp2/expressions.md + - 'Declaration syntax': cpp2/declarations.md + - 'Functions & variables': cpp2/functions-and-variables.md + - 'Types': cpp2/types.md + - 'Metafunctions & reflection API': cpp2/metafunctions.md + - 'Namespaces': cpp2/namespaces.md + - 'Aliases': cpp2/aliases.md + - 'Modules': cpp2/modules.md + - 'Cppfront reference': + - 'Overview': cppfront.md + - 'Using Cpp1 (today''ds C++) and Cpp2 in the same source file': cppfront/mixed.md + - 'Cppfront command line options': cppfront/options.md + markdown_extensions: - pymdownx.highlight: anchor_linenums: true From 0786e27767116bce2e488a2cccc99e755c3b0cb9 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Wed, 14 Feb 2024 11:33:46 -1000 Subject: [PATCH 25/64] Organize the welcome info into three pages And try to use extra CSS to tweak the navigation pane --- docs/index.md | 201 ++---------------------------------- docs/stylesheets/extra.css | 9 ++ docs/welcome/hello-world.md | 145 ++++++++++++++++++++++++++ docs/welcome/integration.md | 43 ++++++++ mkdocs.yml | 10 +- 5 files changed, 211 insertions(+), 197 deletions(-) create mode 100644 docs/stylesheets/extra.css create mode 100644 docs/welcome/hello-world.md create mode 100644 docs/welcome/integration.md diff --git a/docs/index.md b/docs/index.md index f6760f4733..100018bfdd 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,9 +1,7 @@ -# Welcome & getting started +# Overview: What are Cpp2 and cppfront? How do I get and build cppfront? -## What are Cpp2 and cppfront? - -### What is Cpp2? +## What is Cpp2? "Cpp2," short for "C++ syntax 2," is my ([Herb Sutter's](https://github.com/hsutter)) personal project to try to make writing ordinary C++ types/functions/objects be much **simpler and safer**, without breaking backward compatibility. Bjarne Stroustrup said it best: @@ -24,13 +22,14 @@ We can't make an improvement that large to C++ via gradual evolution to today's **What it is.** Cpp2 aims to be another "skin" for C++ itself, just a simpler and safer way to write ordinary C++ types/functions/objects. It seamlessly uses Standard C++ modules and concepts requirements and other features, and it works with all existing C++20 or higher compilers and tools right out of the box with zero overhead. -### What is cppfront? +## What is cppfront? [**Cppfront**](https://github.com/hsutter/cppfront) is a compiler that compiles Cpp2 syntax to today's Cpp1 syntax. This lets you start trying out Cpp2 syntax in any existing C++ project and build system just by renaming a source file from `.cpp` to `.cpp2` and [adding a build step](#adding-cppfront-in-your-ide-build-system), and the result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). This deliberately follows Bjarne Stroustrup's wise approach with [**cfront**](https://en.wikipedia.org/wiki/Cfront), the original C++ compiler: In the 1980s and 1990s, Stroustrup created cfront to translate C++ to pure C, and similarly ensured that C++ could be interleaved with C in the same source file, and that C++ could always call any C code with no wrapping/marshaling/thunking. By providing a C++ compiler that emitted pure C, Stroustrup ensured full compatibility with the C ecosystems that already existed, and made it easy for people to start trying out C++ code in any existing C project by adding just another build step to translate the C++ to C first, and the result Just Worked with existing C tools. -### How do I get and build cppfront? + +## How do I get and build cppfront? The full source code for cppfront is at the [**Cppfront GitHub repo**](https://github.com/hsutter/cppfront). @@ -53,193 +52,5 @@ clang++ cppfront.cpp -std=c++20 -o cppfront That's it! -## **Hello, world!** - -### A `hello.cpp2` program - -Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program, no `#include` required: - -``` cpp title="hello.cpp2 — on one line" -main: () = std::cout << "Hello, world!\n"; -``` - -But let's add a little more, just to show a few things: - -``` cpp title="hello.cpp2 — slightly more interesting" -main: () = { - words: std::vector = ( "Alice", "Bob" ); - hello( words[0] ); - hello( words[1] ); - std::cout << "... and goodnight\n"; -} - -hello: (msg: std::string_view) = - std::cout << "Hello, (msg)$!\n"; -``` - -This short program code already illustrates a few Cpp2 essentials. - -**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object/namespace/etc. declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced **"defined as."** - -- `main` **is a** function that takes no arguments and returns nothing, and is **defined as** the code body shown. - -- `words` **is a** `std::vector`, initially **defined as** holding `"Alice"` and `"Bob"`. - -- `hello` **is a** function that takes a `std::string_view` it will only read from and that returns nothing, and is **defined as** code that prints the string to `cout` the usual C++ way. - -All grammar is context-free. In particular, we (the human reading the code, and the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. For details, see [Design note: Unambiguous parsing](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unambiguous-parsing). - -**Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. - -- Declaring `words` uses **"CTAD"** (C++'s normal constructor template argument deduction) to declare its `words` variable. - -- Calling `words[0]` and `words[1]` is **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. - -- `hello` uses **string interpolation** to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. - -**Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: - -- We can omit writing the `-> void` return type for a function that doesn't return anything, as both of these functions do. - -- We can omit the `{` `}` around single-statement function bodies, as `hello` does. - -- We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), `inout` for read-write, and also `copy`, `out`, `move`, and `forward`. - -For details, see [Design note: Defaults are one way to say the same thing](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Defaults-are-one-way-to-say-the-same-thing). - -**Order-independent by default.** Did you notice that `main` called `hello`, which was defined later? Cpp2 code is order-independent by default — there are no forward declarations. - -**Seamless compatibility and interop.** We can just use `std::cout` and `std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using ordinary direct calls without any wrapping/marshaling/thunking. - -**C++ standard library always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`). Cppfront is regularly updated to be compatible with C++23 and the latest draft C++26 library additions as soon as the ISO C++ committee votes them into the C++26 working draft, so as soon as you have a C++ implementation that has a new standard (or bleeding-edge draft standard!) C++ library feature, you'll be able to fully use it in Cpp2 code. - - -### Building `hello.cpp2` from the command line - -Now use `cppfront` to compile `hello.cpp2` to a standard C++ file `hello.cpp`: - -``` bash title="Call cppfront to produce hello.cpp" -cppfront hello.cpp2 -p -``` - -The result is an ordinary C++ file that looks like this: [^clean-cpp1] - -``` cpp title="hello.cpp — created by cppfront" linenums="1" -#define CPP2_IMPORT_STD Yes - -#include "cpp2util.h" - -auto main() -> int; - -auto hello(cpp2::in msg) -> void; -auto main() -> int{ - std::vector words {"Alice", "Bob"}; - hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(words, 0)); - hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(std::move(words), 1)); - std::cout << "... and goodnight\n"; -} - -auto hello(cpp2::in msg) -> void { - std::cout << ("Hello, " + cpp2::to_string(msg) + "!\n"); } -``` - -Here we can see more of how Cpp2 makes it features work. - -**How: Consistent context-free syntax.** - -- **Lines 8, 9, and 15:** Cpp2's context-free syntax converts directly to today's Cpp1 syntax. We can write and read our C++ types/functions/objects in simpler Cpp2 syntax without wrestling with context sensitivity and ambiguity, and they're all still just ordinary types/functions/objects. - -**How: Simple and safe by default.** - -- **Line 9:** CTAD just works, because it turns into ordinary C++ code which is CTAD-aware. -- **Lines 10-11:** The accesses of `words[0]` and `words[1]` are bounds-checked nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::ssize`-aware, when you use them from safe Cpp2 code. -- **Line 16:** String interpolation performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `bool`, to print `false` and `true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). - -**How: Simplicity through generality + defaults.** - -- **Line 7:** The default `in` parameter passing convention is implemented using `cpp2::in<>`, which is smart enough to pass by `const` value when that's safe and appropriate, otherwise by `const&`, so you don't have to choose the right one by hand. - -**How: Order-independent by default.** - -- **Lines 5 and 7:** Cppfront achieves order independence is by generating all the type and function forward declarations for you, so you don't have to. That's why `main` can just call `hello`: Both are forward-declared, so they can both see each other. - -**How: Seamless compatibility and interop.** - -- **Lines 9, 12, and 16:** Calling existing C++ code is just ordinary direct calls, so there's never a need for wrapping/marshaling/thunking. - -**How: C++ standard library always available.** - -- **Lines 1 and 3:** Because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet), the generated code tells `cpp2util.h` to `import` the entire standard library as a module (or do the equivalent via headers if modules are not available). - - -### Building and running `hello.cpp` from the command line - -Finally, just build `hello.cpp` using your favorite C++20 compiler, where `CPPFRONT_INCLUDE` is the path to `/cppfront/include`: - - - -``` title="MSVC" -> cl hello.cpp -std:c++20 -EHsc -I CPPFRONT_INCLUDE -> hello.exe -Hello, world! -``` - -``` bash title="GCC" -$ g++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello -$ ./hello.exe -Hello, world! -``` - -``` bash title="Clang" -$ clang++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello -$ ./hello.exe -Hello, world! -``` - -## Adding cppfront in your IDE / build system - -To start trying out Cpp2 syntax in any existing C++ project, just add a build step to translate the Cpp2 to Cpp1 syntax: - -- Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode. -- Tell the IDE to build that file using a custom build tool to invoke cppfront. - -That's it... The result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). The IDE build should just pick up the `.cpp2` file source locations for any error messages, and the debugger should just step through the `.cpp2` file. - -The following uses Visual Studio as an example, but others have done the same in Xcode, Qt Creator, CMake, and other IDEs. - -#### 1. Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode - -For Visual Studio: In the Solution Explorer, right-click on Source Files and pick Add to add the file to the project. - -

- -Also in Solution Explorer, right-click on the `.cpp` file Properties and make sure it's in C++20 (or C++latest) mode. - -

- - -#### 2. Tell the project system to build that file using a custom build tool to invoke cppfront, and add `cppfront/include` to the include path - -For Visual Studio: In Solution Explorer, right-click on the `.cpp2` file and select Properties, and add the custom build tool. Remember to also tell it that the custom build tool produces the `.cpp` file, so that it knows about the build dependency: - -

- -Finally, put the `/cppfront/include` directory on your `INCLUDE` path. In Solution Explorer, right-click the app and select Properties, and add it to the VC++ Directories > Include Directories: - -

- -#### That's it: Error message outputs, debuggers, visualizers, and other tools should just work - -That's enough to enable builds, and the IDE just picks up the rest from the `.cpp` file that cppfront generated: - -- **The cppfront error messages in `filename(line, col)` format.** Most C++ IDEs recognize these, and usually automatically merge any diagnostic output wherever compiler error output normally appears. If your IDE prefers `filename:line:col`, just use the cppfront `-format-colon-errors` command line option. - -- **The `#line` directives cppfront emits in the generated `.cpp` file.** Most C++ debuggers recognize these and will know to step through the `.cpp2` file. Note that `#line` emission is on by default, but if you choose `-c` (short for `-clean-cpp1`) these will be suppressed and then the debugger will step through the generated C++ code instead. - -- **Regardless of syntax, every type/function/object/namespace/etc. is still just an ordinary C++ type/function/object/namespace/etc.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. - - - -[^simpler]: I'd ideally love to obsolete ~90% of the C++ guidance I've personally had to write and teach over the past quarter century, by removing inconsistencies and pitfalls and gotchas. I love writing C++ code... I just want it to be easier and safer by default. +[^simpler]: I'd ideally love to obsolete ~90% of my own books. I know that Cpp2 can eliminate that much of the C++ guidance I've personally had to write and teach over the past quarter century, by removing inconsistencies and pitfalls and gotchas, so that they're either impossible to write or are compile-time errors (either way, we don't have to teach them). I love writing C++ code... I just want it to be easier and safer by default. -[^clean-cpp1]: For presentation purposes, this documentation generally shows the `.cpp` as generated when using cppfront's `-c` (short for `-clean-cpp1`), which suppresses extra information cppfront normally emits in the `.cpp` to light up C++ tools (e.g., to let IDEs integrate cppfront error message output, debuggers step to the right lines in Cpp2 source code, and so forth). In normal use, you won't need `-c`. diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css new file mode 100644 index 0000000000..e8fab55a44 --- /dev/null +++ b/docs/stylesheets/extra.css @@ -0,0 +1,9 @@ + +/* + todo: try to make the nav pane section labels larger + + for now, this at least adds space between sections + to section starts are easier to see +*/ +.md-nav__item { font-size: 20pt; } +.md-nav__link { font-size: medium; } \ No newline at end of file diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md new file mode 100644 index 0000000000..d2105c56ab --- /dev/null +++ b/docs/welcome/hello-world.md @@ -0,0 +1,145 @@ +# **Hello, world!** + +## A `hello.cpp2` program + +Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program, no `#include` required: + +``` cpp title="hello.cpp2 — on one line" +main: () = std::cout << "Hello, world!\n"; +``` + +But let's add a little more, just to show a few things: + +``` cpp title="hello.cpp2 — slightly more interesting" +main: () = { + words: std::vector = ( "Alice", "Bob" ); + hello( words[0] ); + hello( words[1] ); + std::cout << "... and goodnight\n"; +} + +hello: (msg: std::string_view) = + std::cout << "Hello, (msg)$!\n"; +``` + +This short program code already illustrates a few Cpp2 essentials. + +**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object/namespace/etc. declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced **"defined as."** + +- `main` **is a** function that takes no arguments and returns nothing, and is **defined as** the code body shown. + +- `words` **is a** `std::vector`, initially **defined as** holding `"Alice"` and `"Bob"`. + +- `hello` **is a** function that takes a `std::string_view` it will only read from and that returns nothing, and is **defined as** code that prints the string to `cout` the usual C++ way. + +All grammar is context-free. In particular, we (the human reading the code, and the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. For details, see [Design note: Unambiguous parsing](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unambiguous-parsing). + +**Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. + +- Declaring `words` uses **"CTAD"** (C++'s normal constructor template argument deduction) to declare its `words` variable. + +- Calling `words[0]` and `words[1]` is **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. + +- `hello` uses **string interpolation** to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. + +**Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: + +- We can omit writing the `-> void` return type for a function that doesn't return anything, as both of these functions do. + +- We can omit the `{` `}` around single-statement function bodies, as `hello` does. + +- We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), `inout` for read-write, and also `copy`, `out`, `move`, and `forward`. + +For details, see [Design note: Defaults are one way to say the same thing](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Defaults-are-one-way-to-say-the-same-thing). + +**Order-independent by default.** Did you notice that `main` called `hello`, which was defined later? Cpp2 code is order-independent by default — there are no forward declarations. + +**Seamless compatibility and interop.** We can just use `std::cout` and `std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using ordinary direct calls without any wrapping/marshaling/thunking. + +**C++ standard library always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`). Cppfront is regularly updated to be compatible with C++23 and the latest draft C++26 library additions as soon as the ISO C++ committee votes them into the C++26 working draft, so as soon as you have a C++ implementation that has a new standard (or bleeding-edge draft standard!) C++ library feature, you'll be able to fully use it in Cpp2 code. + + +## Building `hello.cpp2` from the command line + +Now use `cppfront` to compile `hello.cpp2` to a standard C++ file `hello.cpp`: + +``` bash title="Call cppfront to produce hello.cpp" +cppfront hello.cpp2 -p +``` + +The result is an ordinary C++ file that looks like this: [^clean-cpp1] + +``` cpp title="hello.cpp — created by cppfront" linenums="1" +#define CPP2_IMPORT_STD Yes + +#include "cpp2util.h" + +auto main() -> int; + +auto hello(cpp2::in msg) -> void; +auto main() -> int{ + std::vector words {"Alice", "Bob"}; + hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(words, 0)); + hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(std::move(words), 1)); + std::cout << "... and goodnight\n"; +} + +auto hello(cpp2::in msg) -> void { + std::cout << ("Hello, " + cpp2::to_string(msg) + "!\n"); } +``` + +Here we can see more of how Cpp2 makes it features work. + +**How: Consistent context-free syntax.** + +- **Lines 8, 9, and 15:** Cpp2's context-free syntax converts directly to today's Cpp1 syntax. We can write and read our C++ types/functions/objects in simpler Cpp2 syntax without wrestling with context sensitivity and ambiguity, and they're all still just ordinary types/functions/objects. + +**How: Simple and safe by default.** + +- **Line 9:** CTAD just works, because it turns into ordinary C++ code which is CTAD-aware. +- **Lines 10-11:** The accesses of `words[0]` and `words[1]` are bounds-checked nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::ssize`-aware, when you use them from safe Cpp2 code. +- **Line 16:** String interpolation performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `bool`, to print `false` and `true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). + +**How: Simplicity through generality + defaults.** + +- **Line 7:** The default `in` parameter passing convention is implemented using `cpp2::in<>`, which is smart enough to pass by `const` value when that's safe and appropriate, otherwise by `const&`, so you don't have to choose the right one by hand. + +**How: Order-independent by default.** + +- **Lines 5 and 7:** Cppfront achieves order independence is by generating all the type and function forward declarations for you, so you don't have to. That's why `main` can just call `hello`: Both are forward-declared, so they can both see each other. + +**How: Seamless compatibility and interop.** + +- **Lines 9, 12, and 16:** Calling existing C++ code is just ordinary direct calls, so there's never a need for wrapping/marshaling/thunking. + +**How: C++ standard library always available.** + +- **Lines 1 and 3:** Because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet), the generated code tells `cpp2util.h` to `import` the entire standard library as a module (or do the equivalent via headers if modules are not available). + + +## Building and running `hello.cpp` from the command line + +Finally, just build `hello.cpp` using your favorite C++20 compiler, where `CPPFRONT_INCLUDE` is the path to `/cppfront/include`: + + + +``` title="MSVC" +> cl hello.cpp -std:c++20 -EHsc -I CPPFRONT_INCLUDE +> hello.exe +Hello, world! +``` + +``` bash title="GCC" +$ g++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello +$ ./hello.exe +Hello, world! +``` + +``` bash title="Clang" +$ clang++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello +$ ./hello.exe +Hello, world! +``` + + +[^clean-cpp1]: For presentation purposes, this documentation generally shows the `.cpp` as generated when using cppfront's `-c` (short for `-clean-cpp1`), which suppresses extra information cppfront normally emits in the `.cpp` to light up C++ tools (e.g., to let IDEs integrate cppfront error message output, debuggers step to the right lines in Cpp2 source code, and so forth). In normal use, you won't need `-c`. diff --git a/docs/welcome/integration.md b/docs/welcome/integration.md new file mode 100644 index 0000000000..136f06e89d --- /dev/null +++ b/docs/welcome/integration.md @@ -0,0 +1,43 @@ + +# Adding cppfront in your IDE / build system + +To start trying out Cpp2 syntax in any existing C++ project, just add a build step to translate the Cpp2 to Cpp1 syntax: + +- Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode. +- Tell the IDE to build that file using a custom build tool to invoke cppfront. + +That's it... The result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). The IDE build should just pick up the `.cpp2` file source locations for any error messages, and the debugger should just step through the `.cpp2` file. + +The following uses Visual Studio as an example, but others have done the same in Xcode, Qt Creator, CMake, and other IDEs. + +## 1. Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode + +For Visual Studio: In the Solution Explorer, right-click on Source Files and pick Add to add the file to the project. + +

+ +Also in Solution Explorer, right-click on the `.cpp` file Properties and make sure it's in C++20 (or C++latest) mode. + +

+ + +## 2. Tell the project system to build that file using a custom build tool to invoke cppfront, and add `cppfront/include` to the include path + +For Visual Studio: In Solution Explorer, right-click on the `.cpp2` file and select Properties, and add the custom build tool. Remember to also tell it that the custom build tool produces the `.cpp` file, so that it knows about the build dependency: + +

+ +Finally, put the `/cppfront/include` directory on your `INCLUDE` path. In Solution Explorer, right-click the app and select Properties, and add it to the VC++ Directories > Include Directories: + +

+ +## That's it: Error message outputs, debuggers, visualizers, and other tools should just work + +That's enough to enable builds, and the IDE just picks up the rest from the `.cpp` file that cppfront generated: + +- **The cppfront error messages in `filename(line, col)` format.** Most C++ IDEs recognize these, and usually automatically merge any diagnostic output wherever compiler error output normally appears. If your IDE prefers `filename:line:col`, just use the cppfront `-format-colon-errors` command line option. + +- **The `#line` directives cppfront emits in the generated `.cpp` file.** Most C++ debuggers recognize these and will know to step through the `.cpp2` file. Note that `#line` emission is on by default, but if you choose `-c` (short for `-clean-cpp1`) these will be suppressed and then the debugger will step through the generated C++ code instead. + +- **Regardless of syntax, every type/function/object/namespace/etc. is still just an ordinary C++ type/function/object/namespace/etc.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. + diff --git a/mkdocs.yml b/mkdocs.yml index 9ae3b7db3c..395dd2bc99 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -45,8 +45,14 @@ theme: primary: teal accent: lime +extra_css: + - stylesheets/extra.css + nav: - - 'Welcome & getting started': index.md + - 'Welcome & getting started': + - 'Overview: What are Cpp2 and cppfront? How do I get and build cppfront?': index.md + - 'Hello, world!': welcome/hello-world.md + - 'Adding cppfront to your existing C++ project': welcome/integration.md - 'Cpp2 reference': - 'Overview': cpp2.md - 'Common programming concepts': cpp2/common.md @@ -60,7 +66,7 @@ nav: - 'Modules': cpp2/modules.md - 'Cppfront reference': - 'Overview': cppfront.md - - 'Using Cpp1 (today''ds C++) and Cpp2 in the same source file': cppfront/mixed.md + - 'Using Cpp1 (today''s C++) and Cpp2 in the same source file': cppfront/mixed.md - 'Cppfront command line options': cppfront/options.md markdown_extensions: From c13d4e67227583b2a44760804e39ddba17fe93a1 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Wed, 14 Feb 2024 14:41:06 -1000 Subject: [PATCH 26/64] Disable regression tests on this branch --- .github/workflows/build-cppfront.yaml | 8 +++---- .github/workflows/regression-tests.yml | 4 ++++ docs/welcome/hello-world.md | 31 +++++++++++++------------- 3 files changed, 24 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build-cppfront.yaml b/.github/workflows/build-cppfront.yaml index 0bb92a20d8..79e6dd7026 100644 --- a/.github/workflows/build-cppfront.yaml +++ b/.github/workflows/build-cppfront.yaml @@ -1,11 +1,11 @@ name: Multi-platform Build of cppfront on: push: - branches: - - main + branches-ignore: + - docs pull_request: - branches: - - main + branches-ignore: + - docs jobs: build-windows: runs-on: windows-latest diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index fb45e9ac3f..1f1f498d4c 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -2,8 +2,12 @@ name: Regression tests on: pull_request: + branches-ignore: + - docs types: [opened, synchronize, reopened] push: + branches-ignore: + - docs workflow_dispatch: jobs: diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md index d2105c56ab..7127ab6aee 100644 --- a/docs/welcome/hello-world.md +++ b/docs/welcome/hello-world.md @@ -34,7 +34,7 @@ This short program code already illustrates a few Cpp2 essentials. All grammar is context-free. In particular, we (the human reading the code, and the compiler) never need to do name lookup to figure out how to parse something — there is never a ["vexing parse"](https://en.wikipedia.org/wiki/Most_vexing_parse) in Cpp2. For details, see [Design note: Unambiguous parsing](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unambiguous-parsing). -**Simple and safe by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, and more. +**Simple, safe, and efficient by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, automatic move from last use, and more. - Declaring `words` uses **"CTAD"** (C++'s normal constructor template argument deduction) to declare its `words` variable. @@ -59,7 +59,7 @@ For details, see [Design note: Defaults are one way to say the same thing](https **C++ standard library always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`). Cppfront is regularly updated to be compatible with C++23 and the latest draft C++26 library additions as soon as the ISO C++ committee votes them into the C++26 working draft, so as soon as you have a C++ implementation that has a new standard (or bleeding-edge draft standard!) C++ library feature, you'll be able to fully use it in Cpp2 code. -## Building `hello.cpp2` from the command line +## Building `hello.cpp2` Now use `cppfront` to compile `hello.cpp2` to a standard C++ file `hello.cpp`: @@ -92,50 +92,51 @@ Here we can see more of how Cpp2 makes it features work. **How: Consistent context-free syntax.** -- **Lines 8, 9, and 15:** Cpp2's context-free syntax converts directly to today's Cpp1 syntax. We can write and read our C++ types/functions/objects in simpler Cpp2 syntax without wrestling with context sensitivity and ambiguity, and they're all still just ordinary types/functions/objects. +- **Lines 8, 9, and 15: Portable C++20 code** we can build with any C++ compiler. Cpp2's context-free syntax converts directly to today's Cpp1 syntax. We can write and read our C++ types/functions/objects in simpler Cpp2 syntax without wrestling with context sensitivity and ambiguity, and they're all still just ordinary types/functions/objects. -**How: Simple and safe by default.** +**How: Simple, safe, and efficient by default.** -- **Line 9:** CTAD just works, because it turns into ordinary C++ code which is CTAD-aware. -- **Lines 10-11:** The accesses of `words[0]` and `words[1]` are bounds-checked nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::ssize`-aware, when you use them from safe Cpp2 code. -- **Line 16:** String interpolation performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `bool`, to print `false` and `true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). +- **Line 9: CTAD** just works, because it turns into ordinary C++ code which is CTAD-aware. +- **Lines 10-11: Automatic bounds checking** is added to `words[0]` and `words[1]` nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::ssize`-aware, when you use them from safe Cpp2 code. +- **Line 11: Automatic move from last use** ensures the last use of `words` will automatically avoid a copy if it's being passed to something that's optimized for rvalues. +- **Line 16: String interpolation** performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `bool`, to print `false` and `true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). **How: Simplicity through generality + defaults.** -- **Line 7:** The default `in` parameter passing convention is implemented using `cpp2::in<>`, which is smart enough to pass by `const` value when that's safe and appropriate, otherwise by `const&`, so you don't have to choose the right one by hand. +- **Line 7: `in` parameters** are implemented using `cpp2::in<>`, which is smart enough to pass by `const` value when that's safe and appropriate, otherwise by `const&`, so you don't have to choose the right one by hand. **How: Order-independent by default.** -- **Lines 5 and 7:** Cppfront achieves order independence is by generating all the type and function forward declarations for you, so you don't have to. That's why `main` can just call `hello`: Both are forward-declared, so they can both see each other. +- **Lines 5 and 7: Order independence** happens because cppfront generates all the type and function forward declarations for you, so you don't have to. That's why `main` can just call `hello`: Both are forward-declared, so they can both see each other. **How: Seamless compatibility and interop.** -- **Lines 9, 12, and 16:** Calling existing C++ code is just ordinary direct calls, so there's never a need for wrapping/marshaling/thunking. +- **Lines 9, 12, and 16: Ordinary direct calls** to existing C++ code, so there's never a need for wrapping/marshaling/thunking. **How: C++ standard library always available.** -- **Lines 1 and 3:** Because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet), the generated code tells `cpp2util.h` to `import` the entire standard library as a module (or do the equivalent via headers if modules are not available). +- **Lines 1 and 3: `std::` is available** because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet). The generated code tells `cpp2util.h` to `import` the entire standard library as a module (or do the equivalent via headers if modules are not available). -## Building and running `hello.cpp` from the command line +## Building and running `hello.cpp` with any recent C++ compiler Finally, just build `hello.cpp` using your favorite C++20 compiler, where `CPPFRONT_INCLUDE` is the path to `/cppfront/include`: -``` title="MSVC" +``` title="MSVC (Visual Studio 2019 version 16.11 or higher)" > cl hello.cpp -std:c++20 -EHsc -I CPPFRONT_INCLUDE > hello.exe Hello, world! ``` -``` bash title="GCC" +``` bash title="GCC (GCC 10 or higher)" $ g++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello $ ./hello.exe Hello, world! ``` -``` bash title="Clang" +``` bash title="Clang (Clang 12 or higher)" $ clang++ hello.cpp -std=c++20 -ICPPFRONT_INCLUDE -o hello $ ./hello.exe Hello, world! From be7cd497e0cce0e441dde7caa40961aff379a648 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Wed, 14 Feb 2024 15:42:13 -1000 Subject: [PATCH 27/64] Add more types and `operator=` material And do further cleanup on the docs structure --- docs/cpp2.md | 6 -- docs/cpp2/types.md | 164 +++++++++++++++++++++++++++++++++++++---- docs/cpp2/variables.md | 8 ++ docs/cppfront.md | 5 -- mkdocs.yml | 7 +- 5 files changed, 161 insertions(+), 29 deletions(-) delete mode 100644 docs/cpp2.md create mode 100644 docs/cpp2/variables.md delete mode 100644 docs/cppfront.md diff --git a/docs/cpp2.md b/docs/cpp2.md deleted file mode 100644 index f182facee7..0000000000 --- a/docs/cpp2.md +++ /dev/null @@ -1,6 +0,0 @@ - -# Cpp2 reference - -### See also: **[Hello, world!](index.md/#hello-world)** - -TODO diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index 2b650ed807..326afb18c9 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -20,14 +20,24 @@ mytype: type = } ``` -## `this` — Parameters +## `this` — The parameter name -The `this` parameter is explicit, and has special sauce: +**`this`** is a synonym for the current object. Inside the scope of a type that has a member named `member`, `member` by default means `this.member`. + +> Note: In Cpp2, `this` is not a pointer. + +The name `this` may only be used for the first parameter of a type-scope function (aka member function). It is never declared with an explicit `: its_type` because its type is always the current type. + +`this` can be an `in` (default), `inout`, `out`, or `move` parameter. Which you choose naturally determines what kind of member function is being declared: + +- **`in this`**: Writing `myfunc: (this /*...*/)`, which is shorthand for `myfunc: (in this /*...*/)`, defines a Cpp1 `const`-qualified member function, because `in` parameters are `const`. + +- **`inout this`**: Writing `myfunc: (inout this /*...*/)` defines a Cpp1 non-`const` member function. + +- **`out this`**: Writing `myfunc: (out this /*...*/)` defines a Cpp1 constructor... and more. (See below.) + +- **`move this`**: Writing `myfunc: (move this /*...*/)` defines a Cpp1 `&&`-qualified member function, or if there are no additional parameters it defines the destructor. -- `this` is a synonym for the current object (not a pointer). -- Inside a type scope function, writing `this.` before a member name is optional. -- `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` always implies 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 read-only member function named `print` that takes a read-only string value and prints this object's data value and the string message: ``` cpp title="Example: this" @@ -55,7 +65,17 @@ Because base and member subobjects are all declared in the same place (the type ## `virtual`, `override`, and `final` — Virtual functions -Virtual functions are written by specifying exactly one of `virtual`, `override`, or `final` on the `this` parameter. A pure virtual function is a function with a `virtual this` parameter and no body. For example: +A `this` parameter can additionally be declared as one of the following: + +- **`virtual`**: Writing `myfunc: (virtual this /*...*/)` defines a new virtual function. + +- **`override`**: Writing `myfunc: (override this /*...*/)` defines an override of an existing base class virtual function. + +- **`final`**: Writing `myfunc: (final this /*...*/)` defines a final override of an existing base class virtual function. + +A pure virtual function is a function with a `virtual this` parameter and no body. + +For example: ``` cpp title="Example: Virtual functions" abstract_base: type @@ -78,25 +98,141 @@ derived: type } ``` + +## `implicit` — Controlling conversion functions + +A `this` parameter of an `operator=` function can additionally be declared as: + +- **`implicit`**: Writing `operator=: (implicit out this, /*...*/)` defines a function that will not be marked as "explicit" when lowered to Cpp1 syntax. + +> Note: This reverses the Cpp1 default, where constructors are not "explicit" by default, and you have to write "explicit" to make them explicit. + + ## `operator=` — Construction, assignment, and destruction -All value operations are spelled `operator=`, including construction, assignment, and destruction. All default to memberwise semantics and safe "explicit" by default. A special `that` parameter makes writing copy/move/conversion in particular simpler and safer. For details, see [Design note: operator=, this & that](https://github.com/hsutter/cppfront/wiki/Cpp2:-operator=,-this-&-that). Briefly summarizing here: +All value operations are spelled `operator=`, including construction, assignment, and destruction. `operator=` sets the value of `this` object, so the `this` parameter can be pass as anything but `in` (which would imply `const`): + +- **`out this`:** Writing `operator=: (out this /*...*/ )` is naturally both a constructor and an assignment operator, because an `out` parameter can take an uninitialized or initialized argument. If you don't write a more-specialized `inout this` assignment operator, Cpp2 will use the `out this` function also for assignment. + +- **`inout this`:** Writing `operator=: (inout this /*...*/ )` is an assignment operator (only), because an `inout` parameter requires an initialized modifiable argument. + +- **`move this`:** Writing `operator=: (move this)` is the destructor. No other parameters are allowed, so it connotes "move `this` nowhere." + +Unifying `operator=` enables usable `out` parameters, which is essential for composable guaranteed initialization. We want the expression syntax `x = value` to be able to call a constructor or an assignment operator, so naming them both `operator=` is consistent. + +> Note: Writing `=` always invokes an `operator=` (in fact for a Cpp2-authored type, and semantically for a Cpp1-authored type). This avoids the Cpp1 inconsistency that "writing `=` calls `operator=`, except when it doesn't" (such as in a Cpp1 variable initialization). Conversely, `operator=` is always invoked by `=` in Cpp2. + +### `that` — A source parameter + +All functions can have a **`that`** is a synonym for the object to be copied/moved from. Like `this`, at type scope it is never declared with an explicit `: its_type` because its type is always the current type. + +`that` can be an `in` (default) or `move` parameter. Which you choose naturally determines what kind of member function is being declared: + +- **`in that`**: Writing `myfunc: (/*...*/ this, that)`, which is shorthand for `myfunc: (/*...*/ this, in that)`, is naturally both a copy and move function, because it can accept an lvalue or an rvalue `that` argument. If you don't write a more-specialized `move that` move function, Cpp2 will automatically use the `in that` function also for move. + +- **`move that`**: Writing `myfunc: (/*...*/ this, move that)` defines a move function. + +Putting `this` and `that` together: The most general form of `operator=` is `operator=: (out this, that)`. It 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. + + +### `operator=` can generalize (A)ssignment from construction, and (M)ove from copy + +As mentioned above: +- If you don't write an `inout this` function, Cpp2 will use your `out this` function in its place (if you wrote one). +- If you don't write a `move that` function, Cpp2 will use your `in that` function in its place (if you wrote one). + +> Note: When lowering to Cpp1, this just means generating the applicable special member functions from the appropriate Cpp2 function. + +This graphic summarizes these generalizations. For convenience I've numbered the (A)ssignment and (M)ove defaults. + +![image](https://user-images.githubusercontent.com/1801526/226261443-03125a35-7890-4cc7-bf7d-f23b3a0bb0df.png) + +In Cpp1 terms, they can be described as follows: + +- **(M)ove, M1, M2:** If you write a copy constructor or assignment operator, but not a corresponding move constructor or assignment operator, the latter is generated. + +- **(A)ssignment, A1, A2, A3:** If you write a copy or move or converting constructor, but not a corresponding copy or move or converting assignment operator, the latter is generated. + +- **The arrows are transitive.** For example, if you write a copy constructor and nothing else, the move constructor, copy assignment operator, and move assignment operator are generated. + +- **M2 is preferred over A2.** Both M2 and A2 can generate a missing `(inout this, move that)` function. If both options are available, Cpp2 prefers to use M2 (generate move assignment from copy assignment, which could itself have been generated from copy construction) rather than A2 (generate move assignment from move construction). This is because M2 is a better fit: Move assignment is more like copy assignment than like move construction, because assignments are designed structurally to set the value of an existing `this` object. + +The most general `operator=` with `that` is `(out this, that)`. In Cpp1 terms, it generates all four combinations of { copy, move } x { constructor, assignment }. This is often sufficient, so you can write all these value-setting just once. If you do want to write a more specific version that does something else, though, you can always write it too. + +> Note: Generating `inout this` (assignment) from `out this` also generates **converting assignment** from converting construction, which is a new thing. Today in Cpp1, if you write a converting constructor from another type `X`, you may or may not write the corresponding assignment from `X`; in Cpp2 you will get that by default, and it sets the object to the same state as the converting constructor from `X` does. + + + +### Minimal functions generated by default + +There are only two defaults the language will generate implicitly for a type: - 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). +All other `operator=` functions are explicitly written, either by hand or by opting into applying a metafunction (see below). + +> 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" for the purpose of suppressing unwanted generated functions. -> 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" for the purpose of suppressing unwanted generated functions. +### Memberwise by default + +All copy/move/comparison `operator=` functions are memberwise by default in Cpp2. That includes when you write memberwise construction and assignment yourself. In a hand-written `operator=`: + +- The body must begin with a series of `member = value;` statements, one for each of the type's data members in order. + +- If the body does not mention a member, by default the member's default initializer is used. + +- In an assignment operator (`inout this`), you an explicitly skip setting a member by writing `member = _;` where it would normally be set, if you know you have a reason to set its value later instead. + +For example: + +``` cpp title="Memberwise operator= semantics +mytype: type += { + // data members (private by default) + name: std::string; + social_handle: std::string = "(unknown)"; + + // conversion from string + operator=: (out this, who: std::string) = { + name = who; + // if social_handle is not mentioned, defaults to: + // social_handle = "(unknown)"; + + // now that the members have been set, + // any other code can follow... + print(); + } + + // copy/move constructor/assignment + operator=: (out this, that) = { + // if neither data member is mentioned, defaults to: + // name = that.name; + // social_handle = that.social_handle; + print(); + } + + print: (this) = std::cout << "value is [(name)$] [(social_handle)$]\n"; +} + +// The above definition of mytype allows all of the following... +main: () = { + x: mytype = "Jim"; // construct from string + x = "John"; // assign from string + y := x; // copy construct + y = x; // copy assign + z := (move x); // move construct + z = (move y); // move assign + x.print(); // [] [] - moved from + y.print(); // [] [] - moved from +} +``` -- The most general form of `operator=` is `operator=: (out this, that)`. It 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 above for details). +> Note: This makes memberwise semantics symmetric for construction and assignment. In Cpp1, only non-copy/move constructors have a default, which is to initialize a member with its default initializer. In Cpp2, both constructors and assignment operators default to using the default initializer for if it's a conversion function (non-`that`, aka non-copy/move), and using memberwise `member = that.member;` for copy/move functions. -- All copy/move/comparison operator= functions are memberwise by default in Cpp2, including memberwise construction and assignment when you write them yourself. -- 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. ## `operator<=>` — Unified comparisons diff --git a/docs/cpp2/variables.md b/docs/cpp2/variables.md new file mode 100644 index 0000000000..435ef7198f --- /dev/null +++ b/docs/cpp2/variables.md @@ -0,0 +1,8 @@ +## Overview + +TODO + +## Guaranteed initialization + +TODO + diff --git a/docs/cppfront.md b/docs/cppfront.md deleted file mode 100644 index 8553d0dc58..0000000000 --- a/docs/cppfront.md +++ /dev/null @@ -1,5 +0,0 @@ - -# Cppfront reference - -TODO - diff --git a/mkdocs.yml b/mkdocs.yml index 395dd2bc99..dd8ec2c092 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,19 +54,18 @@ nav: - 'Hello, world!': welcome/hello-world.md - 'Adding cppfront to your existing C++ project': welcome/integration.md - 'Cpp2 reference': - - 'Overview': cpp2.md - 'Common programming concepts': cpp2/common.md - 'Common expressions': cpp2/expressions.md - 'Declaration syntax': cpp2/declarations.md - - 'Functions & variables': cpp2/functions-and-variables.md + - 'Variables and initialization': cpp2/variables.md + - 'Functions': cpp2/functions-and-variables.md - 'Types': cpp2/types.md - 'Metafunctions & reflection API': cpp2/metafunctions.md - 'Namespaces': cpp2/namespaces.md - 'Aliases': cpp2/aliases.md - 'Modules': cpp2/modules.md - 'Cppfront reference': - - 'Overview': cppfront.md - - 'Using Cpp1 (today''s C++) and Cpp2 in the same source file': cppfront/mixed.md + - 'Using Cpp1 (today''s syntax) and Cpp2 in the same source file': cppfront/mixed.md - 'Cppfront command line options': cppfront/options.md markdown_extensions: From bbc25eb52ab2ba03f66a3ae431f8b45ce99dd764 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Thu, 15 Feb 2024 13:03:30 -1000 Subject: [PATCH 28/64] Add keywords, objects, and heap allocation --- docs/cpp2/common.md | 14 ++++++ docs/cpp2/declarations.md | 41 ++++++++++-------- docs/cpp2/functions-and-variables.md | 4 +- docs/cpp2/metafunctions.md | 4 +- docs/cpp2/objects.md | 64 ++++++++++++++++++++++++++++ docs/cpp2/types.md | 4 +- docs/cpp2/variables.md | 8 ---- docs/index.md | 7 ++- docs/welcome/hello-world.md | 11 +++-- mkdocs.yml | 4 +- 10 files changed, 123 insertions(+), 38 deletions(-) create mode 100644 docs/cpp2/objects.md delete mode 100644 docs/cpp2/variables.md diff --git a/docs/cpp2/common.md b/docs/cpp2/common.md index 7a976e9be7..0ecab7b80a 100644 --- a/docs/cpp2/common.md +++ b/docs/cpp2/common.md @@ -15,6 +15,20 @@ The usual `// line comments` and `/* stream comments */` are supported. For exam */ ``` + +## Reserved keywords + +Cpp2 has very few globally reserved keywords; nearly all keywords are contextual, where they have their special meaning when they appear in a particular place in the grammar. For example: + +- `new` is used as an ordinary function to do allocation (e.g., `shared.new(1, 2, 3)`). + +- `struct` and `enum` are used as function names in the metafunctions library. + +- `type` can be used as an ordinary name (e.g., `std::common_type::type`). + +In rare cases, usually when consuming code written in other languages, you may need to write a name that is a reserved keyword. The way to do that is to prefix it with `__identifer__`, which treats it as an ordinary identifier (without the prefix). + + ## Fundamental data types Cpp2 supports the same fundamental types as today's Cpp1, but additionally provides the following aliases in namespace `cpp2`: diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index ff3b57a889..9d18b98061 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -5,38 +5,41 @@ All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. - The `:` is pronounced **"is a."** + - The `=` is pronounced **"defined as."** + - The _statement_ is typically an expression statement (e.g., `a + b();`) or a compound statement (e.g., `{ /*...*/ return c(d) / e; }`). + - Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted entirely to accept a default (e.g., `x: int = 0;` can be equivalently written `x: _ = 0;` or `x := 0;` both of which deduce the type). ## Examples ``` cpp title="Example: Consistent declarations — name : kind = statement" -// n is a namespace defined as the following scope +// n is a namespace defined as the following scope n: namespace = { - // shape is a type defined as the following scope + // shape is a type defined as the following scope shape: type = { - // points is an object of type std::vector, - // defined as having an empty default value - // (type-scope objects are private by default) + // points is an object of type std::vector, + // defined as having an empty default value + // (type-scope objects are private by default) points: std::vector = (); - // draw is a function taking 'this' and 'canvas' parameters - // and returning bool, defined as the following body - // (type-scope functions are public by default) - // - this is as if 'this: shape', an object of type shape - // - where is an object of type canvas + // draw is a function taking 'this' and 'canvas' parameters + // and returning bool, defined as the following body + // (type-scope functions are public by default) + // - this is as if 'this: shape', an object of type shape + // - where is an object of type canvas draw: (this, where: canvas) -> bool = { - // pen is an object of deduced (omitted) type 'color', - // defined as having initial value 'color::red' + // pen is an object of deduced (omitted) type 'color', + // defined as having initial value 'color::red' pen := color::red; - // success is an object of deduced (omitted) type bool, - // defined as having initial value 'false' + // success is an object of deduced (omitted) type bool, + // defined as having initial value 'false' success := false; // ... @@ -44,18 +47,20 @@ n: namespace return success; } - // count is a function taking 'this' and returning a type - // deduced from its body, defined as a single-expression body + // count is a function taking 'this' and returning a type + // deduced from its body, defined as a single-expression body count: (this) = points.ssize(); - // ... + // ... } - // color is an @enum type (described later) + // color is an @enum type (see Note) color: @enum type = { red; green; blue; } } ``` +> Note: `@enum` is a metafunction, which provides an easy way to opt into a group of defaults, constraints, and generated functions. For details, see [`@enum`, `@flag_enum`](metafunctions.md/#enum-flag_enum) + ## `requires` constraints diff --git a/docs/cpp2/functions-and-variables.md b/docs/cpp2/functions-and-variables.md index da8ac5076e..39c880d06a 100644 --- a/docs/cpp2/functions-and-variables.md +++ b/docs/cpp2/functions-and-variables.md @@ -1,5 +1,5 @@ -# Functions and variables +# Functions ## Overview @@ -9,7 +9,7 @@ TODO TODO -All parameters and other objects in Cpp2 are `const` by default, except for local variables. For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). +All parameters and other objects in Cpp2 are `const` by default, except for local variables. For details, see [Design note: `const` objects by default](https://github.com/hsutter/cppfront/wiki/Design-note%3A-const-objects-by-default). ## Control flow diff --git a/docs/cpp2/metafunctions.md b/docs/cpp2/metafunctions.md index c0fd131b6e..7407794d29 100644 --- a/docs/cpp2/metafunctions.md +++ b/docs/cpp2/metafunctions.md @@ -4,14 +4,16 @@ A metafunction is a compile-time function that can participate in interpreting the meaning of a declaration, and can: - apply defaults (e.g., `interface` makes functions virtual by default) + - enforce constraints (e.g., `value` enforces that the type has no virtual functions) + - generate additional functions and other code (e.g., `value` generates copy/move/comparison operations for a type if it didn't write them explicitly) The most important thing about metafunctions is that they are not hardwired language features — they are compile-time library code that uses the reflection and code generation API, that lets the author of an ordinary 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. ## Applying metafunctions -Using a metafunctionis always opt-in, by writing `@name` afer the `:` of a declaration, where `name` is the name of the metafunctions. This lets the type author declare (and the human reader see) the intent up front: "This isn't just any `type`, this is a `@value type`" which automatically gives the type default/copy/move construction and assignment, `<=>` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions: +Metafunctions provide an easy way for a type author to opt into a group of defaults, constraints, and generated functions: Just write `@name` afer the `:` of a declaration, where `name` is the name of the metafunctions. This lets the type author declare (and the human reader see) the intent up front: "This isn't just any `type`, this is a `@value type`" which automatically gives the type default/copy/move construction and assignment, `<=>` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions: ``` cpp title="Example: Using the value metafunction when writing a type" point2d: @value type = { diff --git a/docs/cpp2/objects.md b/docs/cpp2/objects.md new file mode 100644 index 0000000000..310b750a0c --- /dev/null +++ b/docs/cpp2/objects.md @@ -0,0 +1,64 @@ +## Overview + +An object can be declared at any scope: in a namespace, in a `type`, in a function, in an expression. + +Its declaration is written using the same **name `:` kind `=` value** [declaration syntax](../cpp2/declarations.md) as everything in Cpp2: + +- **name** starts with a letter and is followed by other letters, digits, or `_`. Examples: `count`, `skat_game`, `Point2D` are valid names. + +- **kind** is the object's type. In most places, except type scopes, you can write the `_` wildcard as the type (or omit the type entirely) to ask for the type to be deduced. When the type is a template and the templated arguments can be inferred from the constructor (via [CTAD](../welcome/hello-world.md#CTAD)). + +- **value** is the object's initial value. To use the default-constructed value, write `()`. + + +For example: + +``` cpp title="Example: Declaring some objects" +// numbers is an object of type std::vector, +// defined as having the initial contents 1, 2, 3 +numbers: std::vector = (1, 2, 3); +numbers: std::vector = (1, 2, 3); // same, deducing the vector's type + +// count is an object of type int, defined as having initial value -1 +count: int = -1; +count: _ = -1; // same, deducing the object's type with the _ wildcard +count := -1; // same, deducing the object's type by just omitting it +``` + + +## Heap objects + +Objects can also be allocated on the heap using `arena.new (/*initializer, arguments)` where `arena` is any object that acts as a memory arena and provides a `.new` function template. Two memory arena objects are provided in namespace `cpp2`: + +- `unique.new` calls `std::make_unique` and returns a `std::unique_ptr`. + +- `shared.new` calls `std::make_shared` and returns a `std::shared_ptr`. + +The default is `unique.new` if you don't specify an arena object. + +For example (see [types](types.md) for more details about writing types): + + +``` cpp title="Example: Heap allocation" +f: () -> std::shared_ptr += { + // Dynamically allocate an object owned by a std::unique_ptr + // 'vec' is a unique_ptr> containing three values + vec := new>(1, 2, 3); + // shorthand for 'unique.new<...>(...)' + std::cout << vec*.ssize(); // prints 3 + + // Dynamically allocate an object with shared ownership + wid := cpp2::shared.new(); + store_a_copy( wid ); // store a copy of 'wid' somewhere + return wid; // and move-return a copy too + +} // as always in C++, vec is destroyed here automatically, which + // destroys the heap vector and deallocates its dynamic memory +``` + + +## Guaranteed initialization + +TODO + diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index 326afb18c9..cfc9d2faac 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -3,7 +3,7 @@ ## Overview -A user-defined `type` is written using the same **name `:` kind `=` value** syntax as everything in Cpp2. The type's "value" is a `{}`-enclosed body containing more declarations. +A user-defined `type` is written using the same **name `:` kind `=` value** [declaration syntax](../cpp2/declarations.md) as everything in Cpp2. The type's "value" is a `{}`-enclosed body containing more declarations. In a `type`, data members are private by default, and functions and nested types are public by default. To explicitly declare a type scope declaration `public`, `protected`, or `private`, write that keyword at the beginning of the declaration. @@ -61,7 +61,7 @@ Base types are written as members named this. For example, just as a type could 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 subobject, so that it naturally outlives the base subobject. -> Cpp2 code doesn't need workarounds like Boost's `base_from_member`, because all of the motivating examples for that can be written directly. See my comments on [cppfront issue #334](https://github.com/hsutter/cppfront/issues/334) for details. +> Cpp2 code doesn't need workarounds like Boost's `base_from_member`, because all of the motivating examples for that can be written directly. See [this explanation](https://github.com/hsutter/cppfront/issues/334#issuecomment-1500984173) for details. ## `virtual`, `override`, and `final` — Virtual functions diff --git a/docs/cpp2/variables.md b/docs/cpp2/variables.md deleted file mode 100644 index 435ef7198f..0000000000 --- a/docs/cpp2/variables.md +++ /dev/null @@ -1,8 +0,0 @@ -## Overview - -TODO - -## Guaranteed initialization - -TODO - diff --git a/docs/index.md b/docs/index.md index 100018bfdd..29e8a976df 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,9 +13,9 @@ My goal is to try to prove that Stroustrup is right: that it's possible and desi We can't make an improvement that large to C++ via gradual evolution to today's syntax, because some important changes would require changing the meaning of code written in today's syntax. For example, we can never change a language feature default in today's syntax, not even if the default creates a security vulnerability pitfall, because changing a default would break vast swathes of existing code. Having a distinct alternative syntax gives us a "bubble of new code" that doesn't exist today, and have: -- **Freedom to make any desired improvement, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default when using "syntax 2." Examples: Writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer. You can always "break the glass" when needed to violate the guidance, but has to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. For details, see [Design note: unsafe code](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unsafe-code). +- **Freedom to make any desired improvement, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default when using "syntax 2." Examples: Writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer. You can always "break the glass" when needed to violate the guidance, but has to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. For details, see [Design note: Unsafe code](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unsafe-code). -- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object/namespace/etc. written in either syntax is always still just a normal C++ type/function/object/namespace/etc., so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. +- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object/namespace written in either syntax is always still just a normal C++ type/function/object/namespace, so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. **What it isn't.** Cpp2 is not a successor or alternate language with its own divergent or incompatible ecosystem. For example, it does not have its own nonstandard incompatible modules/concepts/etc. that compete with the Standard C++ features; and it does not replace your Standard C++ compiler and other tools. @@ -52,5 +52,8 @@ clang++ cppfront.cpp -std=c++20 -o cppfront That's it! +### ➤ Next: [Hello, world!](welcome/hello-world.md) + + [^simpler]: I'd ideally love to obsolete ~90% of my own books. I know that Cpp2 can eliminate that much of the C++ guidance I've personally had to write and teach over the past quarter century, by removing inconsistencies and pitfalls and gotchas, so that they're either impossible to write or are compile-time errors (either way, we don't have to teach them). I love writing C++ code... I just want it to be easier and safer by default. diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md index 7127ab6aee..54a3726390 100644 --- a/docs/welcome/hello-world.md +++ b/docs/welcome/hello-world.md @@ -24,7 +24,7 @@ hello: (msg: std::string_view) = This short program code already illustrates a few Cpp2 essentials. -**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 type/function/object/namespace/etc. declarations use the unambiguous and context-free syntax **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced **"defined as."** +**Consistent context-free syntax.** Cpp2 is designed so that there is one general way to spell a given thing, that works consistently everywhere. All Cpp2 types/functions/objects/namespaces are written using the unambiguous and context-free [declaration syntax](../cpp2/declarations.md) **"_name_ `:` _kind_ `=` _statement_"**. The `:` is pronounced **"is a,"** and the `=` is pronounced **"defined as."** - `main` **is a** function that takes no arguments and returns nothing, and is **defined as** the code body shown. @@ -36,7 +36,9 @@ All grammar is context-free. In particular, we (the human reading the code, and **Simple, safe, and efficient by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, automatic move from last use, and more. -- Declaring `words` uses **"CTAD"** (C++'s normal constructor template argument deduction) to declare its `words` variable. + + +- Declaring `words` uses **"CTAD"** (C++'s normal [constructor template argument deduction](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)) to declare its `words` variable. - Calling `words[0]` and `words[1]` is **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. @@ -96,7 +98,7 @@ Here we can see more of how Cpp2 makes it features work. **How: Simple, safe, and efficient by default.** -- **Line 9: CTAD** just works, because it turns into ordinary C++ code which is CTAD-aware. +- **Line 9: CTAD** just works, because it turns into ordinary C++ code which already supports CTAD. - **Lines 10-11: Automatic bounds checking** is added to `words[0]` and `words[1]` nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::ssize`-aware, when you use them from safe Cpp2 code. - **Line 11: Automatic move from last use** ensures the last use of `words` will automatically avoid a copy if it's being passed to something that's optimized for rvalues. - **Line 16: String interpolation** performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `bool`, to print `false` and `true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). @@ -143,4 +145,7 @@ Hello, world! ``` +### ➤ Next: [Adding cppfront to your existing C++ project](integration.md) + + [^clean-cpp1]: For presentation purposes, this documentation generally shows the `.cpp` as generated when using cppfront's `-c` (short for `-clean-cpp1`), which suppresses extra information cppfront normally emits in the `.cpp` to light up C++ tools (e.g., to let IDEs integrate cppfront error message output, debuggers step to the right lines in Cpp2 source code, and so forth). In normal use, you won't need `-c`. diff --git a/mkdocs.yml b/mkdocs.yml index dd8ec2c092..af69dea70a 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -15,7 +15,7 @@ # the documentation, leave the server process running and the browser # pages will auto-reload as you save edits. # -site_name: "Cpp2 and cppfront — An experimental 'C++ syntax 2', and its first compiler" +site_name: "Cpp2 and cppfront — An experimental 'C++ syntax 2' and its first compiler" theme: name: material features: @@ -57,7 +57,7 @@ nav: - 'Common programming concepts': cpp2/common.md - 'Common expressions': cpp2/expressions.md - 'Declaration syntax': cpp2/declarations.md - - 'Variables and initialization': cpp2/variables.md + - 'Objects, initialization, and memory': cpp2/objects.md - 'Functions': cpp2/functions-and-variables.md - 'Types': cpp2/types.md - 'Metafunctions & reflection API': cpp2/metafunctions.md From b7c2cd12bcb99843686c2db85737b86d2aa272c7 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Fri, 16 Feb 2024 15:45:34 -0800 Subject: [PATCH 29/64] Add `main` --- docs/cpp2/common.md | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/docs/cpp2/common.md b/docs/cpp2/common.md index 0ecab7b80a..ac4cee0ef6 100644 --- a/docs/cpp2/common.md +++ b/docs/cpp2/common.md @@ -1,5 +1,41 @@ # Common programming concepts +## `main` + +As always, `main` is the entry point of the program. For example: + +`main` can have either: + +- No parameters: `main: () /*etc.*/` + +- One parameter of implicit type named `args`: `main: (args) /*etc*/`. + - The type of `args` cannot be explicitly specified. It is always `cpp2::args_t`, which behaves similarly to a `const std::array`. + - Using `args` performs zero heap allocations. Every `string_view` is directly bound to the string storage provided by host environment. + - `args.argc` and `args.argv` dditionally provide access to the raw C/C++ `main` parameters. + +``` cpp title="Examples: main with (args)" +// Print out command line arguments, then invoke +// a Qt event loop for a non-UI Qt application +main: (args) -> int += { + for args do (arg) { + std::cout << arg << "\n"; + } + + app: QCoreApplication = (args.argc, args.argv); + return app.exec(); +} +``` + +`main` can return: + +- `void`, the default return value for functions. In this case, the compiled Cpp1 code returns an `int` (as required by the standard) with value `0`. + +- `int`. If the body has no `return` statement, the default is to `return 0;` at the end of the function body. + +- Some other type that your Cpp1 compiler(s) supports as a nonstandard extension. + + ## Comments The usual `// line comments` and `/* stream comments */` are supported. For example: @@ -138,6 +174,7 @@ Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~ For more details, see [Design note: Postfix unary operators vs binary operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-unary-operators-vs-binary-operators). + ## Binary operators Binary operators are the same as in Cpp1. From highest to lowest precedence: From 6796af1f53754f764d15bd56b8df0718f78cb5d7 Mon Sep 17 00:00:00 2001 From: gregmarr Date: Fri, 16 Feb 2024 18:53:05 -0500 Subject: [PATCH 30/64] Reviewing cppfront docs (#982) * Reviewing cppfront docs * integrations.md * expressions and objects --- docs/cpp2/expressions.md | 6 +++--- docs/cpp2/objects.md | 2 +- docs/index.md | 4 ++-- docs/welcome/hello-world.md | 12 ++++++------ docs/welcome/integration.md | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md index 4363722076..6bc78f2506 100644 --- a/docs/cpp2/expressions.md +++ b/docs/cpp2/expressions.md @@ -40,11 +40,11 @@ For details, see [Design note: Explicit discard](https://github.com/hsutter/cppf ## `is` — safe type/value queries -An `x is C` expression allows safe type and value queries, and evaluates to `true` if `x` matches constraint `C`. It supports both static and dynamic queries, including customization with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. +An `x is C` expression allows safe type and value queries, and evaluates to `true` if `x` matches constraint `C`. It supports both static and dynamic queries, including customization, with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. There are two kinds of `is`: -- A **type query**, where `C` is a type constraint: A type, a template name, a concept, or a type predicate. Here `x` may be a type, or an object or expression; if it is an object or expression, the query refers to `x`'s type. +- A **type query**, where `C` is a type constraint: a type, a template name, a concept, or a type predicate. Here `x` may be a type, or an object or expression; if it is an object or expression, the query refers to `x`'s type. | Type constraint kind | Example | |---|---| @@ -53,7 +53,7 @@ There are two kinds of `is`: | Static template type query | `x is std::vector` | | Static concept query | `x is std::integral` | -- A **value query**, where `C` is a value constraint: A value, or a value predicate. Here `x` must be an object or expression. +- A **value query**, where `C` is a value constraint: a value, or a value predicate. Here `x` must be an object or expression. | Value constraint kind | Example | |---|---| diff --git a/docs/cpp2/objects.md b/docs/cpp2/objects.md index 310b750a0c..bc162a1526 100644 --- a/docs/cpp2/objects.md +++ b/docs/cpp2/objects.md @@ -6,7 +6,7 @@ Its declaration is written using the same **name `:` kind `=` value** [declarati - **name** starts with a letter and is followed by other letters, digits, or `_`. Examples: `count`, `skat_game`, `Point2D` are valid names. -- **kind** is the object's type. In most places, except type scopes, you can write the `_` wildcard as the type (or omit the type entirely) to ask for the type to be deduced. When the type is a template and the templated arguments can be inferred from the constructor (via [CTAD](../welcome/hello-world.md#CTAD)). +- **kind** is the object's type. In most places, except type scopes, you can write the `_` wildcard as the type (or omit the type entirely) to ask for the type to be deduced. When the type is a template, the templated arguments can be inferred from the constructor (via [CTAD](../welcome/hello-world.md#CTAD)). - **value** is the object's initial value. To use the default-constructed value, write `()`. diff --git a/docs/index.md b/docs/index.md index 29e8a976df..498526c32e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,9 +13,9 @@ My goal is to try to prove that Stroustrup is right: that it's possible and desi We can't make an improvement that large to C++ via gradual evolution to today's syntax, because some important changes would require changing the meaning of code written in today's syntax. For example, we can never change a language feature default in today's syntax, not even if the default creates a security vulnerability pitfall, because changing a default would break vast swathes of existing code. Having a distinct alternative syntax gives us a "bubble of new code" that doesn't exist today, and have: -- **Freedom to make any desired improvement, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make it the default when using "syntax 2." Examples: Writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer. You can always "break the glass" when needed to violate the guidance, but has to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. For details, see [Design note: Unsafe code](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unsafe-code). +- **Freedom to make any desired improvement, without breaking any of today's code.** Cpp2 is designed to take all the consensus C++ best-practices guidance we already teach, and make them the default when using "syntax 2." Examples: Writing unsafe type casts is just not possible in Cpp2 syntax; and Cpp2 can change language defaults to make them simpler and safer. You can always "break the glass" when needed to violate the guidance, but you have to opt out explicitly to write unsafe code, so if the program has a bug you can grep for those places to look at first. For details, see [Design note: unsafe code](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Unsafe-code). -- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object/namespace written in either syntax is always still just a normal C++ type/function/object/namespace, so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source files that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. +- **Perfect link compatibility always on, perfect source compatibility always available (but you pay for it only if you use it).** Any type/function/object/namespace written in either syntax is always still just a normal C++ type/function/object/namespace, so any code or library written in either Cpp2 or today's C++ syntax ("Cpp1" for short) can seamlessly call each other, with no wrapping/marshaling/thunking. You can write a "mixed" source file that has both Cpp2 and Cpp1 code and get perfect backward C++ source compatibility (even SFINAE and macros), or you can write a "pure" all-Cpp2 source file and write code in a 10x simpler syntax. **What it isn't.** Cpp2 is not a successor or alternate language with its own divergent or incompatible ecosystem. For example, it does not have its own nonstandard incompatible modules/concepts/etc. that compete with the Standard C++ features; and it does not replace your Standard C++ compiler and other tools. diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md index 54a3726390..6d7fd08eab 100644 --- a/docs/welcome/hello-world.md +++ b/docs/welcome/hello-world.md @@ -40,9 +40,9 @@ All grammar is context-free. In particular, we (the human reading the code, and - Declaring `words` uses **"CTAD"** (C++'s normal [constructor template argument deduction](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)) to declare its `words` variable. -- Calling `words[0]` and `words[1]` is **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()`, including any in-house integer-indexed container types you already have that can easily provide `std::size()` if they don't already. +- Calling `words[0]` and `words[1]` is **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()` and `std::ssize()`, and for which `std::begin()` returns a random access iterator, including any in-house integer-indexed container types you already have that can easily provide `std::size()` and `std::ssize()` if they don't already. -- `hello` uses **string interpolation** to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, (" << msg << "!\n"`. +- `hello` uses **string interpolation** to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, " << msg << "!\n"`. **Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: @@ -50,7 +50,7 @@ All grammar is context-free. In particular, we (the human reading the code, and - We can omit the `{` `}` around single-statement function bodies, as `hello` does. -- We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), `inout` for read-write, and also `copy`, `out`, `move`, and `forward`. +- We can omit the `in` on the `msg` parameter. Cpp2 has just six ways to pass parameters: The most common ones are `in` for read-only (the default so we can omit it, as `hello` does), and `inout` for read-write. The others are `copy`, `out`, `move`, and `forward`. For details, see [Design note: Defaults are one way to say the same thing](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Defaults-are-one-way-to-say-the-same-thing). @@ -58,7 +58,7 @@ For details, see [Design note: Defaults are one way to say the same thing](https **Seamless compatibility and interop.** We can just use `std::cout` and `std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using ordinary direct calls without any wrapping/marshaling/thunking. -**C++ standard library always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`). Cppfront is regularly updated to be compatible with C++23 and the latest draft C++26 library additions as soon as the ISO C++ committee votes them into the C++26 working draft, so as soon as you have a C++ implementation that has a new standard (or bleeding-edge draft standard!) C++ library feature, you'll be able to fully use it in Cpp2 code. +**C++ standard library is always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`), or if you use `-im` (short for `-import-std`). Cppfront is regularly updated to be compatible with C++23 and the latest draft C++26 library additions as soon as the ISO C++ committee votes them into the C++26 working draft, so as soon as you have a C++ implementation that has a new standard (or bleeding-edge draft standard!) C++ library feature, you'll be able to fully use it in Cpp2 code. ## Building `hello.cpp2` @@ -99,7 +99,7 @@ Here we can see more of how Cpp2 makes it features work. **How: Simple, safe, and efficient by default.** - **Line 9: CTAD** just works, because it turns into ordinary C++ code which already supports CTAD. -- **Lines 10-11: Automatic bounds checking** is added to `words[0]` and `words[1]` nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::ssize`-aware, when you use them from safe Cpp2 code. +- **Lines 10-11: Automatic bounds checking** is added to `words[0]` and `words[1]` nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::size` and `std::ssize`-aware, when you use them from safe Cpp2 code. - **Line 11: Automatic move from last use** ensures the last use of `words` will automatically avoid a copy if it's being passed to something that's optimized for rvalues. - **Line 16: String interpolation** performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `bool`, to print `false` and `true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). @@ -148,4 +148,4 @@ Hello, world! ### ➤ Next: [Adding cppfront to your existing C++ project](integration.md) -[^clean-cpp1]: For presentation purposes, this documentation generally shows the `.cpp` as generated when using cppfront's `-c` (short for `-clean-cpp1`), which suppresses extra information cppfront normally emits in the `.cpp` to light up C++ tools (e.g., to let IDEs integrate cppfront error message output, debuggers step to the right lines in Cpp2 source code, and so forth). In normal use, you won't need `-c`. +[^clean-cpp1]: For presentation purposes, this documentation generally shows the `.cpp` as generated when using cppfront's `-c` (short for `-clean-cpp1`), which suppresses extra information cppfront normally emits in the `.cpp` to light up C++ tools (e.g., to let IDEs integrate cppfront error message output, debuggers step to the right lines in Cpp2 source code, and so forth). In normal use, you won't need or even want `-c`. diff --git a/docs/welcome/integration.md b/docs/welcome/integration.md index 136f06e89d..df3279f3d9 100644 --- a/docs/welcome/integration.md +++ b/docs/welcome/integration.md @@ -37,7 +37,7 @@ That's enough to enable builds, and the IDE just picks up the rest from the `.cp - **The cppfront error messages in `filename(line, col)` format.** Most C++ IDEs recognize these, and usually automatically merge any diagnostic output wherever compiler error output normally appears. If your IDE prefers `filename:line:col`, just use the cppfront `-format-colon-errors` command line option. -- **The `#line` directives cppfront emits in the generated `.cpp` file.** Most C++ debuggers recognize these and will know to step through the `.cpp2` file. Note that `#line` emission is on by default, but if you choose `-c` (short for `-clean-cpp1`) these will be suppressed and then the debugger will step through the generated C++ code instead. +- **The `#line` directives cppfront emits in the generated `.cpp` file.** Most C++ debuggers recognize these and will know to step through the `.cpp2` file. Note that `#line` emission is on by default, but if you choose `-c` (short for `-clean-cpp1`) these will be suppressed and then the debugger will step through the generated C++ code instead. If your debugger can't find the files, you may need to use `-line-paths` to have absolute paths instead of relative paths in the `#line` directives. - **Regardless of syntax, every type/function/object/namespace/etc. is still just an ordinary C++ type/function/object/namespace/etc.** Most C++ debugger visualizers will just work and show beautiful output for the types your program uses, including to use any in-the-box visualizers for all the `std::` types (since those are used directly as usual) and any custom visualizers you may have already written for your own types or popular library types. From 4ee9a3629d6f65da7ed7e1bde87f8324c4635a41 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Fri, 16 Feb 2024 15:53:57 -0800 Subject: [PATCH 31/64] Fix "lines 8, 9, and 15" wording --- docs/welcome/hello-world.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md index 6d7fd08eab..feab6c2621 100644 --- a/docs/welcome/hello-world.md +++ b/docs/welcome/hello-world.md @@ -94,7 +94,7 @@ Here we can see more of how Cpp2 makes it features work. **How: Consistent context-free syntax.** -- **Lines 8, 9, and 15: Portable C++20 code** we can build with any C++ compiler. Cpp2's context-free syntax converts directly to today's Cpp1 syntax. We can write and read our C++ types/functions/objects in simpler Cpp2 syntax without wrestling with context sensitivity and ambiguity, and they're all still just ordinary types/functions/objects. +- **All compiled lines are portable C++20 code** we can build with pretty much any C++ compiler released circa 2019 or later. Cpp2's context-free syntax converts directly to today's Cpp1 syntax. We can write and read our C++ types/functions/objects in simpler Cpp2 syntax without wrestling with context sensitivity and ambiguity, and they're all still just ordinary types/functions/objects. **How: Simple, safe, and efficient by default.** From 1b7017a0585e22708553f308ad2062d3b42f6dbb Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sat, 17 Feb 2024 16:05:50 -0800 Subject: [PATCH 32/64] Mention UFCS --- ...unctions-and-variables.md => functions.md} | 21 +++++++++++++++++++ mkdocs.yml | 2 +- 2 files changed, 22 insertions(+), 1 deletion(-) rename docs/cpp2/{functions-and-variables.md => functions.md} (56%) diff --git a/docs/cpp2/functions-and-variables.md b/docs/cpp2/functions.md similarity index 56% rename from docs/cpp2/functions-and-variables.md rename to docs/cpp2/functions.md index 39c880d06a..6fb0bc78d1 100644 --- a/docs/cpp2/functions-and-variables.md +++ b/docs/cpp2/functions.md @@ -5,6 +5,27 @@ TODO +## Calling functions: Nonmember function syntax, and UFCS syntax + +A function call like `f(x)` is a normal non-member function call. It will call non-member functions only. + +A function call like `x.f()` is a unified function call syntax (aka UFCS) call. It will call a member function if one is available, and otherwise will call `f(x)`. Having UFCS is important for generic code that may want to call a member or a non-member function, whichever is available. It's also important to enable fluid programming styles and natural IDE autocompletion support. + +For example: + +``` cpp title="Example: Function calls" +f: ( v : std::vector ) = { + + // This calls std::vector::size() + std::cout << v.size(); + + // This calls std::ssize(v), because v + // doesn't have a .ssize member function + std::cout << v.ssize(); +} +``` + + ## Parameter passing TODO diff --git a/mkdocs.yml b/mkdocs.yml index af69dea70a..2933ff32a3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -58,7 +58,7 @@ nav: - 'Common expressions': cpp2/expressions.md - 'Declaration syntax': cpp2/declarations.md - 'Objects, initialization, and memory': cpp2/objects.md - - 'Functions': cpp2/functions-and-variables.md + - 'Functions': cpp2/functions.md - 'Types': cpp2/types.md - 'Metafunctions & reflection API': cpp2/metafunctions.md - 'Namespaces': cpp2/namespaces.md From d8d8a5a300bd98eca3b641d65b93c8819ef7b216 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sun, 18 Feb 2024 14:21:51 -0800 Subject: [PATCH 33/64] Function outputs and explicit discard --- docs/cpp2/common.md | 4 +-- docs/cpp2/functions.md | 58 ++++++++++++++++++++++++++++++++++++++++-- docs/cpp2/objects.md | 3 ++- 3 files changed, 60 insertions(+), 5 deletions(-) diff --git a/docs/cpp2/common.md b/docs/cpp2/common.md index ac4cee0ef6..8fb6d5136d 100644 --- a/docs/cpp2/common.md +++ b/docs/cpp2/common.md @@ -8,10 +8,10 @@ As always, `main` is the entry point of the program. For example: - No parameters: `main: () /*etc.*/` -- One parameter of implicit type named `args`: `main: (args) /*etc*/`. +- One parameter of implicit type named `args`: `main: (args) /*etc.*/` - The type of `args` cannot be explicitly specified. It is always `cpp2::args_t`, which behaves similarly to a `const std::array`. - Using `args` performs zero heap allocations. Every `string_view` is directly bound to the string storage provided by host environment. - - `args.argc` and `args.argv` dditionally provide access to the raw C/C++ `main` parameters. + - `args.argc` and `args.argv` additionally provide access to the raw C/C++ `main` parameters. ``` cpp title="Examples: main with (args)" // Print out command line arguments, then invoke diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index 6fb0bc78d1..0932effd73 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -25,12 +25,66 @@ f: ( v : std::vector ) = { } ``` +If the function has an output that cannot be silently discarded, you can -## Parameter passing + +## Parameters + +TODO + +> Note: All parameters and other objects in Cpp2 are `const` by default, except for local variables. For details, see [Design note: `const` objects by default](https://github.com/hsutter/cppfront/wiki/Design-note%3A-const-objects-by-default). + + +## Return values TODO -All parameters and other objects in Cpp2 are `const` by default, except for local variables. For details, see [Design note: `const` objects by default](https://github.com/hsutter/cppfront/wiki/Design-note%3A-const-objects-by-default). + +### Function outputs + +A function's outputs are its return values, and the "out" state of any `out` and `inout` parameters. + +Function outputs cannot be silently discarded. To explicitly discard a function output, assign it to `_`. For example: + +``` cpp title="Example: No silent discard" +f: () -> void = { } +g: () -> int = { return 10; } +h: (inout x: int) -> void = { x = 20; } + +main: () += { + f(); // ok, no return value + + std::cout << g(); // ok, use return value + _ = g(); // ok, explicitly discard return value + g(); // ERROR, return value is ignored + + { + x := 0; + h( x ); // ok, x is referred to again... + std::cout << x; // ... here, so its new value is used + } + + { + x := 0; + h( x ); // ok, x is referred to again... + _ = x; // ... here, its value explicitly discarded + } + + { + x := 0; + h( x ); // ERROR, this is a definite last use of x + } // so x is not referred to again, and its + // 'out' value can't be implicitly discarded +} +``` + +> Cpp2 imbues Cpp1 code with nondiscardable semantics, while staying fully compatible as usual: +> +> - A function written in Cpp2 syntax that returns something other than `void` is always compiled to Cpp1 with `[[nodiscard]]`. +> +> - A function call written in Cpp2 `x.f()` member call syntax always treats a non-`void` return type as not discardable, even if the function was written in Cpp1 syntax that did not write `[[nodiscard]]`. + ## Control flow diff --git a/docs/cpp2/objects.md b/docs/cpp2/objects.md index bc162a1526..3a38481a34 100644 --- a/docs/cpp2/objects.md +++ b/docs/cpp2/objects.md @@ -45,8 +45,9 @@ f: () -> std::shared_ptr // Dynamically allocate an object owned by a std::unique_ptr // 'vec' is a unique_ptr> containing three values vec := new>(1, 2, 3); - // shorthand for 'unique.new<...>(...)' + // shorthand for 'unique.new<...>(...)' std::cout << vec*.ssize(); // prints 3 + // note that * dereference is a suffix operator // Dynamically allocate an object with shared ownership wid := cpp2::shared.new(); From 36475b6d2607292dff8ca9441155ee221c59bf0c Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sun, 18 Feb 2024 17:16:26 -0800 Subject: [PATCH 34/64] Add definite initialization section And: - add placeholder for contracts - add some is/as side-by-side examples - add parameter passing styles --- docs/cpp2/contracts.md | 16 +++++++++ docs/cpp2/expressions.md | 22 ++++++++++++ docs/cpp2/functions.md | 26 +++++++++++--- docs/cpp2/objects.md | 74 +++++++++++++++++++++++++++++++++++++--- mkdocs.yml | 1 + 5 files changed, 129 insertions(+), 10 deletions(-) create mode 100644 docs/cpp2/contracts.md diff --git a/docs/cpp2/contracts.md b/docs/cpp2/contracts.md new file mode 100644 index 0000000000..6117c19ed3 --- /dev/null +++ b/docs/cpp2/contracts.md @@ -0,0 +1,16 @@ + +# Contracts + +## Overview + +TODO + + +## Contract groups + +TODO + + +## Customizing the violation handler + +TODO diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md index 6bc78f2506..20c93e6965 100644 --- a/docs/cpp2/expressions.md +++ b/docs/cpp2/expressions.md @@ -72,6 +72,17 @@ There are two kinds of `is`: - [`pure2-type-safety-1.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-type-safety-1.cpp2) - [`pure2-type-safety-2-with-inspect-expression.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/pure2-type-safety-2-with-inspect-expression.cpp2) +Here are some `is` queries with their Cpp1 equivalents. In this table, uppercase names are type names, lowercase names are objects, `v` is a `std::variant` where one alternative is `T`, `o` is a `std::optional`, and `a` is a `std::any`: + +| Some sample `is` queries | Cpp1 equivalent +|---|---| +| `X is Y && Y is X` | `std::is_same_v` | +| `D is B` | `std::is_base_of` | +| `pb is *D` | `dynamic_cast(pb) != nullptr` | +| `v is T` | `std::holds_alternative(v)` | +| `a is T` | `a.type() == typeid(T)` | +| `o is T` | `o.has_value()` | + ## `as` — safe casts and conversions @@ -97,6 +108,17 @@ test: (x) = { } ``` +Here are some `as` casts with their Cpp1 equivalents. In this table, uppercase names are type names, lowercase names are objects, `v` is a `std::variant` where one alternative is `T`, `o` is a `std::optional`, and `a` is a `std::any`: + +| Some sample `as` casts | Cpp1 equivalent +|---|---| +| `x as Y` | `Y{x}` | +| `pb as *D` | `dynamic_cast(pb)` | +| `v as T` | `std::get(v)` | +| `a as T` | `std::any_cast(a)` | +| `o as T` | `o.value()` | + + ## `inspect` — pattern matching An `inspect expr -> Type` expression allows pattern matching using `is`. diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index 0932effd73..fe18e517a8 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -5,7 +5,7 @@ TODO -## Calling functions: Nonmember function syntax, and UFCS syntax +## Calling functions: `f(x)` syntax, and `x.f()` UFCS syntax A function call like `f(x)` is a normal non-member function call. It will call non-member functions only. @@ -15,7 +15,6 @@ For example: ``` cpp title="Example: Function calls" f: ( v : std::vector ) = { - // This calls std::vector::size() std::cout << v.size(); @@ -23,14 +22,31 @@ f: ( v : std::vector ) = { // doesn't have a .ssize member function std::cout << v.ssize(); } -``` -If the function has an output that cannot be silently discarded, you can +// Generic function to print "hello, ___!" for any printable type +hello: (name) = { + // Using the C standard library is arguably nicer with UFCS + myfile := fopen("xyzzy.txt", "w"); + myfile.fprintf( "Hello, (name)%!\n" ); + myfile.fclose(); +} +``` ## Parameters -TODO +There are six ways to pass parameters that cover all use cases: + +| Parameter kind | "Pass me an `x` I can ______" | Accepts arguments that are | Special semantics | +|---|---|---|---| +| `in` (default) | read from | anything | always `const`

automatically passes by value if cheaply copyable | +| `copy` | take a copy of | anything | acts like a normal local variable initialized with the argument | +| `inout` | read from and write to | lvalues | | +| `out` | write to (including construct) | lvalues, including uninitialized lvalues | must `=` assign/construct before other uses | +| `move` | move from | rvalues | automatically moves from every definite last use | +| `forward` | forward | anything | automatically forwards from every definite last use | + + > Note: All parameters and other objects in Cpp2 are `const` by default, except for local variables. For details, see [Design note: `const` objects by default](https://github.com/hsutter/cppfront/wiki/Design-note%3A-const-objects-by-default). diff --git a/docs/cpp2/objects.md b/docs/cpp2/objects.md index 3a38481a34..b4f9d886d8 100644 --- a/docs/cpp2/objects.md +++ b/docs/cpp2/objects.md @@ -26,6 +26,75 @@ count := -1; // same, deducing the object's type by just omitting it ``` +## Guaranteed initialization + +Every object must be initialized using `=` before it is used. + +An object in any scope can be initialized at its declaration. For example: + +``` cpp title="Example: Initializing objects when they are declared" hl_lines="4 10" +shape: type = { + // An object at type scope (data member) + // initialized with its type's default value + points: std::vector = (); + + draw: (this, where: canvas) -> bool + = { + // An object at function scope (local variable) + // initialized with color::red + pen := color::red; + + // ... + } + + // ... +} +``` + +Additionally, at function local scope an object `obj` can be initialized separately from its declaration. This can be useful when the object must be declared before a program-meaningful initial value is known (to avoid a dead write of a wrong 'dummy' value), and/or when the object may be initialized in more than one way depending on other logic (e.g., by using different constructors on different paths). The way to do this is: + +- Declare `obj` without an initializer, such as `obj: some_type;`. This allocates stack space for the object, but does not construct it. + +- `obj` must have a definite first use on every `if`/`else` branch path, and + +- that definite first use must be of the form `obj = value;` which is a constructor call, or else pass `obj` as an `out` argument to an `out` parameter (which is also effectively a constructor call, and performs the construction in the callee). + +For example: + +``` cpp title="Example: Initializing local objects after they are declared" +f: () = { + buf: std::array; // uninitialized + // ... calculate some things ... + // ... no uses of buf here ... + buf = some_calculated_value; // constructs (not assigns) buf + // ... + std::cout buf[0]; // ok, a has been initialized +} + +g: () = { + buf: std::array; // uninitialized + if flip_coin_is_heads() { + if heads_default_is_available { + buf = copy_heads_default(); // constructs buf + } + else { + buf = (other, constructor); // constructs buf + } + } + else { + load_from_disk( out buf ); // constructs buf (*) + } + std::cout buf[0]; // ok, a has been initialized +} + +load_from_disk: (out buffer) = { + x = /* data read from disk */ ; // when `buffer` is uninitialized, +} // constructs it; otherwise, assigns +``` + +In the above example, note the simple rule for branches: The local variable must be initialized on both the `if` and `else` branches, or neither branch. + + ## Heap objects Objects can also be allocated on the heap using `arena.new (/*initializer, arguments)` where `arena` is any object that acts as a memory arena and provides a `.new` function template. Two memory arena objects are provided in namespace `cpp2`: @@ -58,8 +127,3 @@ f: () -> std::shared_ptr // destroys the heap vector and deallocates its dynamic memory ``` - -## Guaranteed initialization - -TODO - diff --git a/mkdocs.yml b/mkdocs.yml index 2933ff32a3..a179b1bdc7 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -59,6 +59,7 @@ nav: - 'Declaration syntax': cpp2/declarations.md - 'Objects, initialization, and memory': cpp2/objects.md - 'Functions': cpp2/functions.md + - 'Contracts': cpp2/contracts.md - 'Types': cpp2/types.md - 'Metafunctions & reflection API': cpp2/metafunctions.md - 'Namespaces': cpp2/namespaces.md From 9257d9701e5d6b48ff753af9b3c27dd88b90bcc0 Mon Sep 17 00:00:00 2001 From: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Date: Mon, 19 Feb 2024 11:18:04 +1000 Subject: [PATCH 35/64] Update CTAD description in hello-world.md (#984) Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> --- docs/welcome/hello-world.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md index feab6c2621..1af522b8f8 100644 --- a/docs/welcome/hello-world.md +++ b/docs/welcome/hello-world.md @@ -38,7 +38,7 @@ All grammar is context-free. In particular, we (the human reading the code, and -- Declaring `words` uses **"CTAD"** (C++'s normal [constructor template argument deduction](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)) to declare its `words` variable. +- Declaring `words` uses **"CTAD"** (C++'s normal [constructor template argument deduction](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)) to deduce the type of elements in the `vector`. - Calling `words[0]` and `words[1]` is **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()` and `std::ssize()`, and for which `std::begin()` returns a random access iterator, including any in-house integer-indexed container types you already have that can easily provide `std::size()` and `std::ssize()` if they don't already. From 6facf81aa061ab23855bbf91ea7f8aadc40e660c Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 19 Feb 2024 10:12:34 -0800 Subject: [PATCH 36/64] Embiggen text font, add `move` and `out` arguments And code block cleanup: - highlight key lines in all code blocks - remove redundant "Example:" in code block titles - add a chained comparisons example --- docs/cpp2/common.md | 10 +++++----- docs/cpp2/declarations.md | 2 +- docs/cpp2/expressions.md | 26 +++++++++++++----------- docs/cpp2/functions.md | 41 +++++++++++++++++++++++++++----------- docs/cpp2/metafunctions.md | 12 +++++------ docs/cpp2/objects.md | 10 +++++----- docs/cpp2/types.md | 17 +++++++++++----- docs/stylesheets/extra.css | 8 +++++++- mkdocs.yml | 8 ++++---- 9 files changed, 83 insertions(+), 51 deletions(-) diff --git a/docs/cpp2/common.md b/docs/cpp2/common.md index 8fb6d5136d..d5be1c484d 100644 --- a/docs/cpp2/common.md +++ b/docs/cpp2/common.md @@ -13,7 +13,7 @@ As always, `main` is the entry point of the program. For example: - Using `args` performs zero heap allocations. Every `string_view` is directly bound to the string storage provided by host environment. - `args.argc` and `args.argv` additionally provide access to the raw C/C++ `main` parameters. -``` cpp title="Examples: main with (args)" +``` cpp title="main with (args)" hl_lines="5 9" // Print out command line arguments, then invoke // a Qt event loop for a non-UI Qt application main: (args) -> int @@ -40,7 +40,7 @@ main: (args) -> int The usual `// line comments` and `/* stream comments */` are supported. For example: -``` cpp title="Example: Comments" +``` cpp title="Writing comments" // A line comment: After //, the entire // rest of the line is part of the comment @@ -98,7 +98,7 @@ Cpp2 supports the same fundamental types as today's Cpp1, but additionally provi Types can be qualified with `const` and `*`. Types are written left-to-right, so a qualifier always applies to what immediately follows it. For example, to declare a `const` pointer to a non-`const` pointer to a `const i32` object, write: -``` cpp title="Example: Type qualifiers" +``` cpp title="Using type qualifiers" // A const pointer to a non-const pointer to a const i32 object p: const * * const i32; ``` @@ -123,7 +123,7 @@ Operators have the same precedence and associativity as in Cpp1, but some unary The operators `!`, `+`, and `-` are prefix, as in Cpp1. For example: -``` cpp title="Example: Prefix operators" +``` cpp title="Using prefix operators" if !vec.empty() { vec.emplace_back( -123.45 ); } @@ -137,7 +137,7 @@ if !vec.empty() { The operators `.`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, and `$` are postfix. For example: -``` cpp title="Example: Postfix operators" +``` cpp title="Using postfix operators" // Cpp1 examples, from cppfront's own source code: // address = &(*tokens)[pos + num]; // is_void = *(*u)->identifier == "void"; diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index 9d18b98061..74427e7b49 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -15,7 +15,7 @@ All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. ## Examples -``` cpp title="Example: Consistent declarations — name : kind = statement" +``` cpp title="Consistent declarations — name : kind = statement" hl_lines="2 5 10 17 21 25 34 40" // n is a namespace defined as the following scope n: namespace = { diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md index 20c93e6965..aef4a8196f 100644 --- a/docs/cpp2/expressions.md +++ b/docs/cpp2/expressions.md @@ -5,7 +5,7 @@ `_` is pronounced **"don't care"** and allowed as a wildcard in most contexts. For example: -``` cpp title="Example: _ wildcard" +``` cpp title="Using the _ wildcard" hl_lines="2 5 11" // We don't care about the guard variable's name _ : std::lock_guard = mut; @@ -22,7 +22,7 @@ return inspect v -> std::string { Cpp2 treats all function outputs (return values, and results produced via `inout` and `out` parameters) as important, and does not let them be silently discarded by default. To explicitly discard such a value, assign it to `_`. For example: -``` cpp title="Example: Using _ for explicit discard" +``` cpp title="Using _ for explicit discard" hl_lines="1 8" _ = vec.emplace_back(1,2,3); // "_ =" is required to explicitly discard emplace_back's // return value (which is non-void since C++17) @@ -88,7 +88,7 @@ Here are some `is` queries with their Cpp1 equivalents. In this table, uppercase An `x as T` expression allows safe type casts. `x` must be an object or expression, and `T` must be a type. It supports both static and dynamic typing, including customization with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. For example: -``` cpp title="Example: Using as" +``` cpp title="Using as" hl_lines="4 6 14" main: () = { a: std::any = 0; // a's type is now int, value 0 test(a); // prints "zero" @@ -130,19 +130,21 @@ An `inspect expr -> Type` expression allows pattern matching using `is`. For example: -``` cpp title="Example: Using inspect" +``` cpp title="Using inspect" hl_lines="6-13" // A generic function that takes an argument 'x' of any type // and inspects various things about `x` test: (x) = { forty_two := 42; - std::cout << inspect x -> std::string { - is 0 = "zero"; // == 0 - is (forty_two) = "the answer"; // == 42 - is int = "integer"; // is type int (and not 0 or 42) - is std::string = x as std::string; // is type std::string - is std::vector = "a std::vector"; // is a vector - is _ = "(no match)"; // is something else - } << "\n"; + std::cout + << inspect x -> std::string { + is 0 = "zero"; // == 0 + is (forty_two) = "the answer"; // == 42 + is int = "integer"; // is type int (and not 0 or 42) + is std::string = x as std::string; // is type std::string + is std::vector = "a std::vector"; // is a vector + is _ = "(no match)"; // is something else + } + << "\n"; } // Sample call site diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index fe18e517a8..64f151bbbc 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -7,31 +7,48 @@ TODO ## Calling functions: `f(x)` syntax, and `x.f()` UFCS syntax -A function call like `f(x)` is a normal non-member function call. It will call non-member functions only. +A function call like `f(x)` is a normal function call that will call non-member functions only, as usual in C++. A function call like `x.f()` is a unified function call syntax (aka UFCS) call. It will call a member function if one is available, and otherwise will call `f(x)`. Having UFCS is important for generic code that may want to call a member or a non-member function, whichever is available. It's also important to enable fluid programming styles and natural IDE autocompletion support. +An operator notation call like `a + b` will call an overloaded operator function if one is available, as usual in C++. + For example: -``` cpp title="Example: Function calls" +``` cpp title="Function calls" hl_lines="3 7 11 16 19 20" +// Generic function to log something +// This calls operator<< using operator notation +log: (x) = clog << x; + f: ( v : std::vector ) = { - // This calls std::vector::size() - std::cout << v.size(); + // This calls log() with the result of std::vector::size() + log( v.size() ); - // This calls std::ssize(v), because v - // doesn't have a .ssize member function - std::cout << v.ssize(); + // This calls log() with the result of std::ssize(v), because + // v doesn't have a .ssize member function + log( v.ssize() ); } // Generic function to print "hello, ___!" for any printable type hello: (name) = { - // Using the C standard library is arguably nicer with UFCS myfile := fopen("xyzzy.txt", "w"); - myfile.fprintf( "Hello, (name)%!\n" ); + // Direct calls to C nonmember functions, using UFCS and safe + // string interpolation (instead of type-unsafe format strings) + myfile.fprintf( "Hello, (name)$!\n" ); myfile.fclose(); + // The C and C++ standard libraries are not only fully available, + // but safer (and arguably nicer) when used from Cpp2 syntax code } ``` +To explicitly treat an object name passed as an argument as `move` or `out`, write that keyword before the variable name. + +- Explicit `move` is rarely needed. Every definite last use of a local variable will apply `move` by default. Writing `move` from an object before its definite last use means that later uses may see a moved-from state. + +- Explicit `out` is needed only when initializing a local variable separately from its declaration using a call to a function with an `out` parameter. For details, see [Guaranteed initialization](../cpp2/objects.md#Init). + +For example: + ## Parameters @@ -62,7 +79,7 @@ A function's outputs are its return values, and the "out" state of any `out` and Function outputs cannot be silently discarded. To explicitly discard a function output, assign it to `_`. For example: -``` cpp title="Example: No silent discard" +``` cpp title="No silent discard" hl_lines="10 11 22 27" f: () -> void = { } g: () -> int = { return 10; } h: (inout x: int) -> void = { x = 20; } @@ -84,7 +101,7 @@ main: () { x := 0; h( x ); // ok, x is referred to again... - _ = x; // ... here, its value explicitly discarded + _ = x; // ... here where its value explicitly discarded } { @@ -114,7 +131,7 @@ TODO Loops can be named using the usual **name `:`** name introduction syntax, and `break` and `continue` can refer to those names. For example: -``` cpp title="Example: Writing a simple type" +``` cpp title="Using named break and continue" hl_lines="6 10" outer: while i` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions: -``` cpp title="Example: Using the value metafunction when writing a type" +``` cpp title="Using the value metafunction when writing a type" hl_lines="1" point2d: @value type = { x: i32 = 0; y: i32 = 0; @@ -69,7 +69,7 @@ TODO Cpp2 has no `enum` feature hardwired into the language. Instead you apply the `@enum` metafunction when writing an ordinary `type`: -``` cpp title="Example: Using @enum" +``` cpp title="Using the @enum metafunction when writing a type" hl_lines="14" // 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 @@ -99,7 +99,7 @@ Unlike C `enum`, this `@enum` is scoped and strongly typed (does not implicitly Unlike C++11 `enum class`, it's "just a `type`" which means it can naturally also have member functions and other things that a type can have: -``` cpp title="Example: An @enum type with a member function" +``` cpp title="An @enum type with a member function" hl_lines="1" janus: @enum type = { past; future; @@ -113,7 +113,7 @@ janus: @enum type = { There's also a `flag_enum` variation with power-of-two semantics and an unsigned underlying type: -``` cpp title="Example: Using @flag_enum" +``` cpp title="Using the @flag_enum metafunction when writing a type" hl_lines="11" // 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 @@ -137,7 +137,7 @@ file_attributes: @flag_enum type = { `@union` declaratively opts into writing a safe discriminated union/variant dynamic type. For example: -``` cpp title="Example: Using @union" +``` cpp title="Using the @union metafunction when writing a type" hl_lines="9" // 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 @@ -173,7 +173,7 @@ Each `@union` type has its own type-safe name, has clear and unambiguous named m Because a `@union type` is still a `type`, it can naturally have other things normal types can have, such as template parameter lists and member functions: -``` cpp title="Example: A templated custom safe union type" +``` cpp title="A templated custom safe union type" hl_lines="1" name_or_other: @union type = { name : std::string; diff --git a/docs/cpp2/objects.md b/docs/cpp2/objects.md index b4f9d886d8..b2b218b1fd 100644 --- a/docs/cpp2/objects.md +++ b/docs/cpp2/objects.md @@ -13,7 +13,7 @@ Its declaration is written using the same **name `:` kind `=` value** [declarati For example: -``` cpp title="Example: Declaring some objects" +``` cpp title="Declaring some objects" // numbers is an object of type std::vector, // defined as having the initial contents 1, 2, 3 numbers: std::vector = (1, 2, 3); @@ -26,13 +26,13 @@ count := -1; // same, deducing the object's type by just omitting it ``` -## Guaranteed initialization +## Guaranteed initialization Every object must be initialized using `=` before it is used. An object in any scope can be initialized at its declaration. For example: -``` cpp title="Example: Initializing objects when they are declared" hl_lines="4 10" +``` cpp title="Initializing objects when they are declared" hl_lines="4 10" shape: type = { // An object at type scope (data member) // initialized with its type's default value @@ -61,7 +61,7 @@ Additionally, at function local scope an object `obj` can be initialized separat For example: -``` cpp title="Example: Initializing local objects after they are declared" +``` cpp title="Initializing local objects after they are declared" hl_lines="5 14 17 21" f: () = { buf: std::array; // uninitialized // ... calculate some things ... @@ -108,7 +108,7 @@ The default is `unique.new` if you don't specify an arena object. For example (see [types](types.md) for more details about writing types): -``` cpp title="Example: Heap allocation" +``` cpp title="Heap allocation" hl_lines="3-6 10-11" f: () -> std::shared_ptr = { // Dynamically allocate an object owned by a std::unique_ptr diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index cfc9d2faac..5ed8796081 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -7,7 +7,7 @@ A user-defined `type` is written using the same **name `:` kind `=` value** [dec In a `type`, data members are private by default, and functions and nested types are public by default. To explicitly declare a type scope declaration `public`, `protected`, or `private`, write that keyword at the beginning of the declaration. -``` cpp title="Example: Writing a simple type" +``` cpp title="Writing a simple type" hl_lines="1" mytype: type = { // data members are private by default @@ -40,7 +40,7 @@ The name `this` may only be used for the first parameter of a type-scope functio For example, here is how to write read-only member function named `print` that takes a read-only string value and prints this object's data value and the string message: -``` cpp title="Example: this" +``` cpp title="The this parameter" hl_lines="4 6" mytype: type = { data: i32; // some data member (private by default) @@ -73,11 +73,11 @@ A `this` parameter can additionally be declared as one of the following: - **`final`**: Writing `myfunc: (final this /*...*/)` defines a final override of an existing base class virtual function. -A pure virtual function is a function with a `virtual this` parameter and no body. +A pure virtual function is a function with a `virtual this` or `override this` parameter and no body. For example: -``` cpp title="Example: Virtual functions" +``` cpp title="Virtual functions" hl_lines="3 4 14 15" abstract_base: type = { // A pure virtual function: virtual + no body @@ -187,7 +187,7 @@ All copy/move/comparison `operator=` functions are memberwise by default in Cpp2 For example: -``` cpp title="Memberwise operator= semantics +``` cpp title="Memberwise operator= semantics" hl_lines="9-11 20-22" mytype: type = { // data members (private by default) @@ -242,5 +242,12 @@ Most of Cpp2's `operator<=>` has already been merged into ISO C++, except for al - **Invalid chains: Everything else.** Nonsense chains like `a >= b < c` and `a != b != c` are compile time errors. They are "nonsense" because they are non-transitive; these chains do not imply any relationship between `a` and `c`. +``` cpp title="Chained comparisons" hl_lines="2" +// If requested is in the range of values [lo, hi) +if lo <= requested < hi { + // ... do something ... +} +``` + For details, see [P0515 "Consistent comparison" section 3.3](https://wg21.link/p0515) and [P0893 "Chaining comparisons"](https://wg21.link/p0893). diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index e8fab55a44..2cbf8168ea 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -1,3 +1,9 @@ +/* + the default font sizes look small and encourage zooming, + which loses the navigation side panels +*/ +p { font-size: 16px; } +td { font-size: 16px; } /* todo: try to make the nav pane section labels larger @@ -6,4 +12,4 @@ to section starts are easier to see */ .md-nav__item { font-size: 20pt; } -.md-nav__link { font-size: medium; } \ No newline at end of file +.md-nav__link { font-size: medium; } diff --git a/mkdocs.yml b/mkdocs.yml index a179b1bdc7..e4bf4e6326 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -58,10 +58,10 @@ nav: - 'Common expressions': cpp2/expressions.md - 'Declaration syntax': cpp2/declarations.md - 'Objects, initialization, and memory': cpp2/objects.md - - 'Functions': cpp2/functions.md - - 'Contracts': cpp2/contracts.md - - 'Types': cpp2/types.md - - 'Metafunctions & reflection API': cpp2/metafunctions.md + - 'Functions, branches, and loops': cpp2/functions.md + - 'Contracts and assertions': cpp2/contracts.md + - 'Types and inheritance': cpp2/types.md + - 'Metafunctions and reflection': cpp2/metafunctions.md - 'Namespaces': cpp2/namespaces.md - 'Aliases': cpp2/aliases.md - 'Modules': cpp2/modules.md From 8bb227daf90cf6618019c3c7413ef5f8878d0a03 Mon Sep 17 00:00:00 2001 From: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Date: Tue, 20 Feb 2024 04:13:42 +1000 Subject: [PATCH 37/64] Fix small typo in common.md (#985) Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> --- docs/cpp2/common.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cpp2/common.md b/docs/cpp2/common.md index d5be1c484d..149b205d53 100644 --- a/docs/cpp2/common.md +++ b/docs/cpp2/common.md @@ -113,7 +113,7 @@ Cpp2 supports using Cpp1 user-defined literals for compatibility, to support sea - You can write a 'constexpr' function like `nm: (value: i64) -> my_nanometer_type == { /*...*/ }` that takes an integer and returns a value of a strongly typed "nanometer" type, and then create a `nm` value by writing either `nm(123)` or **`123.nm()`**. -Both **`123.n()`** and **`123.u8()`** are very similar to user-defined literal syntax, and more general. +Both **`123.nm()`** and **`123.u8()`** are very similar to user-defined literal syntax, and more general. ## Operators From c7eccf45b9bcf67fd0746c7af904728385ccfcc0 Mon Sep 17 00:00:00 2001 From: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Date: Tue, 20 Feb 2024 04:16:34 +1000 Subject: [PATCH 38/64] Changes to expressions.md (#986) * Update expressions.md Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> * Minor tweak to not lose "dynamic" types --------- Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Co-authored-by: Herb Sutter --- docs/cpp2/expressions.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md index aef4a8196f..eaf2f73e7f 100644 --- a/docs/cpp2/expressions.md +++ b/docs/cpp2/expressions.md @@ -35,12 +35,12 @@ _ = vec.emplace_back(1,2,3); } ``` -For details, see [Design note: Explicit discard](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Explicit-discard). In Cpp2, 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. +For details, see [Design note: Explicit discard](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Explicit-discard). In Cpp2, data is always initialized, data is never silently lost, data flow is always visible. Data is precious, and it's always safe. ## `is` — safe type/value queries -An `x is C` expression allows safe type and value queries, and evaluates to `true` if `x` matches constraint `C`. It supports both static and dynamic queries, including customization, with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. +An `x is C` expression allows safe type and value queries, and evaluates to `true` if `x` matches constraint `C`. It supports both static and dynamic queries, including customization, with support for standard library dynamic types like `std::variant`, `std::optional`, `std::expected`, and `std::any` provided out of the box. There are two kinds of `is`: @@ -60,7 +60,7 @@ There are two kinds of `is`: | Value | `x is 0` | | Value predicate | `x is (in(10, 20))` | -`is` is useful throughout the language, including in `inspect` pattern matching alternatives. `is` is extensible, and works out of the box with `std::variant`, `std::optional`, and `std::any`. For examples, see: +`is` is useful throughout the language, including in `inspect` pattern matching alternatives. `is` is extensible, and works out of the box with `std::variant`, `std::optional`, `std::expected`, and `std::any`. For examples, see: - [`mixed-inspect-templates.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-templates.cpp2) - [`mixed-inspect-values.cpp2`](https://github.com/hsutter/cppfront/tree/main/regression-tests/mixed-inspect-values.cpp2) @@ -86,7 +86,7 @@ Here are some `is` queries with their Cpp1 equivalents. In this table, uppercase ## `as` — safe casts and conversions -An `x as T` expression allows safe type casts. `x` must be an object or expression, and `T` must be a type. It supports both static and dynamic typing, including customization with support for the standard dynamically typed libraries `std::variant`, `std::optional`, and `std::any` provided in the box. For example: +An `x as T` expression allows safe type casts. `x` must be an object or expression, and `T` must be a type. Like `is`, `as` supports both static and dynamic typing, including customization, with support for standard library dynamic types like `std::variant`, `std::optional`, `std::expected`, and `std::any` provided out of the box. For example: ``` cpp title="Using as" hl_lines="4 6 14" main: () = { From eedc6054de62f8820dcf14c877f0c1c1dc76c976 Mon Sep 17 00:00:00 2001 From: gregmarr Date: Mon, 19 Feb 2024 13:20:43 -0500 Subject: [PATCH 39/64] Another round of docs updates (#983) * types * metafunctions * Taking a pass over the updates, and adding that `that` must be `in` or `move` * Remove TODO comment for now Not sure of its meaning, we can add again later --------- Co-authored-by: Herb Sutter --- docs/cpp2/metafunctions.md | 14 ++++++++------ docs/cpp2/types.md | 27 ++++++++++++++++----------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/docs/cpp2/metafunctions.md b/docs/cpp2/metafunctions.md index d569b8b25d..6eb51d27b6 100644 --- a/docs/cpp2/metafunctions.md +++ b/docs/cpp2/metafunctions.md @@ -13,7 +13,7 @@ The most important thing about metafunctions is that they are not hardwired lang ## Applying metafunctions -Metafunctions provide an easy way for a type author to opt into a group of defaults, constraints, and generated functions: Just write `@name` afer the `:` of a declaration, where `name` is the name of the metafunctions. This lets the type author declare (and the human reader see) the intent up front: "This isn't just any `type`, this is a `@value type`" which automatically gives the type default/copy/move construction and assignment, `<=>` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions: +Metafunctions provide an easy way for a type author to opt into a group of defaults, constraints, and generated functions: Just write `@name` afer the `:` of a declaration, where `name` is the name of the metafunction. This lets the type author declare (and the human reader see) the intent up front: "This isn't just any `type`, this is a `@value type`" which automatically gives the type default/copy/move construction and assignment, `<=>` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions: ``` cpp title="Using the value metafunction when writing a type" hl_lines="1" point2d: @value type = { @@ -95,7 +95,7 @@ skat_game: @enum type = { Consider `hearts`: It's a member object declaration, but it doesn't have a type (or a default value) which is normally illegal, but here 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). -Unlike C `enum`, this `@enum` is scoped and strongly typed (does not implicitly convert to the underlying type. +Unlike C `enum`, this `@enum` is scoped and strongly typed (does not implicitly convert to the underlying type). Unlike C++11 `enum class`, it's "just a `type`" which means it can naturally also have member functions and other things that a type can have: @@ -141,10 +141,11 @@ file_attributes: @flag_enum 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 +// each member has .is_member(), .set_member(), and .member() +// accessors using the member name... 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; @@ -155,6 +156,7 @@ main: () = { x: name_or_number = (); x.set_name("xyzzy"); // now x is a string + assert( x.is_name() ); std::cout << x.name(); // prints the string // trying to use x.num() here would cause a Type safety diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index 5ed8796081..db4faea1d1 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -110,7 +110,7 @@ A `this` parameter of an `operator=` function can additionally be declared as: ## `operator=` — Construction, assignment, and destruction -All value operations are spelled `operator=`, including construction, assignment, and destruction. `operator=` sets the value of `this` object, so the `this` parameter can be pass as anything but `in` (which would imply `const`): +All value operations are spelled `operator=`, including construction, assignment, and destruction. `operator=` sets the value of `this` object, so the `this` parameter can be passed as anything but `in` (which would imply `const`): - **`out this`:** Writing `operator=: (out this /*...*/ )` is naturally both a constructor and an assignment operator, because an `out` parameter can take an uninitialized or initialized argument. If you don't write a more-specialized `inout this` assignment operator, Cpp2 will use the `out this` function also for assignment. @@ -120,11 +120,14 @@ All value operations are spelled `operator=`, including construction, assignment Unifying `operator=` enables usable `out` parameters, which is essential for composable guaranteed initialization. We want the expression syntax `x = value` to be able to call a constructor or an assignment operator, so naming them both `operator=` is consistent. +TODO Return type of assignment operator? + > Note: Writing `=` always invokes an `operator=` (in fact for a Cpp2-authored type, and semantically for a Cpp1-authored type). This avoids the Cpp1 inconsistency that "writing `=` calls `operator=`, except when it doesn't" (such as in a Cpp1 variable initialization). Conversely, `operator=` is always invoked by `=` in Cpp2. + ### `that` — A source parameter -All functions can have a **`that`** is a synonym for the object to be copied/moved from. Like `this`, at type scope it is never declared with an explicit `: its_type` because its type is always the current type. +All type-scope functions can have **`that`** as their second parameter, which is a synonym for the object to be copied/moved from. Like `this`, at type scope it is never declared with an explicit `: its_type` because its type is always the current type. Unlike `this`, `that` is always passed as a `in` (the default) or `move` parameter. `that` can be an `in` (default) or `move` parameter. Which you choose naturally determines what kind of member function is being declared: @@ -157,7 +160,7 @@ In Cpp1 terms, they can be described as follows: - **M2 is preferred over A2.** Both M2 and A2 can generate a missing `(inout this, move that)` function. If both options are available, Cpp2 prefers to use M2 (generate move assignment from copy assignment, which could itself have been generated from copy construction) rather than A2 (generate move assignment from move construction). This is because M2 is a better fit: Move assignment is more like copy assignment than like move construction, because assignments are designed structurally to set the value of an existing `this` object. -The most general `operator=` with `that` is `(out this, that)`. In Cpp1 terms, it generates all four combinations of { copy, move } x { constructor, assignment }. This is often sufficient, so you can write all these value-setting just once. If you do want to write a more specific version that does something else, though, you can always write it too. +The most general `operator=` with `that` is `(out this, that)`. In Cpp1 terms, it generates all four combinations of { copy, move } x { constructor, assignment }. This is often sufficient, so you can write all these value-setting functions just once. If you do want to write a more specific version that does something else, though, you can always write it too. > Note: Generating `inout this` (assignment) from `out this` also generates **converting assignment** from converting construction, which is a new thing. Today in Cpp1, if you write a converting constructor from another type `X`, you may or may not write the corresponding assignment from `X`; in Cpp2 you will get that by default, and it sets the object to the same state as the converting constructor from `X` does. @@ -169,7 +172,7 @@ There are only two defaults the language will generate implicitly for a type: - 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. +- If no `operator=` functions other than the destructor 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). @@ -177,13 +180,15 @@ All other `operator=` functions are explicitly written, either by hand or by opt ### Memberwise by default -All copy/move/comparison `operator=` functions are memberwise by default in Cpp2. That includes when you write memberwise construction and assignment yourself. In a hand-written `operator=`: +All copy/move/comparison `operator=` functions are memberwise by default in Cpp2. That includes when you write memberwise construction and assignment yourself. + +In a hand-written `operator=`: -- The body must begin with a series of `member = value;` statements, one for each of the type's data members in order. +- The body must begin with a series of `member = value;` statements, one for each of the type's data members (including base classes) in declaration order. -- If the body does not mention a member, by default the member's default initializer is used. +- If the body does not mention a member in the appropriate place in the beginning section, by default the member's default initializer is used. -- In an assignment operator (`inout this`), you an explicitly skip setting a member by writing `member = _;` where it would normally be set, if you know you have a reason to set its value later instead. +- In an assignment operator (`inout this`), you can explicitly skip setting a member by writing `member = _;` where it would normally be set if you know you have a reason to set its value later instead or if the existing value needs to be preserved. For example: @@ -224,12 +229,12 @@ main: () = { y = x; // copy assign z := (move x); // move construct z = (move y); // move assign - x.print(); // [] [] - moved from - y.print(); // [] [] - moved from + x.print(); // "value is [] []" - moved from + y.print(); // "value is [] []" - moved from } ``` -> Note: This makes memberwise semantics symmetric for construction and assignment. In Cpp1, only non-copy/move constructors have a default, which is to initialize a member with its default initializer. In Cpp2, both constructors and assignment operators default to using the default initializer for if it's a conversion function (non-`that`, aka non-copy/move), and using memberwise `member = that.member;` for copy/move functions. +> Note: This makes memberwise semantics symmetric for construction and assignment. In Cpp1, only non-copy/move constructors have a default, which is to initialize a member with its default initializer. In Cpp2, both constructors and assignment operators default to using the default initializer if it's a conversion function (non-`that`, aka non-copy/move), and using memberwise `member = that.member;` for copy/move functions. From 4495b656f24bd8e6fc1757581cc8c9a6151c5880 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 19 Feb 2024 11:08:12 -0800 Subject: [PATCH 40/64] Add syntax highlighting for inline code blocks using `#!cpp` shebangs Note: I deliberately did not add shebangs for: - `inline code` that wouldn't benefit from them (e.g., had nothing significant to highlight) so as to keep the Markdown more readable - `inline code` that I didn't want to highlight, mainly Cpp2 code that used Cpp1 reserved keywords in a non-reserved way (mainly metafunctions like @enum and @union) --- docs/cpp2/common.md | 65 ++++++++++++++------------- docs/cpp2/declarations.md | 6 ++- docs/cpp2/expressions.md | 21 ++++++--- docs/cpp2/functions.md | 16 +++---- docs/cpp2/metafunctions.md | 6 +-- docs/cpp2/objects.md | 12 ++--- docs/cpp2/types.md | 88 ++++++++++++++++++------------------- docs/cppfront/mixed.md | 4 +- docs/welcome/hello-world.md | 22 +++++----- mkdocs.yml | 1 + 10 files changed, 127 insertions(+), 114 deletions(-) diff --git a/docs/cpp2/common.md b/docs/cpp2/common.md index 149b205d53..4c5275f355 100644 --- a/docs/cpp2/common.md +++ b/docs/cpp2/common.md @@ -6,11 +6,14 @@ As always, `main` is the entry point of the program. For example: `main` can have either: -- No parameters: `main: () /*etc.*/` +- No parameters:   **`#!cpp main: () /*etc.*/`** + +- One parameter of implicit type named `args`:   **`#!cpp main: (args) /*etc.*/`** + + - The type of `args` cannot be explicitly specified. It is always `cpp2::args_t`, which behaves similarly to a `#!cpp const std::array`. -- One parameter of implicit type named `args`: `main: (args) /*etc.*/` - - The type of `args` cannot be explicitly specified. It is always `cpp2::args_t`, which behaves similarly to a `const std::array`. - Using `args` performs zero heap allocations. Every `string_view` is directly bound to the string storage provided by host environment. + - `args.argc` and `args.argv` additionally provide access to the raw C/C++ `main` parameters. ``` cpp title="main with (args)" hl_lines="5 9" @@ -29,16 +32,16 @@ main: (args) -> int `main` can return: -- `void`, the default return value for functions. In this case, the compiled Cpp1 code returns an `int` (as required by the standard) with value `0`. +- `#!cpp void`, the default return value for functions. No `#!cpp return` statement is allowed in the body. In this case, the compiled Cpp1 code behaves as if `main` returned `#!cpp int`. -- `int`. If the body has no `return` statement, the default is to `return 0;` at the end of the function body. +- `#!cpp int`. If the body has no `#!cpp return` statement, the default is to `#!cpp return 0;` at the end of the function body. - Some other type that your Cpp1 compiler(s) supports as a nonstandard extension. ## Comments -The usual `// line comments` and `/* stream comments */` are supported. For example: +The usual `#!cpp // line comments` and `#!cpp /* stream comments */` are supported. For example: ``` cpp title="Writing comments" // A line comment: After //, the entire @@ -82,21 +85,21 @@ Cpp2 supports the same fundamental types as today's Cpp1, but additionally provi | Variable-width types
(Cpp2-compatible single-word names) | Synonym for (these multi-word
names are not allowed in Cpp2) | |---|---| -| `ushort` | `unsigned short` | -| `uint` | `unsigned int` | -| `ulong` | `unsigned long` | -| `longlong` | `long long` | -| `ulonglong` | `unsigned long long` | -| `longdouble` | `long double` | +| `ushort` | `#!cpp unsigned short` | +| `uint` | `#!cpp unsigned int` | +| `ulong` | `#!cpp unsigned long` | +| `longlong` | `#!cpp long long` | +| `ulonglong` | `#!cpp unsigned long long` | +| `longdouble` | `#!cpp long double` | | For compatibility/interop only,
so deliberately ugly names | Synonym for | Notes | |---|---|---| -| `_schar` | `signed char` | Normally, prefer `i8` instead | -| `_uchar` | `unsigned char` | Normally, prefer `u8` instead | +| `_schar` | `#!cpp signed char` | Normally, prefer `i8` instead | +| `_uchar` | `#!cpp unsigned char` | Normally, prefer `u8` instead | ## Type qualifiers -Types can be qualified with `const` and `*`. Types are written left-to-right, so a qualifier always applies to what immediately follows it. For example, to declare a `const` pointer to a non-`const` pointer to a `const i32` object, write: +Types can be qualified with `#!cpp const` and `#!cpp *`. Types are written left-to-right, so a qualifier always applies to what immediately follows it. For example, to declare a `#!cpp const` pointer to a non-`#!cpp const` pointer to a `#!cpp const i32` object, write: ``` cpp title="Using type qualifiers" // A const pointer to a non-const pointer to a const i32 object @@ -105,13 +108,13 @@ p: const * * const i32; ## Literals -Cpp2 supports the same `'c'`haracter, `"string"`, binary, integer, and floating point literals as Cpp1, including most Unicode encoding prefixes and raw string literals. +Cpp2 supports the same `#!cpp 'c'`haracter, `#!cpp "string"`, binary, integer, and floating point literals as Cpp1, including most Unicode encoding prefixes and raw string literals. Cpp2 supports using Cpp1 user-defined literals for compatibility, to support seamlessly using existing libraries. However, because Cpp2 has unified function call syntax (UFCS), the preferred way to author the equivalent in Cpp2 is to just write a function or type name as a `.` call suffix. For example: - You can create a `u8` value by writing either `u8(123)` or **`123.u8()`**. [^u8using] -- You can write a 'constexpr' function like `nm: (value: i64) -> my_nanometer_type == { /*...*/ }` that takes an integer and returns a value of a strongly typed "nanometer" type, and then create a `nm` value by writing either `nm(123)` or **`123.nm()`**. +- You can write a 'constexpr' function like `#!cpp nm: (value: i64) -> my_nanometer_type == { /*...*/ }` that takes an integer and returns a value of a strongly typed "nanometer" type, and then create a `nm` value by writing either `nm(123)` or **`123.nm()`**. Both **`123.nm()`** and **`123.u8()`** are very similar to user-defined literal syntax, and more general. @@ -132,8 +135,8 @@ if !vec.empty() { | Unary operator | Cpp2 example | Cpp1 equivalent | |---|---|---| | `!` | `!vec.empty()` | `!vec.empty()` | -| `+` | `+100` | `+100` | -| `-` | `-100` | `-100` | +| `+` | `#!cpp +100` | `#!cpp +100` | +| `-` | `#!cpp -100` | `#!cpp -100` | The operators `.`, `*`, `&`, `~`, `++`, `--`, `()`, `[]`, and `$` are postfix. For example: @@ -152,15 +155,15 @@ Postfix notation lets the code read fluidly left-to-right, in the same order in | Unary operator | Cpp2 example | Cpp1 equivalent | |---|---|---| -| `.` | `obj.f()` | `obj.f()` | -| `*` | `pobj*.f()` | `(*pobj).f()` or `pobj->f()` | -| `&` | `obj&` | `&obj` | -| `~` | `val~` | `~val` | -| `++` | `iter++` | `++iter` | -| `--` | `iter--` | `--iter` | -| `(` `)` | `f( 1, 2, 3)` | `f( 1, 2, 3)` | -| `[` `]` | `vec[123]` | `vec[123]` | -| `$` | `val$` | (reflection — no C++23 equivalent) | +| `#!cpp .` | `#!cpp obj.f()` | `#!cpp obj.f()` | +| `#!cpp *` | `#!cpp pobj*.f()` | `#!cpp (*pobj).f()` or `#!cpp pobj->f()` | +| `#!cpp &` | `#!cpp obj&` | `#!cpp &obj` | +| `#!cpp ~` | `#!cpp val~` | `#!cpp ~val` | +| `#!cpp ++` | `#!cpp iter++` | `#!cpp ++iter` | +| `#!cpp --` | `#!cpp iter--` | `#!cpp --iter` | +| `(` `)` | `#!cpp f( 1, 2, 3)` | `#!cpp f( 1, 2, 3)` | +| `[` `]` | `#!cpp vec[123]` | `#!cpp vec[123]` | +| `$` | `val$` | _reflection — no Cpp1 equivalent yet_ | > Because `++` and `--` always have in-place update semantics, we never need to remember "use prefix `++`/`--` unless you need a copy of the old value." If you do need a copy of the old value, just take the copy before calling `++`/`--`. @@ -168,9 +171,9 @@ Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~ | Unary postfix operators that
are also binary operators | Cpp2 example | Cpp1 equivalent | |---|---|---| -| `*` | `pobj* * 42` | `(*pobj)*42` | -| `&` | `obj& & mask`

(note: allowed in unsafe code only) | `&obj & mask` | -| `~` | `~val ~ bitcomplement` | `val~ ~ bitcomplement` | +| `#!cpp *` | `#!cpp pobj* * 42` | `#!cpp (*pobj)*42` | +| `#!cpp &` | `#!cpp obj& & mask`

(note: allowed in unsafe code only) | `#!cpp &obj & mask` | +| `#!cpp ~` | `#!cpp ~val ~ bitcomplement` | `#!cpp val~ ~ bitcomplement` | For more details, see [Design note: Postfix unary operators vs binary operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-unary-operators-vs-binary-operators). diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index 74427e7b49..90d352c5b4 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -8,9 +8,11 @@ All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. - The `=` is pronounced **"defined as."** -- The _statement_ is typically an expression statement (e.g., `a + b();`) or a compound statement (e.g., `{ /*...*/ return c(d) / e; }`). +- The _statement_ is typically an expression statement (e.g., `#!cpp a + b();`) or a compound statement (e.g., `#!cpp { /*...*/ return c(d) / e; }`). -- Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted entirely to accept a default (e.g., `x: int = 0;` can be equivalently written `x: _ = 0;` or `x := 0;` both of which deduce the type). +- Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted entirely to accept a default (e.g., `#!cpp x: int = 0;` can be equivalently written `#!cpp x: _ = 0;` or `#!cpp x := 0;` both of which deduce the type). + +> Note: When the type is omitted, whitespace does not matter, and writing `#!cpp x: = 0;` or `#!cpp x : = 0;` or `#!cpp x := 0;` or other whitespace is just a stylistic choice. This documentation's style uses the last one, except when there are multiple adjacent declaration lines this style lines up their `:` and `=`. ## Examples diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md index eaf2f73e7f..bbfb6d1ec8 100644 --- a/docs/cpp2/expressions.md +++ b/docs/cpp2/expressions.md @@ -40,7 +40,7 @@ For details, see [Design note: Explicit discard](https://github.com/hsutter/cppf ## `is` — safe type/value queries -An `x is C` expression allows safe type and value queries, and evaluates to `true` if `x` matches constraint `C`. It supports both static and dynamic queries, including customization, with support for standard library dynamic types like `std::variant`, `std::optional`, `std::expected`, and `std::any` provided out of the box. +An `x is C` expression allows safe type and value queries, and evaluates to `#!cpp true` if `x` matches constraint `C`. It supports both static and dynamic queries, including customization, with support for standard library dynamic types like `std::variant`, `std::optional`, `std::expected`, and `std::any` provided out of the box. There are two kinds of `is`: @@ -57,8 +57,8 @@ There are two kinds of `is`: | Value constraint kind | Example | |---|---| -| Value | `x is 0` | -| Value predicate | `x is (in(10, 20))` | +| Value | `#!cpp x is 0` | +| Value predicate | `#!cpp x is (in(10, 20))` | `is` is useful throughout the language, including in `inspect` pattern matching alternatives. `is` is extensible, and works out of the box with `std::variant`, `std::optional`, `std::expected`, and `std::any`. For examples, see: @@ -78,11 +78,13 @@ Here are some `is` queries with their Cpp1 equivalents. In this table, uppercase |---|---| | `X is Y && Y is X` | `std::is_same_v` | | `D is B` | `std::is_base_of` | -| `pb is *D` | `dynamic_cast(pb) != nullptr` | +| `#!cpp pb is *D` | `#!cpp dynamic_cast(pb) != nullptr` | | `v is T` | `std::holds_alternative(v)` | -| `a is T` | `a.type() == typeid(T)` | +| `a is T` | `#!cpp a.type() == typeid(T)` | | `o is T` | `o.has_value()` | +> Note: `is` unifies a variety of differently-named Cpp1 language and library queries under one syntax, and supports only the type-safe ones. + ## `as` — safe casts and conversions @@ -113,19 +115,24 @@ Here are some `as` casts with their Cpp1 equivalents. In this table, uppercase n | Some sample `as` casts | Cpp1 equivalent |---|---| | `x as Y` | `Y{x}` | -| `pb as *D` | `dynamic_cast(pb)` | +| `#!cpp pb as *D` | `#!cpp dynamic_cast(pb)` | | `v as T` | `std::get(v)` | | `a as T` | `std::any_cast(a)` | | `o as T` | `o.value()` | +> Note: `as` unifies a variety of differently-named Cpp1 language and library casts and conversions under one syntax, and supports only the type-safe ones. + ## `inspect` — pattern matching An `inspect expr -> Type` expression allows pattern matching using `is`. - `expr` is evaluated once. + - Each alternative spelled `is C` is evaluated in order as if called with `expr is C`. -- If an alternative evaluates to `true`, then its `= alternative;` body is used as the value of the entire `inspect` expression, and the meaning is the same as if the entire `inspect` expression had been written as just `:Type = alternative;` — i.e., an unnamed object expression (aka 'temporary object') of type `Type` initialized with `alternative`. + +- If an alternative evaluates to `#!cpp true`, then its `#!cpp = alternative;` body is used as the value of the entire `inspect` expression, and the meaning is the same as if the entire `inspect` expression had been written as just `#!cpp :Type = alternative;` — i.e., an unnamed object expression (aka 'temporary object') of type `Type` initialized with `alternative`. + - A catchall `is _` is required. For example: diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index 64f151bbbc..196badb8f0 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -11,7 +11,7 @@ A function call like `f(x)` is a normal function call that will call non-member A function call like `x.f()` is a unified function call syntax (aka UFCS) call. It will call a member function if one is available, and otherwise will call `f(x)`. Having UFCS is important for generic code that may want to call a member or a non-member function, whichever is available. It's also important to enable fluid programming styles and natural IDE autocompletion support. -An operator notation call like `a + b` will call an overloaded operator function if one is available, as usual in C++. +An operator notation call like `#!cpp a + b` will call an overloaded operator function if one is available, as usual in C++. For example: @@ -56,7 +56,7 @@ There are six ways to pass parameters that cover all use cases: | Parameter kind | "Pass me an `x` I can ______" | Accepts arguments that are | Special semantics | |---|---|---|---| -| `in` (default) | read from | anything | always `const`

automatically passes by value if cheaply copyable | +| `in` (default) | read from | anything | always `#!cpp const`

automatically passes by value if cheaply copyable | | `copy` | take a copy of | anything | acts like a normal local variable initialized with the argument | | `inout` | read from and write to | lvalues | | | `out` | write to (including construct) | lvalues, including uninitialized lvalues | must `=` assign/construct before other uses | @@ -65,7 +65,7 @@ There are six ways to pass parameters that cover all use cases: -> Note: All parameters and other objects in Cpp2 are `const` by default, except for local variables. For details, see [Design note: `const` objects by default](https://github.com/hsutter/cppfront/wiki/Design-note%3A-const-objects-by-default). +> Note: All parameters and other objects in Cpp2 are `#!cpp const` by default, except for local variables. For details, see [Design note: `#!cpp const` objects by default](https://github.com/hsutter/cppfront/wiki/Design-note%3A-const-objects-by-default). ## Return values @@ -114,22 +114,22 @@ main: () > Cpp2 imbues Cpp1 code with nondiscardable semantics, while staying fully compatible as usual: > -> - A function written in Cpp2 syntax that returns something other than `void` is always compiled to Cpp1 with `[[nodiscard]]`. +> - A function written in Cpp2 syntax that returns something other than `#!cpp void` is always compiled to Cpp1 with `[[nodiscard]]`. > -> - A function call written in Cpp2 `x.f()` member call syntax always treats a non-`void` return type as not discardable, even if the function was written in Cpp1 syntax that did not write `[[nodiscard]]`. +> - A function call written in Cpp2 `x.f()` member call syntax always treats a non-`#!cpp void` return type as not discardable, even if the function was written in Cpp1 syntax that did not write `[[nodiscard]]`. ## Control flow -## `if`, `else` — Branches +## `#!cpp if`, `#!cpp else` — Branches TODO -## `for`, `while`, `do` — Loops +## `#!cpp for`, `#!cpp while`, `#!cpp do` — Loops TODO -Loops can be named using the usual **name `:`** name introduction syntax, and `break` and `continue` can refer to those names. For example: +Loops can be named using the usual **name `:`** syntax that introduces all names, and `#!cpp break` and `#!cpp continue` can refer to those names. For example: ``` cpp title="Using named break and continue" hl_lines="6 10" outer: while i type = { Consider `hearts`: It's a member object declaration, but it doesn't have a type (or a default value) which is normally illegal, but here 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). -Unlike C `enum`, this `@enum` is scoped and strongly typed (does not implicitly convert to the underlying type). +Unlike C `#!cpp enum`, this `@enum` is scoped and strongly typed (does not implicitly convert to the underlying type). -Unlike C++11 `enum class`, it's "just a `type`" which means it can naturally also have member functions and other things that a type can have: +Unlike C++11 `#!cpp enum class`, it's "just a `type`" which means it can naturally also have member functions and other things that a type can have: ``` cpp title="An @enum type with a member function" hl_lines="1" janus: @enum type = { @@ -167,7 +167,7 @@ main: () = { } ``` -Unlike C `union`, this `@union` is safe to use because it always ensures only the active type is accessed. +Unlike C `#!cpp union`, this `@union` is safe to use because it always ensures only the active type is accessed. Unlike C++11 `std::variant`, this `@union` is easier to use because its alternatives are anonymous, and safer to use because each union type is a distinct type. [^variant] diff --git a/docs/cpp2/objects.md b/docs/cpp2/objects.md index b2b218b1fd..a519b3a536 100644 --- a/docs/cpp2/objects.md +++ b/docs/cpp2/objects.md @@ -55,7 +55,7 @@ Additionally, at function local scope an object `obj` can be initialized separat - Declare `obj` without an initializer, such as `obj: some_type;`. This allocates stack space for the object, but does not construct it. -- `obj` must have a definite first use on every `if`/`else` branch path, and +- `obj` must have a definite first use on every `#!cpp if`/`#!cpp else` branch path, and - that definite first use must be of the form `obj = value;` which is a constructor call, or else pass `obj` as an `out` argument to an `out` parameter (which is also effectively a constructor call, and performs the construction in the callee). @@ -92,18 +92,18 @@ load_from_disk: (out buffer) = { } // constructs it; otherwise, assigns ``` -In the above example, note the simple rule for branches: The local variable must be initialized on both the `if` and `else` branches, or neither branch. +In the above example, note the simple rule for branches: The local variable must be initialized on both the `#!cpp if` and `#!cpp else` branches, or neither branch. ## Heap objects -Objects can also be allocated on the heap using `arena.new (/*initializer, arguments)` where `arena` is any object that acts as a memory arena and provides a `.new` function template. Two memory arena objects are provided in namespace `cpp2`: +Objects can also be allocated on the heap using `#!cpp arena.new (/*initializer, arguments*/)` where `arena` is any object that acts as a memory arena and provides a `#!cpp .new` function template. Two memory arena objects are provided in namespace `cpp2`: -- `unique.new` calls `std::make_unique` and returns a `std::unique_ptr`. +- `#!cpp unique.new` calls `std::make_unique` and returns a `std::unique_ptr`. -- `shared.new` calls `std::make_shared` and returns a `std::shared_ptr`. +- `#!cpp shared.new` calls `std::make_shared` and returns a `std::shared_ptr`. -The default is `unique.new` if you don't specify an arena object. +The default is `#!cpp unique.new` if you don't specify an arena object. For example (see [types](types.md) for more details about writing types): diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index db4faea1d1..2c9b6d6305 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -5,7 +5,7 @@ A user-defined `type` is written using the same **name `:` kind `=` value** [declaration syntax](../cpp2/declarations.md) as everything in Cpp2. The type's "value" is a `{}`-enclosed body containing more declarations. -In a `type`, data members are private by default, and functions and nested types are public by default. To explicitly declare a type scope declaration `public`, `protected`, or `private`, write that keyword at the beginning of the declaration. +In a `type`, data members are private by default, and functions and nested types are public by default. To explicitly declare a type scope declaration `#!cpp public`, `#!cpp protected`, or `#!cpp private`, write that keyword at the beginning of the declaration. ``` cpp title="Writing a simple type" hl_lines="1" mytype: type = @@ -20,23 +20,23 @@ mytype: type = } ``` -## `this` — The parameter name +## `#!cpp this` — The parameter name -**`this`** is a synonym for the current object. Inside the scope of a type that has a member named `member`, `member` by default means `this.member`. +**`#!cpp this`** is a synonym for the current object. Inside the scope of a type that has a member named `member`, `member` by default means `#!cpp this.member`. -> Note: In Cpp2, `this` is not a pointer. +> Note: In Cpp2, `#!cpp this` is not a pointer. -The name `this` may only be used for the first parameter of a type-scope function (aka member function). It is never declared with an explicit `: its_type` because its type is always the current type. +The name `#!cpp this` may only be used for the first parameter of a type-scope function (aka member function). It is never declared with an explicit `: its_type` because its type is always the current type. -`this` can be an `in` (default), `inout`, `out`, or `move` parameter. Which you choose naturally determines what kind of member function is being declared: +`#!cpp this` can be an `in` (default), `inout`, `out`, or `move` parameter. Which you choose naturally determines what kind of member function is being declared: -- **`in this`**: Writing `myfunc: (this /*...*/)`, which is shorthand for `myfunc: (in this /*...*/)`, defines a Cpp1 `const`-qualified member function, because `in` parameters are `const`. +- **`#!cpp in this`**: Writing `#!cpp myfunc: (this /*...*/)`, which is shorthand for `#!cpp myfunc: (in this /*...*/)`, defines a Cpp1 `#!cpp const`-qualified member function, because `in` parameters are `#!cpp const`. -- **`inout this`**: Writing `myfunc: (inout this /*...*/)` defines a Cpp1 non-`const` member function. +- **`#!cpp inout this`**: Writing `#!cpp myfunc: (inout this /*...*/)` defines a Cpp1 non-`#!cpp const` member function. -- **`out this`**: Writing `myfunc: (out this /*...*/)` defines a Cpp1 constructor... and more. (See below.) +- **`#!cpp out this`**: Writing `#!cpp myfunc: (out this /*...*/)` defines a Cpp1 constructor... and more. (See below.) -- **`move this`**: Writing `myfunc: (move this /*...*/)` defines a Cpp1 `&&`-qualified member function, or if there are no additional parameters it defines the destructor. +- **`#!cpp move this`**: Writing `#!cpp myfunc: (move this /*...*/)` defines a Cpp1 `#!cpp &&`-qualified member function, or if there are no additional parameters it defines the destructor. For example, here is how to write read-only member function named `print` that takes a read-only string value and prints this object's data value and the string message: @@ -53,27 +53,27 @@ mytype: type = { } ``` -## `this` — Inheritance +## `#!cpp this` — Inheritance -Base types are written as members named this. For example, just as a type could write a data member as `data: string = "xyzzy";`, which is pronounced "`data` is a `string` defined as having the default value `"xyzzy"`, a base type is written as `this: Shape = (default, values);`, which is pronounced "`this` is a `Shape` defined as having these default values." +Base types are written as members named `#!cpp this`. For example, just as a type could write a data member as `#!cpp data: string = "xyzzy";`, which is pronounced "`data` is a `string` defined as having the default value `#!cpp "xyzzy"`, a base type is written as `#!cpp this: Shape = (default, values);`, which is pronounced "`#!cpp this` is a `Shape` defined as having these default values." > Cpp2 syntax has no separate base 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 subobject, so that it naturally outlives the base subobject. +Because base and member subobjects are all declared in the same place (the type body) and initialized in the same place (an `#!cpp 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 subobject, so that it naturally outlives the base subobject. > Cpp2 code doesn't need workarounds like Boost's `base_from_member`, because all of the motivating examples for that can be written directly. See [this explanation](https://github.com/hsutter/cppfront/issues/334#issuecomment-1500984173) for details. -## `virtual`, `override`, and `final` — Virtual functions +## `#!cpp virtual`, `#!cpp override`, and `#!cpp final` — Virtual functions -A `this` parameter can additionally be declared as one of the following: +A `#!cpp this` parameter can additionally be declared as one of the following: -- **`virtual`**: Writing `myfunc: (virtual this /*...*/)` defines a new virtual function. +- **`#!cpp virtual`**: Writing `#!cpp myfunc: (virtual this /*...*/)` defines a new virtual function. -- **`override`**: Writing `myfunc: (override this /*...*/)` defines an override of an existing base class virtual function. +- **`#!cpp override`**: Writing `#!cpp myfunc: (override this /*...*/)` defines an override of an existing base class virtual function. -- **`final`**: Writing `myfunc: (final this /*...*/)` defines a final override of an existing base class virtual function. +- **`#!cpp final`**: Writing `#!cpp myfunc: (final this /*...*/)` defines a final override of an existing base class virtual function. -A pure virtual function is a function with a `virtual this` or `override this` parameter and no body. +A pure virtual function is a function with a `#!cpp virtual this` or `#!cpp override this` parameter and no body. For example: @@ -101,47 +101,47 @@ derived: type ## `implicit` — Controlling conversion functions -A `this` parameter of an `operator=` function can additionally be declared as: +A `#!cpp this` parameter of an `#!cpp operator=` function can additionally be declared as: -- **`implicit`**: Writing `operator=: (implicit out this, /*...*/)` defines a function that will not be marked as "explicit" when lowered to Cpp1 syntax. +- **`implicit`**: Writing `#!cpp operator=: (implicit out this, /*...*/)` defines a function that will not be marked as "explicit" when lowered to Cpp1 syntax. > Note: This reverses the Cpp1 default, where constructors are not "explicit" by default, and you have to write "explicit" to make them explicit. -## `operator=` — Construction, assignment, and destruction +## `#!cpp operator=` — Construction, assignment, and destruction -All value operations are spelled `operator=`, including construction, assignment, and destruction. `operator=` sets the value of `this` object, so the `this` parameter can be passed as anything but `in` (which would imply `const`): +All value operations are spelled `#!cpp operator=`, including construction, assignment, and destruction. `#!cpp operator=` sets the value of `#!cpp this` object, so the `#!cpp this` parameter can be passed as anything but `in` (which would imply `#!cpp const`): -- **`out this`:** Writing `operator=: (out this /*...*/ )` is naturally both a constructor and an assignment operator, because an `out` parameter can take an uninitialized or initialized argument. If you don't write a more-specialized `inout this` assignment operator, Cpp2 will use the `out this` function also for assignment. +- **`#!cpp out this`:** Writing `#!cpp operator=: (out this /*...*/ )` is naturally both a constructor and an assignment operator, because an `out` parameter can take an uninitialized or initialized argument. If you don't also write a more-specialized `#!cpp inout this` assignment operator, Cpp2 will use the `#!cpp out this` function also for assignment. -- **`inout this`:** Writing `operator=: (inout this /*...*/ )` is an assignment operator (only), because an `inout` parameter requires an initialized modifiable argument. +- **`#!cpp inout this`:** Writing `#!cpp operator=: (inout this /*...*/ )` is an assignment operator (only), because an `inout` parameter requires an initialized modifiable argument. -- **`move this`:** Writing `operator=: (move this)` is the destructor. No other parameters are allowed, so it connotes "move `this` nowhere." +- **`#!cpp move this`:** Writing `#!cpp operator=: (move this)` is the destructor. No other parameters are allowed, so it connotes "move `#!cpp this` nowhere." -Unifying `operator=` enables usable `out` parameters, which is essential for composable guaranteed initialization. We want the expression syntax `x = value` to be able to call a constructor or an assignment operator, so naming them both `operator=` is consistent. +Unifying `#!cpp operator=` enables usable `out` parameters, which is essential for composable guaranteed initialization. We want the expression syntax `#!cpp x = value` to be able to call a constructor or an assignment operator, so naming them both `#!cpp operator=` is consistent. TODO Return type of assignment operator? -> Note: Writing `=` always invokes an `operator=` (in fact for a Cpp2-authored type, and semantically for a Cpp1-authored type). This avoids the Cpp1 inconsistency that "writing `=` calls `operator=`, except when it doesn't" (such as in a Cpp1 variable initialization). Conversely, `operator=` is always invoked by `=` in Cpp2. +> Note: Writing `=` always invokes an `#!cpp operator=` (in fact for a Cpp2-authored type, and semantically for a Cpp1-authored type). This avoids the Cpp1 inconsistency that "writing `=` calls `#!cpp operator=`, except when it doesn't" (such as in a Cpp1 variable initialization). Conversely, `#!cpp operator=` is always invoked by `=` in Cpp2. ### `that` — A source parameter -All type-scope functions can have **`that`** as their second parameter, which is a synonym for the object to be copied/moved from. Like `this`, at type scope it is never declared with an explicit `: its_type` because its type is always the current type. Unlike `this`, `that` is always passed as a `in` (the default) or `move` parameter. +All type-scope functions can have **`that`** as their second parameter, which is a synonym for the object to be copied/moved from. Like `this`, at type scope it is never declared with an explicit `: its_type` because its type is always the current type. `that` can be an `in` (default) or `move` parameter. Which you choose naturally determines what kind of member function is being declared: -- **`in that`**: Writing `myfunc: (/*...*/ this, that)`, which is shorthand for `myfunc: (/*...*/ this, in that)`, is naturally both a copy and move function, because it can accept an lvalue or an rvalue `that` argument. If you don't write a more-specialized `move that` move function, Cpp2 will automatically use the `in that` function also for move. +- **`in that`**: Writing `#!cpp myfunc: (/*...*/ this, that)`, which is shorthand for `#!cpp myfunc: (/*...*/ this, in that)`, is naturally both a copy and move function, because it can accept an lvalue or an rvalue `that` argument. If you don't write a more-specialized `move that` move function, Cpp2 will automatically use the `in that` function also for move. -- **`move that`**: Writing `myfunc: (/*...*/ this, move that)` defines a move function. +- **`move that`**: Writing `#!cpp myfunc: (/*...*/ this, move that)` defines a move function. -Putting `this` and `that` together: The most general form of `operator=` is `operator=: (out this, that)`. It 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. +Putting `this` and `that` together: The most general form of `#!cpp operator=` is **`#!cpp operator=: (out this, that)`**. It 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. -### `operator=` can generalize (A)ssignment from construction, and (M)ove from copy +### `#!cpp operator=` can generalize (A)ssignment from construction, and (M)ove from copy As mentioned above: -- If you don't write an `inout this` function, Cpp2 will use your `out this` function in its place (if you wrote one). +- If you don't write an `#!cpp inout this` function, Cpp2 will use your `#!cpp out this` function in its place (if you wrote one). - If you don't write a `move that` function, Cpp2 will use your `in that` function in its place (if you wrote one). > Note: When lowering to Cpp1, this just means generating the applicable special member functions from the appropriate Cpp2 function. @@ -158,11 +158,11 @@ In Cpp1 terms, they can be described as follows: - **The arrows are transitive.** For example, if you write a copy constructor and nothing else, the move constructor, copy assignment operator, and move assignment operator are generated. -- **M2 is preferred over A2.** Both M2 and A2 can generate a missing `(inout this, move that)` function. If both options are available, Cpp2 prefers to use M2 (generate move assignment from copy assignment, which could itself have been generated from copy construction) rather than A2 (generate move assignment from move construction). This is because M2 is a better fit: Move assignment is more like copy assignment than like move construction, because assignments are designed structurally to set the value of an existing `this` object. +- **M2 is preferred over A2.** Both M2 and A2 can generate a missing `#!cpp (inout this, move that)` function. If both options are available, Cpp2 prefers to use M2 (generate move assignment from copy assignment, which could itself have been generated from copy construction) rather than A2 (generate move assignment from move construction). This is because M2 is a better fit: Move assignment is more like copy assignment than like move construction, because assignments are designed structurally to set the value of an existing `#!cpp this` object. -The most general `operator=` with `that` is `(out this, that)`. In Cpp1 terms, it generates all four combinations of { copy, move } x { constructor, assignment }. This is often sufficient, so you can write all these value-setting functions just once. If you do want to write a more specific version that does something else, though, you can always write it too. +The most general `#!cpp operator=` with `that` is `#!cpp (out this, that)`. In Cpp1 terms, it generates all four combinations of { copy, move } x { constructor, assignment }. This is often sufficient, so you can write all these value-setting functions just once. If you do want to write a more specific version that does something else, though, you can always write it too. -> Note: Generating `inout this` (assignment) from `out this` also generates **converting assignment** from converting construction, which is a new thing. Today in Cpp1, if you write a converting constructor from another type `X`, you may or may not write the corresponding assignment from `X`; in Cpp2 you will get that by default, and it sets the object to the same state as the converting constructor from `X` does. +> Note: Generating `#!cpp inout this` (assignment) from `#!cpp out this` also generates **converting assignment** from converting construction, which is a new thing. Today in Cpp1, if you write a converting constructor from another type `X`, you may or may not write the corresponding assignment from `X`; in Cpp2 you will get that by default, and it sets the object to the same state as the converting constructor from `X` does. @@ -172,23 +172,23 @@ There are only two defaults the language will generate implicitly for a type: - 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 other than the destructor are written by hand, a public default constructor is generated by default. +- If no `#!cpp operator=` functions other than the destructor 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). +All other `#!cpp operator=` functions are explicitly written, either by hand or by opting into applying a metafunction (see below). > 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" for the purpose of suppressing unwanted generated functions. ### Memberwise by default -All copy/move/comparison `operator=` functions are memberwise by default in Cpp2. That includes when you write memberwise construction and assignment yourself. +All copy/move/comparison `#!cpp operator=` functions are memberwise by default in Cpp2. That includes when you write memberwise construction and assignment yourself. -In a hand-written `operator=`: +In a hand-written `#!cpp operator=`: - The body must begin with a series of `member = value;` statements, one for each of the type's data members (including base classes) in declaration order. - If the body does not mention a member in the appropriate place in the beginning section, by default the member's default initializer is used. -- In an assignment operator (`inout this`), you can explicitly skip setting a member by writing `member = _;` where it would normally be set if you know you have a reason to set its value later instead or if the existing value needs to be preserved. +- In an assignment operator (`#!cpp inout this`), you can explicitly skip setting a member by writing `member = _;` where it would normally be set if you know you have a reason to set its value later instead or if the existing value needs to be preserved. For example: @@ -239,9 +239,9 @@ main: () = { -## `operator<=>` — Unified comparisons +## `#!cpp operator<=>` — Unified comparisons -Most of Cpp2's `operator<=>` has already been merged into ISO C++, except for allowing chained comparisons. In Cpp2, comparisons can be safely chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: +Most of Cpp2's `#!cpp operator<=>` has already been merged into ISO C++ (Cpp1), except for allowing chained comparisons. In Cpp2, comparisons can be safely chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: - **Valid chains: All `<`/`<=`, all `>`/`>=`, or all `==`.** All mathematically sound and safe chains like `a <= b < c` are supported, with efficient single evaluation of each term. They are "sound" because they are transitive; these chains imply a relationship between `a` and `c` (in this case, the chain implies that `a <= c` is also true). diff --git a/docs/cppfront/mixed.md b/docs/cppfront/mixed.md index 9aac7e674e..f3464f4024 100644 --- a/docs/cppfront/mixed.md +++ b/docs/cppfront/mixed.md @@ -17,7 +17,7 @@ When cppfront compiles such a mixed file, it just passes through the Cpp1 code a For example, this source file is fine, where the Cpp2 and Cpp1 code are side by side and seamlessly call each other directly as usual: -``` cpp title="mixed.cpp2 — Mixing Cpp1 and Cpp2 code side by side in the same source file is okay" linenums="1" hl_lines="4-7" +``` cpp title="mixed.cpp2 — Mixing Cpp1 and Cpp2 code side by side in the same source file is okay" linenums="1" hl_lines="4-7" #include // Cpp1 #include // Cpp1 @@ -55,5 +55,5 @@ main: () = { // Cpp2 } // Cpp2 ``` -The above nesting is not supported because it would create not just parsing problems but also semantic ambiguities. For example, lines 11-13 are syntactically valid as Cpp1 or as Cpp2, but if they are treated as Cpp2 then the `words[0]` and `words[1]` expressions' `std::vector::operator[]` calls are bounds-checked and bounds-safe by default, whereas if they are treated as Cpp1 then they are not bounds-checked. And that's a pretty important difference to be sure about! +The above nesting is not supported because it would create not just parsing problems but also semantic ambiguities. For example, lines 11-13 are syntactically valid as Cpp1 or as Cpp2, but if they are treated as Cpp2 then the `#!cpp words[0]` and `#!cpp words[1]` expressions' `#!cpp std::vector::operator[]` calls are bounds-checked and bounds-safe by default, whereas if they are treated as Cpp1 then they are not bounds-checked. And that's a pretty important difference to be sure about! diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md index 1af522b8f8..72a2f85799 100644 --- a/docs/welcome/hello-world.md +++ b/docs/welcome/hello-world.md @@ -2,7 +2,7 @@ ## A `hello.cpp2` program -Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program, no `#include` required: +Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program, no `#!cpp #include` required: ``` cpp title="hello.cpp2 — on one line" main: () = std::cout << "Hello, world!\n"; @@ -28,7 +28,7 @@ This short program code already illustrates a few Cpp2 essentials. - `main` **is a** function that takes no arguments and returns nothing, and is **defined as** the code body shown. -- `words` **is a** `std::vector`, initially **defined as** holding `"Alice"` and `"Bob"`. +- `words` **is a** `std::vector`, initially **defined as** holding `#!cpp "Alice"` and `#!cpp "Bob"`. - `hello` **is a** function that takes a `std::string_view` it will only read from and that returns nothing, and is **defined as** code that prints the string to `cout` the usual C++ way. @@ -40,13 +40,13 @@ All grammar is context-free. In particular, we (the human reading the code, and - Declaring `words` uses **"CTAD"** (C++'s normal [constructor template argument deduction](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)) to deduce the type of elements in the `vector`. -- Calling `words[0]` and `words[1]` is **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()` and `std::ssize()`, and for which `std::begin()` returns a random access iterator, including any in-house integer-indexed container types you already have that can easily provide `std::size()` and `std::ssize()` if they don't already. +- Calling `#!cpp words[0]` and `#!cpp words[1]` is **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()` and `std::ssize()`, and for which `std::begin()` returns a random access iterator, including any in-house integer-indexed container types you already have that can easily provide `std::size()` and `std::ssize()` if they don't already. -- `hello` uses **string interpolation** to be able to write `"Hello, (msg)$!\n"` instead of `"Hello, " << msg << "!\n"`. +- `hello` uses **string interpolation** to be able to write `#!cpp "Hello, (msg)$!\n"` instead of `#!cpp "Hello, " << msg << "!\n"`. **Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: -- We can omit writing the `-> void` return type for a function that doesn't return anything, as both of these functions do. +- We can omit writing the `#!cpp -> void` return type for a function that doesn't return anything, as both of these functions do. - We can omit the `{` `}` around single-statement function bodies, as `hello` does. @@ -56,9 +56,9 @@ For details, see [Design note: Defaults are one way to say the same thing](https **Order-independent by default.** Did you notice that `main` called `hello`, which was defined later? Cpp2 code is order-independent by default — there are no forward declarations. -**Seamless compatibility and interop.** We can just use `std::cout` and `std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using ordinary direct calls without any wrapping/marshaling/thunking. +**Seamless compatibility and interop.** We can just use `std::cout` and `#!cpp std::operator<<` and `std::string_view` directly as usual. Cpp2 code works with any C++ code or library, including the standard library, using ordinary direct calls without any wrapping/marshaling/thunking. -**C++ standard library is always available.** We didn't need `#include ` or `import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`), or if you use `-im` (short for `-import-std`). Cppfront is regularly updated to be compatible with C++23 and the latest draft C++26 library additions as soon as the ISO C++ committee votes them into the C++26 working draft, so as soon as you have a C++ implementation that has a new standard (or bleeding-edge draft standard!) C++ library feature, you'll be able to fully use it in Cpp2 code. +**C++ standard library is always available.** We didn't need `#!cpp #include ` or `#!cpp import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`), or if you use `-im` (short for `-import-std`). Cppfront is regularly updated to be compatible with C++23 and the latest draft C++26 library additions as soon as the ISO C++ committee votes them into the C++26 working draft, so as soon as you have a C++ implementation that has a new standard (or bleeding-edge draft standard!) C++ library feature, you'll be able to fully use it in Cpp2 code. ## Building `hello.cpp2` @@ -99,13 +99,13 @@ Here we can see more of how Cpp2 makes it features work. **How: Simple, safe, and efficient by default.** - **Line 9: CTAD** just works, because it turns into ordinary C++ code which already supports CTAD. -- **Lines 10-11: Automatic bounds checking** is added to `words[0]` and `words[1]` nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::size` and `std::ssize`-aware, when you use them from safe Cpp2 code. +- **Lines 10-11: Automatic bounds checking** is added to `#!cpp words[0]` and `#!cpp words[1]` nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::size` and `std::ssize`-aware, when you use them from safe Cpp2 code. - **Line 11: Automatic move from last use** ensures the last use of `words` will automatically avoid a copy if it's being passed to something that's optimized for rvalues. -- **Line 16: String interpolation** performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `bool`, to print `false` and `true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). +- **Line 16: String interpolation** performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `#!cpp bool`, to print `#!cpp false` and `#!cpp true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). **How: Simplicity through generality + defaults.** -- **Line 7: `in` parameters** are implemented using `cpp2::in<>`, which is smart enough to pass by `const` value when that's safe and appropriate, otherwise by `const&`, so you don't have to choose the right one by hand. +- **Line 7: `in` parameters** are implemented using `#cpp2::in<>`, which is smart enough to pass by `#!cpp const` value when that's safe and appropriate, otherwise by `#!cpp const&`, so you don't have to choose the right one by hand. **How: Order-independent by default.** @@ -117,7 +117,7 @@ Here we can see more of how Cpp2 makes it features work. **How: C++ standard library always available.** -- **Lines 1 and 3: `std::` is available** because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet). The generated code tells `cpp2util.h` to `import` the entire standard library as a module (or do the equivalent via headers if modules are not available). +- **Lines 1 and 3: `std::` is available** because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet). The generated code tells `cpp2util.h` to `#!cpp import` the entire standard library as a module (or do the equivalent via headers if modules are not available). ## Building and running `hello.cpp` with any recent C++ compiler diff --git a/mkdocs.yml b/mkdocs.yml index e4bf4e6326..4cf880cde9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -27,6 +27,7 @@ theme: - search.suggest - search.highlight - content.tabs.link + - content.code.annotate - content.code.annotation - content.code.copy - content.footnote.tooltips From 9f75d3683d76571fb23820f2bcfe7303df37c99b Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 19 Feb 2024 11:11:42 -0800 Subject: [PATCH 41/64] Move function calls to expressions --- docs/cpp2/expressions.md | 46 ++++++++++++++++++++++++++++++++++++++++ docs/cpp2/functions.md | 44 -------------------------------------- 2 files changed, 46 insertions(+), 44 deletions(-) diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md index bbfb6d1ec8..665168d048 100644 --- a/docs/cpp2/expressions.md +++ b/docs/cpp2/expressions.md @@ -1,6 +1,52 @@ # Common expressions +## Calling functions: `f(x)` syntax, and `x.f()` UFCS syntax + +A function call like `f(x)` is a normal function call that will call non-member functions only, as usual in C++. + +A function call like `x.f()` is a unified function call syntax (aka UFCS) call. It will call a member function if one is available, and otherwise will call `f(x)`. Having UFCS is important for generic code that may want to call a member or a non-member function, whichever is available. It's also important to enable fluid programming styles and natural IDE autocompletion support. + +An operator notation call like `#!cpp a + b` will call an overloaded operator function if one is available, as usual in C++. + +For example: + +``` cpp title="Function calls" hl_lines="3 7 11 16 19 20" +// Generic function to log something +// This calls operator<< using operator notation +log: (x) = clog << x; + +f: ( v : std::vector ) = { + // This calls log() with the result of std::vector::size() + log( v.size() ); + + // This calls log() with the result of std::ssize(v), because + // v doesn't have a .ssize member function + log( v.ssize() ); +} + +// Generic function to print "hello, ___!" for any printable type +hello: (name) = { + myfile := fopen("xyzzy.txt", "w"); + // Direct calls to C nonmember functions, using UFCS and safe + // string interpolation (instead of type-unsafe format strings) + myfile.fprintf( "Hello, (name)$!\n" ); + myfile.fclose(); + // The C and C++ standard libraries are not only fully available, + // but safer (and arguably nicer) when used from Cpp2 syntax code +} +``` + +To explicitly treat an object name passed as an argument as `move` or `out`, write that keyword before the variable name. + +- Explicit `move` is rarely needed. Every definite last use of a local variable will apply `move` by default. Writing `move` from an object before its definite last use means that later uses may see a moved-from state. + +- Explicit `out` is needed only when initializing a local variable separately from its declaration using a call to a function with an `out` parameter. For details, see [Guaranteed initialization](../cpp2/objects.md#Init). + +For example: + + + ## `_` — the "don't care" wildcard, including explicit discard `_` is pronounced **"don't care"** and allowed as a wildcard in most contexts. For example: diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index 196badb8f0..3c4e08c271 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -5,50 +5,6 @@ TODO -## Calling functions: `f(x)` syntax, and `x.f()` UFCS syntax - -A function call like `f(x)` is a normal function call that will call non-member functions only, as usual in C++. - -A function call like `x.f()` is a unified function call syntax (aka UFCS) call. It will call a member function if one is available, and otherwise will call `f(x)`. Having UFCS is important for generic code that may want to call a member or a non-member function, whichever is available. It's also important to enable fluid programming styles and natural IDE autocompletion support. - -An operator notation call like `#!cpp a + b` will call an overloaded operator function if one is available, as usual in C++. - -For example: - -``` cpp title="Function calls" hl_lines="3 7 11 16 19 20" -// Generic function to log something -// This calls operator<< using operator notation -log: (x) = clog << x; - -f: ( v : std::vector ) = { - // This calls log() with the result of std::vector::size() - log( v.size() ); - - // This calls log() with the result of std::ssize(v), because - // v doesn't have a .ssize member function - log( v.ssize() ); -} - -// Generic function to print "hello, ___!" for any printable type -hello: (name) = { - myfile := fopen("xyzzy.txt", "w"); - // Direct calls to C nonmember functions, using UFCS and safe - // string interpolation (instead of type-unsafe format strings) - myfile.fprintf( "Hello, (name)$!\n" ); - myfile.fclose(); - // The C and C++ standard libraries are not only fully available, - // but safer (and arguably nicer) when used from Cpp2 syntax code -} -``` - -To explicitly treat an object name passed as an argument as `move` or `out`, write that keyword before the variable name. - -- Explicit `move` is rarely needed. Every definite last use of a local variable will apply `move` by default. Writing `move` from an object before its definite last use means that later uses may see a moved-from state. - -- Explicit `out` is needed only when initializing a local variable separately from its declaration using a call to a function with an `out` parameter. For details, see [Guaranteed initialization](../cpp2/objects.md#Init). - -For example: - ## Parameters From ddfa21ee0cbadcc2559bd43f93c978c55bf27865 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 19 Feb 2024 12:45:02 -0800 Subject: [PATCH 42/64] Add capture section And add Cpp1 lowering notes for parameter passing --- docs/cpp2/common.md | 4 +-- docs/cpp2/expressions.md | 66 ++++++++++++++++++++++++++++++++++++++-- docs/cpp2/functions.md | 17 +++++------ mkdocs.yml | 4 +-- 4 files changed, 76 insertions(+), 15 deletions(-) diff --git a/docs/cpp2/common.md b/docs/cpp2/common.md index 4c5275f355..c0e2323667 100644 --- a/docs/cpp2/common.md +++ b/docs/cpp2/common.md @@ -122,7 +122,7 @@ Both **`123.nm()`** and **`123.u8()`** are very similar to user-defined literal Operators have the same precedence and associativity as in Cpp1, but some unary operators that are prefix (always or sometimes) in Cpp1 are postfix (always) in Cpp2. -## Unary operators +### Unary operators The operators `!`, `+`, and `-` are prefix, as in Cpp1. For example: @@ -178,7 +178,7 @@ Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~ For more details, see [Design note: Postfix unary operators vs binary operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-unary-operators-vs-binary-operators). -## Binary operators +### Binary operators Binary operators are the same as in Cpp1. From highest to lowest precedence: diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md index 665168d048..b66874a78e 100644 --- a/docs/cpp2/expressions.md +++ b/docs/cpp2/expressions.md @@ -212,6 +212,68 @@ For more examples, see also the examples in the previous two sections on `is` an ## `$` — captures, including interpolations -TODO +Suffix `$` is pronounced **"paste the value of"** and captures the value of an expression at the point when the expression where the capture is written is evaluated. Depending the complexity of the capture expression `expr$` and where it is used, parentheses `(expr)$` may be required for precedence or to show the boundaries of the expression. + +`x$` always captures `x` by value. To capture by reference, take the address and capture a pointer using `x&$`. If the value is immediately used, dereference again; for example `:(val) total&$* += val` adds to the `total` local variable itself, not a copy. + +Any capture is evaluated at the point where it is written, in the function expression, contract postcondition, or string literal. The stored captured value can then be used later when the context it is in is evaluated, such as when the function expression body it's in is actually called later (one or more times), when the postcondition it's in is evaluated later when the function returns, or when the string literal it's in is read later. + + +### Capture in function expressions (aka lambdas) + +Any capture in a function expression body is evaluated at the point where the function expression is written, at the declaration of the function expression. The function expression itself is then evaluated each time the function is invoked, and can reference the captured value. + +For example: + +``` cpp title="Capture in an unnamed function expression (aka lambda)" hl_lines="6 7" +main: () = { + s := "-sh\n"; + vec: std::vector = (1, 2, 3, 5, 8, 13 ); + + vec.std::ranges::for_each( + :(i) = { std::cout << i << s$; } + // Function capture: Paste local variable value + ); +} +``` + +The design and syntax are selected so that capture is spelled the same way in all contexts. For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). + + +### Capture in contract postconditions + +Any capture in a postcondition is evaluated at the point where the postcondition is written, at the beginning (entry) of the function. The postcondition itself is then evaluated when the function returns, and can reference the captured value. + +For example: + +``` cpp title="Capture in contract postconditions" hl_lines="2 3" +push_back: (coll, value) + [[post: coll.ssize() == coll.ssize()$ + 1]] + // Postcondition capture: Paste "old" size += { + // ... +} +``` + + +### Capture in string interpolation + +A string literal can capture the value of an expression `expr` by writing `(expr)$` inside the string literal. The `(` `)` are required, and cannot be nested. + +Any capture in a string literal is evaluated at the point where the string literal is written. The string literal can be used repeatedly later, and includes the captured value. + +For example: + +``` cpp title="Capture for string interpolation" hl_lines="2 4" +x := 0; +std::cout << "x is (x)$\n"; +x = 1; +std::cout << "now x+2 is (x+2)$\n"; + +// prints: +// x is 0 +// now x+2 is 3 +``` + +A string literal has type `std::string` if it performs any captures, otherwise it is a normal C/C++ string literal (array of characters). -For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index 3c4e08c271..5fce22f59a 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -10,15 +10,14 @@ TODO There are six ways to pass parameters that cover all use cases: -| Parameter kind | "Pass me an `x` I can ______" | Accepts arguments that are | Special semantics | -|---|---|---|---| -| `in` (default) | read from | anything | always `#!cpp const`

automatically passes by value if cheaply copyable | -| `copy` | take a copy of | anything | acts like a normal local variable initialized with the argument | -| `inout` | read from and write to | lvalues | | -| `out` | write to (including construct) | lvalues, including uninitialized lvalues | must `=` assign/construct before other uses | -| `move` | move from | rvalues | automatically moves from every definite last use | -| `forward` | forward | anything | automatically forwards from every definite last use | - +| Parameter ***kind*** | "Pass me an `x` I can ______" | Accepts arguments that are | Special semantics | ***kind*** `x: X` Compiles to Cpp1 as | +|---|---|---|---|---| +| `in` (default) | read from | anything | always `#!cpp const`

automatically passes by value if cheaply copyable | `X const x` or `X const& x` | +| `copy` | take a copy of | anything | acts like a normal local variable initialized with the argument | `X x` | +| `inout` | read from and write to | lvalues | | `X& x` | +| `out` | write to (including construct) | lvalues, including uninitialized lvalues | must `=` assign/construct before other uses | `cpp2::out` | +| `move` | move from | rvalues | automatically moves from every definite last use | `X&&` | +| `forward` | forward | anything | automatically forwards from every definite last use | `T&&` constrained to type `X` | > Note: All parameters and other objects in Cpp2 are `#!cpp const` by default, except for local variables. For details, see [Design note: `#!cpp const` objects by default](https://github.com/hsutter/cppfront/wiki/Design-note%3A-const-objects-by-default). diff --git a/mkdocs.yml b/mkdocs.yml index 4cf880cde9..9b276bee66 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -55,8 +55,8 @@ nav: - 'Hello, world!': welcome/hello-world.md - 'Adding cppfront to your existing C++ project': welcome/integration.md - 'Cpp2 reference': - - 'Common programming concepts': cpp2/common.md - - 'Common expressions': cpp2/expressions.md + - 'Common concepts': cpp2/common.md + - 'Expressions': cpp2/expressions.md - 'Declaration syntax': cpp2/declarations.md - 'Objects, initialization, and memory': cpp2/objects.md - 'Functions, branches, and loops': cpp2/functions.md From 84fc3c2e06c9ff4cf6397f38913088faa11adddb Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 19 Feb 2024 13:40:26 -0800 Subject: [PATCH 43/64] Add Mermaid build diagram --- docs/index.md | 6 ++++++ docs/welcome/hello-world.md | 15 +++++++++++++++ mkdocs.yml | 6 +++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 498526c32e..6de1fa1fb2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,6 +1,12 @@ # Overview: What are Cpp2 and cppfront? How do I get and build cppfront? +``` cpp title="hello.cpp2" +main: () = { + std::cout << "Hello, world!\n"; +} +``` + ## What is Cpp2? "Cpp2," short for "C++ syntax 2," is my ([Herb Sutter's](https://github.com/hsutter)) personal project to try to make writing ordinary C++ types/functions/objects be much **simpler and safer**, without breaking backward compatibility. Bjarne Stroustrup said it best: diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md index 72a2f85799..e45507c3ec 100644 --- a/docs/welcome/hello-world.md +++ b/docs/welcome/hello-world.md @@ -1,5 +1,20 @@ # **Hello, world!** +
+``` cpp title="hello.cpp2" +main: () = { + std::cout << "Hello, world!\n"; +} +``` + +``` mermaid +graph TB + A[hello.cpp2] ==> B([cppfront]); + B ==> C[hello.cpp]; + C ==> D([Your favorite C++ compiler]); +``` +
+ ## A `hello.cpp2` program Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program, no `#!cpp #include` required: diff --git a/mkdocs.yml b/mkdocs.yml index 9b276bee66..5519c7d70d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -80,7 +80,11 @@ markdown_extensions: generic: true - footnotes - pymdownx.details - - pymdownx.superfences + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format - pymdownx.mark - attr_list From 430afe0ced507b0f740789eba30a53ce8c4aeeb2 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 19 Feb 2024 13:45:54 -0800 Subject: [PATCH 44/64] Add TODO for `member = _;` --- docs/cpp2/types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index 2c9b6d6305..3d74173740 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -236,7 +236,7 @@ main: () = { > Note: This makes memberwise semantics symmetric for construction and assignment. In Cpp1, only non-copy/move constructors have a default, which is to initialize a member with its default initializer. In Cpp2, both constructors and assignment operators default to using the default initializer if it's a conversion function (non-`that`, aka non-copy/move), and using memberwise `member = that.member;` for copy/move functions. - +TODO: include an example of using `member = _;` ## `#!cpp operator<=>` — Unified comparisons From 79252d431c5b5bc79616402d671ca578e2b05922 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 19 Feb 2024 17:52:43 -0800 Subject: [PATCH 45/64] Add interpolation formatting, and other minor cleanup --- docs/cpp2/expressions.md | 19 ++++++++++--------- docs/welcome/hello-world.md | 20 +++++--------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md index b66874a78e..d788126344 100644 --- a/docs/cpp2/expressions.md +++ b/docs/cpp2/expressions.md @@ -212,7 +212,7 @@ For more examples, see also the examples in the previous two sections on `is` an ## `$` — captures, including interpolations -Suffix `$` is pronounced **"paste the value of"** and captures the value of an expression at the point when the expression where the capture is written is evaluated. Depending the complexity of the capture expression `expr$` and where it is used, parentheses `(expr)$` may be required for precedence or to show the boundaries of the expression. +Suffix `$` is pronounced **"paste the value"** and captures the value of an expression at the point when the expression where the capture is written is evaluated. Depending the complexity of the capture expression `expr$` and where it is used, parentheses `(expr)$` may be required for precedence or to show the boundaries of the expression. `x$` always captures `x` by value. To capture by reference, take the address and capture a pointer using `x&$`. If the value is immediately used, dereference again; for example `:(val) total&$* += val` adds to the `total` local variable itself, not a copy. @@ -225,14 +225,14 @@ Any capture in a function expression body is evaluated at the point where the fu For example: -``` cpp title="Capture in an unnamed function expression (aka lambda)" hl_lines="6 7" +``` cpp title="Capture in an unnamed function expression (aka lambda)" hl_lines="6" main: () = { s := "-sh\n"; vec: std::vector = (1, 2, 3, 5, 8, 13 ); vec.std::ranges::for_each( :(i) = { std::cout << i << s$; } - // Function capture: Paste local variable value + // Paste the value of `s` ); } ``` @@ -246,10 +246,10 @@ Any capture in a postcondition is evaluated at the point where the postcondition For example: -``` cpp title="Capture in contract postconditions" hl_lines="2 3" +``` cpp title="Capture in contract postconditions" hl_lines="2" push_back: (coll, value) [[post: coll.ssize() == coll.ssize()$ + 1]] - // Postcondition capture: Paste "old" size + // Paste the value of `coll.ssize()` = { // ... } @@ -258,22 +258,23 @@ push_back: (coll, value) ### Capture in string interpolation -A string literal can capture the value of an expression `expr` by writing `(expr)$` inside the string literal. The `(` `)` are required, and cannot be nested. +A string literal can capture the value of an expression `expr` by writing `(expr)$` inside the string literal. The `(` `)` are required, and cannot be nested. A string literal has type `std::string` if it performs any captures, otherwise it is a normal C/C++ string literal (array of characters). Any capture in a string literal is evaluated at the point where the string literal is written. The string literal can be used repeatedly later, and includes the captured value. For example: -``` cpp title="Capture for string interpolation" hl_lines="2 4" +``` cpp title="Capture for string interpolation" hl_lines="2 5" x := 0; std::cout << "x is (x)$\n"; + // Paste the value of `x` x = 1; std::cout << "now x+2 is (x+2)$\n"; + // Paste the value of `x+2` // prints: // x is 0 // now x+2 is 3 ``` -A string literal has type `std::string` if it performs any captures, otherwise it is a normal C/C++ string literal (array of characters). - +A string literal capture can include a `:suffix` where the suffix is a [standard C++ format specification](https://en.cppreference.com/w/cpp/utility/format/spec). For example, `#!cpp (x.price(): <10.2f)$` evaluates `x.price()` and converts the result to a string with 10-character width, 2 digits of precision, and left-justified. diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md index e45507c3ec..acff83b6ec 100644 --- a/docs/welcome/hello-world.md +++ b/docs/welcome/hello-world.md @@ -1,19 +1,11 @@ # **Hello, world!** -
-``` cpp title="hello.cpp2" -main: () = { - std::cout << "Hello, world!\n"; -} -``` - ``` mermaid -graph TB - A[hello.cpp2] ==> B([cppfront]); +graph LR + A[hello.cpp2] ==> B(["` **cppfront** `"]); B ==> C[hello.cpp]; - C ==> D([Your favorite C++ compiler]); + C ==> D([Your favorite
C++ compiler

... and IDE / libraries / build
system / in-house tools / ...]); ``` -

## A `hello.cpp2` program @@ -30,7 +22,6 @@ main: () = { words: std::vector = ( "Alice", "Bob" ); hello( words[0] ); hello( words[1] ); - std::cout << "... and goodnight\n"; } hello: (msg: std::string_view) = @@ -98,7 +89,6 @@ auto main() -> int{ std::vector words {"Alice", "Bob"}; hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(words, 0)); hello(CPP2_ASSERT_IN_BOUNDS_LITERAL(std::move(words), 1)); - std::cout << "... and goodnight\n"; } auto hello(cpp2::in msg) -> void { @@ -116,7 +106,7 @@ Here we can see more of how Cpp2 makes it features work. - **Line 9: CTAD** just works, because it turns into ordinary C++ code which already supports CTAD. - **Lines 10-11: Automatic bounds checking** is added to `#!cpp words[0]` and `#!cpp words[1]` nonintrusively at the call site by default. Because it's nonintrusive, it works seamlessly with all existing container types that are `std::size` and `std::ssize`-aware, when you use them from safe Cpp2 code. - **Line 11: Automatic move from last use** ensures the last use of `words` will automatically avoid a copy if it's being passed to something that's optimized for rvalues. -- **Line 16: String interpolation** performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `#!cpp bool`, to print `#!cpp false` and `#!cpp true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). +- **Line 15: String interpolation** performs the string capture of `msg`'s current value via `cpp2::to_string`. That uses `std::to_string` when available, and it also works for additional types (such as `#!cpp bool`, to print `#!cpp false` and `#!cpp true` instead of `0` and `1`, without having to remember to use `std::boolalpha`). **How: Simplicity through generality + defaults.** @@ -128,7 +118,7 @@ Here we can see more of how Cpp2 makes it features work. **How: Seamless compatibility and interop.** -- **Lines 9, 12, and 16: Ordinary direct calls** to existing C++ code, so there's never a need for wrapping/marshaling/thunking. +- **Lines 9-11 and 16: Ordinary direct calls** to existing C++ code, so there's never a need for wrapping/marshaling/thunking. **How: C++ standard library always available.** From d3b6575cd33131c03ec99aa0ffa21d8f943371b6 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Tue, 20 Feb 2024 11:00:56 -0800 Subject: [PATCH 46/64] Expand comparisons section Also add short anchor names to all subsections that don't already have them And make anchor names all lowercase-like-this --- docs/cpp2/aliases.md | 8 +++--- docs/cpp2/common.md | 18 ++++++------ docs/cpp2/contracts.md | 4 +-- docs/cpp2/declarations.md | 6 ++-- docs/cpp2/expressions.md | 20 +++++++------- docs/cpp2/functions.md | 18 ++++++------ docs/cpp2/metafunctions.md | 20 ++++++++------ docs/cpp2/objects.md | 6 ++-- docs/cpp2/types.md | 55 +++++++++++++++++++++++++++++-------- docs/index.md | 6 ++-- docs/welcome/hello-world.md | 14 ++++------ docs/welcome/integration.md | 7 +++-- 12 files changed, 107 insertions(+), 75 deletions(-) diff --git a/docs/cpp2/aliases.md b/docs/cpp2/aliases.md index b81d2d7fca..30c53a603c 100644 --- a/docs/cpp2/aliases.md +++ b/docs/cpp2/aliases.md @@ -1,18 +1,18 @@ # Aliases -## Namespace aliases +## Namespace aliases TODO -## Type aliases +## Type aliases TODO -## Function aliases +## Function aliases TODO -## Object aliases +## Object aliases TODO diff --git a/docs/cpp2/common.md b/docs/cpp2/common.md index c0e2323667..41e848490b 100644 --- a/docs/cpp2/common.md +++ b/docs/cpp2/common.md @@ -1,6 +1,6 @@ # Common programming concepts -## `main` +## `main` As always, `main` is the entry point of the program. For example: @@ -39,7 +39,7 @@ main: (args) -> int - Some other type that your Cpp1 compiler(s) supports as a nonstandard extension. -## Comments +## Comments The usual `#!cpp // line comments` and `#!cpp /* stream comments */` are supported. For example: @@ -55,7 +55,7 @@ The usual `#!cpp // line comments` and `#!cpp /* stream comments */` are support ``` -## Reserved keywords +## Reserved keywords Cpp2 has very few globally reserved keywords; nearly all keywords are contextual, where they have their special meaning when they appear in a particular place in the grammar. For example: @@ -68,7 +68,7 @@ Cpp2 has very few globally reserved keywords; nearly all keywords are contextual In rare cases, usually when consuming code written in other languages, you may need to write a name that is a reserved keyword. The way to do that is to prefix it with `__identifer__`, which treats it as an ordinary identifier (without the prefix). -## Fundamental data types +## Fundamental data types Cpp2 supports the same fundamental types as today's Cpp1, but additionally provides the following aliases in namespace `cpp2`: @@ -97,7 +97,7 @@ Cpp2 supports the same fundamental types as today's Cpp1, but additionally provi | `_schar` | `#!cpp signed char` | Normally, prefer `i8` instead | | `_uchar` | `#!cpp unsigned char` | Normally, prefer `u8` instead | -## Type qualifiers +## Type qualifiers Types can be qualified with `#!cpp const` and `#!cpp *`. Types are written left-to-right, so a qualifier always applies to what immediately follows it. For example, to declare a `#!cpp const` pointer to a non-`#!cpp const` pointer to a `#!cpp const i32` object, write: @@ -106,7 +106,7 @@ Types can be qualified with `#!cpp const` and `#!cpp *`. Types are written left- p: const * * const i32; ``` -## Literals +## Literals Cpp2 supports the same `#!cpp 'c'`haracter, `#!cpp "string"`, binary, integer, and floating point literals as Cpp1, including most Unicode encoding prefixes and raw string literals. @@ -118,11 +118,11 @@ Cpp2 supports using Cpp1 user-defined literals for compatibility, to support sea Both **`123.nm()`** and **`123.u8()`** are very similar to user-defined literal syntax, and more general. -## Operators +## Operators Operators have the same precedence and associativity as in Cpp1, but some unary operators that are prefix (always or sometimes) in Cpp1 are postfix (always) in Cpp2. -### Unary operators +### Unary operators The operators `!`, `+`, and `-` are prefix, as in Cpp1. For example: @@ -178,7 +178,7 @@ Unary suffix operators must not be preceded by whitespace. When `*`, `&`, and `~ For more details, see [Design note: Postfix unary operators vs binary operators](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Postfix-unary-operators-vs-binary-operators). -### Binary operators +### Binary operators Binary operators are the same as in Cpp1. From highest to lowest precedence: diff --git a/docs/cpp2/contracts.md b/docs/cpp2/contracts.md index 6117c19ed3..627d2cf2f3 100644 --- a/docs/cpp2/contracts.md +++ b/docs/cpp2/contracts.md @@ -6,11 +6,11 @@ TODO -## Contract groups +## Contract groups TODO -## Customizing the violation handler +## Customizing the violation handler TODO diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index 90d352c5b4..3b749dee67 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -1,6 +1,6 @@ # Declaration syntax -## Unified declaration syntax +## Overview: Unified declaration syntax All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. @@ -61,10 +61,10 @@ n: namespace } ``` -> Note: `@enum` is a metafunction, which provides an easy way to opt into a group of defaults, constraints, and generated functions. For details, see [`@enum`, `@flag_enum`](metafunctions.md/#enum-flag_enum) +> Note: `@enum` is a metafunction, which provides an easy way to opt into a group of defaults, constraints, and generated functions. For details, see [`@enum`, `@flag_enum`](metafunctions.md#enum-flag_enum) -## `requires` constraints +## `requires` constraints TODO diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md index d788126344..79cdcf2756 100644 --- a/docs/cpp2/expressions.md +++ b/docs/cpp2/expressions.md @@ -1,7 +1,7 @@ # Common expressions -## Calling functions: `f(x)` syntax, and `x.f()` UFCS syntax +## Calling functions: `f(x)` syntax, and `x.f()` UFCS syntax A function call like `f(x)` is a normal function call that will call non-member functions only, as usual in C++. @@ -41,13 +41,13 @@ To explicitly treat an object name passed as an argument as `move` or `out`, wri - Explicit `move` is rarely needed. Every definite last use of a local variable will apply `move` by default. Writing `move` from an object before its definite last use means that later uses may see a moved-from state. -- Explicit `out` is needed only when initializing a local variable separately from its declaration using a call to a function with an `out` parameter. For details, see [Guaranteed initialization](../cpp2/objects.md#Init). +- Explicit `out` is needed only when initializing a local variable separately from its declaration using a call to a function with an `out` parameter. For details, see [Guaranteed initialization](../cpp2/objects.md#init). For example: -## `_` — the "don't care" wildcard, including explicit discard +## `_` — the "don't care" wildcard, including explicit discard `_` is pronounced **"don't care"** and allowed as a wildcard in most contexts. For example: @@ -84,7 +84,7 @@ _ = vec.emplace_back(1,2,3); For details, see [Design note: Explicit discard](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Explicit-discard). In Cpp2, data is always initialized, data is never silently lost, data flow is always visible. Data is precious, and it's always safe. -## `is` — safe type/value queries +## `is` — safe type/value queries An `x is C` expression allows safe type and value queries, and evaluates to `#!cpp true` if `x` matches constraint `C`. It supports both static and dynamic queries, including customization, with support for standard library dynamic types like `std::variant`, `std::optional`, `std::expected`, and `std::any` provided out of the box. @@ -132,7 +132,7 @@ Here are some `is` queries with their Cpp1 equivalents. In this table, uppercase > Note: `is` unifies a variety of differently-named Cpp1 language and library queries under one syntax, and supports only the type-safe ones. -## `as` — safe casts and conversions +## `as` — safe casts and conversions An `x as T` expression allows safe type casts. `x` must be an object or expression, and `T` must be a type. Like `is`, `as` supports both static and dynamic typing, including customization, with support for standard library dynamic types like `std::variant`, `std::optional`, `std::expected`, and `std::any` provided out of the box. For example: @@ -169,7 +169,7 @@ Here are some `as` casts with their Cpp1 equivalents. In this table, uppercase n > Note: `as` unifies a variety of differently-named Cpp1 language and library casts and conversions under one syntax, and supports only the type-safe ones. -## `inspect` — pattern matching +## `inspect` — pattern matching An `inspect expr -> Type` expression allows pattern matching using `is`. @@ -210,7 +210,7 @@ test(42); For more examples, see also the examples in the previous two sections on `is` and `as`, many of which use `inspect`. -## `$` — captures, including interpolations +## `$` — captures, including interpolations Suffix `$` is pronounced **"paste the value"** and captures the value of an expression at the point when the expression where the capture is written is evaluated. Depending the complexity of the capture expression `expr$` and where it is used, parentheses `(expr)$` may be required for precedence or to show the boundaries of the expression. @@ -219,7 +219,7 @@ Suffix `$` is pronounced **"paste the value"** and captures the value of an expr Any capture is evaluated at the point where it is written, in the function expression, contract postcondition, or string literal. The stored captured value can then be used later when the context it is in is evaluated, such as when the function expression body it's in is actually called later (one or more times), when the postcondition it's in is evaluated later when the function returns, or when the string literal it's in is read later. -### Capture in function expressions (aka lambdas) +### Capture in function expressions (aka lambdas) Any capture in a function expression body is evaluated at the point where the function expression is written, at the declaration of the function expression. The function expression itself is then evaluated each time the function is invoked, and can reference the captured value. @@ -240,7 +240,7 @@ main: () = { The design and syntax are selected so that capture is spelled the same way in all contexts. For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). -### Capture in contract postconditions +### Capture in contract postconditions Any capture in a postcondition is evaluated at the point where the postcondition is written, at the beginning (entry) of the function. The postcondition itself is then evaluated when the function returns, and can reference the captured value. @@ -256,7 +256,7 @@ push_back: (coll, value) ``` -### Capture in string interpolation +### Capture in string interpolation A string literal can capture the value of an expression `expr` by writing `(expr)$` inside the string literal. The `(` `)` are required, and cannot be nested. A string literal has type `std::string` if it performs any captures, otherwise it is a normal C/C++ string literal (array of characters). diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index 5fce22f59a..227010b3f8 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -6,7 +6,7 @@ TODO -## Parameters +## Parameters There are six ways to pass parameters that cover all use cases: @@ -23,12 +23,12 @@ There are six ways to pass parameters that cover all use cases: > Note: All parameters and other objects in Cpp2 are `#!cpp const` by default, except for local variables. For details, see [Design note: `#!cpp const` objects by default](https://github.com/hsutter/cppfront/wiki/Design-note%3A-const-objects-by-default). -## Return values +## Return values TODO -### Function outputs +### Function outputs are not implicitly discardable A function's outputs are its return values, and the "out" state of any `out` and `inout` parameters. @@ -74,13 +74,13 @@ main: () > - A function call written in Cpp2 `x.f()` member call syntax always treats a non-`#!cpp void` return type as not discardable, even if the function was written in Cpp1 syntax that did not write `[[nodiscard]]`. -## Control flow +## Control flow -## `#!cpp if`, `#!cpp else` — Branches +## `#!cpp if`, `#!cpp else` — Branches TODO -## `#!cpp for`, `#!cpp while`, `#!cpp do` — Loops +## `#!cpp for`, `#!cpp while`, `#!cpp do` — Loops TODO @@ -104,15 +104,15 @@ outer: while i Unnamed function expressions (aka lambdas) TODO -## Move/forward from last use +## Move/forward from definite last use TODO -## Generality: Unifying functions and local scopes +## Generality: Unifying functions and local scopes TODO diff --git a/docs/cpp2/metafunctions.md b/docs/cpp2/metafunctions.md index 0647f1380a..b40a9e259d 100644 --- a/docs/cpp2/metafunctions.md +++ b/docs/cpp2/metafunctions.md @@ -11,7 +11,7 @@ A metafunction is a compile-time function that can participate in interpreting t The most important thing about metafunctions is that they are not hardwired language features — they are compile-time library code that uses the reflection and code generation API, that lets the author of an ordinary 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. -## Applying metafunctions +## Applying metafunctions Metafunctions provide an easy way for a type author to opt into a group of defaults, constraints, and generated functions: Just write `@name` afer the `:` of a declaration, where `name` is the name of the metafunction. This lets the type author declare (and the human reader see) the intent up front: "This isn't just any `type`, this is a `@value type`" which automatically gives the type default/copy/move construction and assignment, `<=>` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions: @@ -26,12 +26,12 @@ point2d: @value type = { } ``` -## Generating source code at compile time +## Generating source code at compile time TODO -## Built-in metafunctions +## Built-in metafunctions The following metafunctions are provided in the box with cppfront. @@ -45,7 +45,7 @@ TODO TODO -### ordered, weakly_ordered, partially_ordered +### ordered, weakly_ordered, partially_ordered TODO @@ -55,7 +55,7 @@ TODO TODO -### basic_value, value, weakly_ordered_value, partially_ordered_value +### basic_value, value, weakly_ordered_value, partially_ordered_value TODO @@ -65,7 +65,7 @@ TODO TODO -### `enum`, `flag_enum` +### `enum` Cpp2 has no `enum` feature hardwired into the language. Instead you apply the `@enum` metafunction when writing an ordinary `type`: @@ -111,7 +111,9 @@ janus: @enum type = { } ``` -There's also a `flag_enum` variation with power-of-two semantics and an unsigned underlying type: +### `flag_enum` + +`flag_enum` is a variation on `enum` that has power-of-two default enumerator values, a default unsigned underlying type, and supports bitwise operators: ``` cpp title="Using the @flag_enum metafunction when writing a type" hl_lines="11" // file_attributes is declaratively a safe flag enum type: @@ -207,12 +209,12 @@ TODO TODO -## Writing your own metafunctions +## Writing your own metafunctions TODO -## Reflection API reference +## Reflection API reference TODO diff --git a/docs/cpp2/objects.md b/docs/cpp2/objects.md index a519b3a536..6b400b309c 100644 --- a/docs/cpp2/objects.md +++ b/docs/cpp2/objects.md @@ -6,7 +6,7 @@ Its declaration is written using the same **name `:` kind `=` value** [declarati - **name** starts with a letter and is followed by other letters, digits, or `_`. Examples: `count`, `skat_game`, `Point2D` are valid names. -- **kind** is the object's type. In most places, except type scopes, you can write the `_` wildcard as the type (or omit the type entirely) to ask for the type to be deduced. When the type is a template, the templated arguments can be inferred from the constructor (via [CTAD](../welcome/hello-world.md#CTAD)). +- **kind** is the object's type. In most places, except type scopes, you can write the `_` wildcard as the type (or omit the type entirely) to ask for the type to be deduced. When the type is a template, the templated arguments can be inferred from the constructor (via [CTAD](../welcome/hello-world.md#ctad)). - **value** is the object's initial value. To use the default-constructed value, write `()`. @@ -26,7 +26,7 @@ count := -1; // same, deducing the object's type by just omitting it ``` -## Guaranteed initialization +## Guaranteed initialization Every object must be initialized using `=` before it is used. @@ -95,7 +95,7 @@ load_from_disk: (out buffer) = { In the above example, note the simple rule for branches: The local variable must be initialized on both the `#!cpp if` and `#!cpp else` branches, or neither branch. -## Heap objects +## Heap objects Objects can also be allocated on the heap using `#!cpp arena.new (/*initializer, arguments*/)` where `arena` is any object that acts as a memory arena and provides a `#!cpp .new` function template. Two memory arena objects are provided in namespace `cpp2`: diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index 3d74173740..88b431572f 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -20,7 +20,7 @@ mytype: type = } ``` -## `#!cpp this` — The parameter name +## `#!cpp this` — The parameter name **`#!cpp this`** is a synonym for the current object. Inside the scope of a type that has a member named `member`, `member` by default means `#!cpp this.member`. @@ -53,7 +53,7 @@ mytype: type = { } ``` -## `#!cpp this` — Inheritance +## `#!cpp this` — Inheritance Base types are written as members named `#!cpp this`. For example, just as a type could write a data member as `#!cpp data: string = "xyzzy";`, which is pronounced "`data` is a `string` defined as having the default value `#!cpp "xyzzy"`, a base type is written as `#!cpp this: Shape = (default, values);`, which is pronounced "`#!cpp this` is a `Shape` defined as having these default values." @@ -63,7 +63,7 @@ Because base and member subobjects are all declared in the same place (the type > Cpp2 code doesn't need workarounds like Boost's `base_from_member`, because all of the motivating examples for that can be written directly. See [this explanation](https://github.com/hsutter/cppfront/issues/334#issuecomment-1500984173) for details. -## `#!cpp virtual`, `#!cpp override`, and `#!cpp final` — Virtual functions +## `#!cpp virtual`, `#!cpp override`, and `#!cpp final` — Virtual functions A `#!cpp this` parameter can additionally be declared as one of the following: @@ -99,7 +99,7 @@ derived: type ``` -## `implicit` — Controlling conversion functions +## `implicit` — Controlling conversion functions A `#!cpp this` parameter of an `#!cpp operator=` function can additionally be declared as: @@ -108,7 +108,7 @@ A `#!cpp this` parameter of an `#!cpp operator=` function can additionally be de > Note: This reverses the Cpp1 default, where constructors are not "explicit" by default, and you have to write "explicit" to make them explicit. -## `#!cpp operator=` — Construction, assignment, and destruction +## `#!cpp operator=` — Construction, assignment, and destruction All value operations are spelled `#!cpp operator=`, including construction, assignment, and destruction. `#!cpp operator=` sets the value of `#!cpp this` object, so the `#!cpp this` parameter can be passed as anything but `in` (which would imply `#!cpp const`): @@ -125,7 +125,7 @@ TODO Return type of assignment operator? > Note: Writing `=` always invokes an `#!cpp operator=` (in fact for a Cpp2-authored type, and semantically for a Cpp1-authored type). This avoids the Cpp1 inconsistency that "writing `=` calls `#!cpp operator=`, except when it doesn't" (such as in a Cpp1 variable initialization). Conversely, `#!cpp operator=` is always invoked by `=` in Cpp2. -### `that` — A source parameter +### `that` — A source parameter All type-scope functions can have **`that`** as their second parameter, which is a synonym for the object to be copied/moved from. Like `this`, at type scope it is never declared with an explicit `: its_type` because its type is always the current type. @@ -138,7 +138,7 @@ All type-scope functions can have **`that`** as their second parameter, which is Putting `this` and `that` together: The most general form of `#!cpp operator=` is **`#!cpp operator=: (out this, that)`**. It 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. -### `#!cpp operator=` can generalize (A)ssignment from construction, and (M)ove from copy +### `#!cpp operator=` can generalize (A)ssignment from construction, and (M)ove from copy As mentioned above: - If you don't write an `#!cpp inout this` function, Cpp2 will use your `#!cpp out this` function in its place (if you wrote one). @@ -166,7 +166,7 @@ The most general `#!cpp operator=` with `that` is `#!cpp (out this, that)`. In C -### Minimal functions generated by default +### Minimal functions generated by default There are only two defaults the language will generate implicitly for a type: @@ -178,7 +178,7 @@ All other `#!cpp operator=` functions are explicitly written, either by hand or > 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" for the purpose of suppressing unwanted generated functions. -### Memberwise by default +### Memberwise by default All copy/move/comparison `#!cpp operator=` functions are memberwise by default in Cpp2. That includes when you write memberwise construction and assignment yourself. @@ -239,14 +239,45 @@ main: () = { TODO: include an example of using `member = _;` -## `#!cpp operator<=>` — Unified comparisons +## `#!cpp operator<=>` — Unified comparisons -Most of Cpp2's `#!cpp operator<=>` has already been merged into ISO C++ (Cpp1), except for allowing chained comparisons. In Cpp2, comparisons can be safely chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: +To write comparison functions for your type, usually you just need to write either or both of `operator<=>` and `operator==` with a first parameter of `this` and a second parameter of any type (usually `that` which is of the same type). If you omit the function body, a memberwise comparison will be generated by default. + +`operator<=>` must return one of `std::strong_ordering`, `std::partial_ordering`, or `std::weak_ordering`. It makes `<`, `<=`, `>`, and `>=` comparisons available for your type. Prefer a strong ordering unless you have a reason to use a partial or weak ordering. If you write `operator<=>` without a custom function body, `operator==` is generated for you. + +`operator==` must return `bool`. It makes `==` and `!=` comparisons available for your type. + +For example: + +``` cpp title="Writing the <=> operator" hl_lines="5-7 13" +item: type = { + x: i32 = 0; + y: std::string = (); + + operator<=>: (this, that) -> std::strong_ordering; + // memberwise by default: first compares x <=> that.x, + // then if those are equal compares y <=> that.y + + // ... +} + +test: (x: item, y: item) = { + if x != y { // ok + // ... + } +} +``` + +The above is the same as in Cpp1 because most of Cpp2's `#!cpp operator<=>` feature has already been merged into ISO C++ (Cpp1). In additiona, in Cpp2 comparisons with the same precedence can be safely chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: - **Valid chains: All `<`/`<=`, all `>`/`>=`, or all `==`.** All mathematically sound and safe chains like `a <= b < c` are supported, with efficient single evaluation of each term. They are "sound" because they are transitive; these chains imply a relationship between `a` and `c` (in this case, the chain implies that `a <= c` is also true). +> Note: These valid chains always give mathematically expected results, even when invoking existing comparison operators authored in Cpp1 syntax. + - **Invalid chains: Everything else.** Nonsense chains like `a >= b < c` and `a != b != c` are compile time errors. They are "nonsense" because they are non-transitive; these chains do not imply any relationship between `a` and `c`. +- **Non-chains: Mixed precedence is not a chain.** Expressions like `a What is Cpp2? "Cpp2," short for "C++ syntax 2," is my ([Herb Sutter's](https://github.com/hsutter)) personal project to try to make writing ordinary C++ types/functions/objects be much **simpler and safer**, without breaking backward compatibility. Bjarne Stroustrup said it best: @@ -28,14 +28,14 @@ We can't make an improvement that large to C++ via gradual evolution to today's **What it is.** Cpp2 aims to be another "skin" for C++ itself, just a simpler and safer way to write ordinary C++ types/functions/objects. It seamlessly uses Standard C++ modules and concepts requirements and other features, and it works with all existing C++20 or higher compilers and tools right out of the box with zero overhead. -## What is cppfront? +## What is cppfront? [**Cppfront**](https://github.com/hsutter/cppfront) is a compiler that compiles Cpp2 syntax to today's Cpp1 syntax. This lets you start trying out Cpp2 syntax in any existing C++ project and build system just by renaming a source file from `.cpp` to `.cpp2` and [adding a build step](#adding-cppfront-in-your-ide-build-system), and the result Just Works with every C++20 or higher compiler and all existing C++ tools (debuggers, build systems, sanitizers, etc.). This deliberately follows Bjarne Stroustrup's wise approach with [**cfront**](https://en.wikipedia.org/wiki/Cfront), the original C++ compiler: In the 1980s and 1990s, Stroustrup created cfront to translate C++ to pure C, and similarly ensured that C++ could be interleaved with C in the same source file, and that C++ could always call any C code with no wrapping/marshaling/thunking. By providing a C++ compiler that emitted pure C, Stroustrup ensured full compatibility with the C ecosystems that already existed, and made it easy for people to start trying out C++ code in any existing C project by adding just another build step to translate the C++ to C first, and the result Just Worked with existing C tools. -## How do I get and build cppfront? +## How do I get and build cppfront? The full source code for cppfront is at the [**Cppfront GitHub repo**](https://github.com/hsutter/cppfront). diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md index acff83b6ec..e746612fa8 100644 --- a/docs/welcome/hello-world.md +++ b/docs/welcome/hello-world.md @@ -2,12 +2,12 @@ ``` mermaid graph LR - A[hello.cpp2] ==> B(["` **cppfront** `"]); + A["` hello.cpp**2** `"] ==> B(["` **cppfront** `"]); B ==> C[hello.cpp]; C ==> D([Your favorite
C++ compiler

... and IDE / libraries / build
system / in-house tools / ...]); ``` -## A `hello.cpp2` program +## A `hello.cpp2` program Here is the usual one-line starter program that prints `Hello, world!`. Note that this is a complete program, no `#!cpp #include` required: @@ -42,13 +42,11 @@ All grammar is context-free. In particular, we (the human reading the code, and **Simple, safe, and efficient by default.** Cpp2 has contracts (tracking draft C++26 contracts), `inspect` pattern matching, string interpolation, automatic move from last use, and more. - - -- Declaring `words` uses **"CTAD"** (C++'s normal [constructor template argument deduction](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)) to deduce the type of elements in the `vector`. +- Declaring `words` uses **"CTAD"** (C++'s normal [constructor template argument deduction](https://en.cppreference.com/w/cpp/language/class_template_argument_deduction)) to deduce the type of elements in the `vector`. - Calling `#!cpp words[0]` and `#!cpp words[1]` is **bounds-checked by default**. From Cpp2 code, ordinary `std::vector` subscript accesses are safely bounds-checked by default without requiring any upgrade to your favorite standard library, and that's true for any similar subscript of something whose size can be queried using `std::size()` and `std::ssize()`, and for which `std::begin()` returns a random access iterator, including any in-house integer-indexed container types you already have that can easily provide `std::size()` and `std::ssize()` if they don't already. -- `hello` uses **string interpolation** to be able to write `#!cpp "Hello, (msg)$!\n"` instead of `#!cpp "Hello, " << msg << "!\n"`. +- `hello` uses **string interpolation** to be able to write `#!cpp "Hello, (msg)$!\n"` instead of `#!cpp "Hello, " << msg << "!\n"`. String interpolation also supports [standard C++ format specifications](https://en.cppreference.com/w/cpp/utility/format/spec), so you won't need iostream manipulators. **Simplicity through generality + defaults.** A major way that Cpp2 delivers simplicity is by providing just one powerful general syntax for a given thing (e.g., one function definition syntax), but designing it so you can omit the parts you're not currently using (e.g., where you're happy with the defaults). We're already using some of those defaults above: @@ -67,7 +65,7 @@ For details, see [Design note: Defaults are one way to say the same thing](https **C++ standard library is always available.** We didn't need `#!cpp #include ` or `#!cpp import std;`. The full C++ standard library is always available by default if your source file contains only syntax-2 code and you compile using cppfront's `-p` (short for `-pure-cpp2`), or if you use `-im` (short for `-import-std`). Cppfront is regularly updated to be compatible with C++23 and the latest draft C++26 library additions as soon as the ISO C++ committee votes them into the C++26 working draft, so as soon as you have a C++ implementation that has a new standard (or bleeding-edge draft standard!) C++ library feature, you'll be able to fully use it in Cpp2 code. -## Building `hello.cpp2` +## Building `hello.cpp2` Now use `cppfront` to compile `hello.cpp2` to a standard C++ file `hello.cpp`: @@ -125,7 +123,7 @@ Here we can see more of how Cpp2 makes it features work. - **Lines 1 and 3: `std::` is available** because cppfront was invoked with `-p`, which implies either `-im` (short for `-import-std`) or `-in` (short for `-include-std`, for compilers that don't support modules yet). The generated code tells `cpp2util.h` to `#!cpp import` the entire standard library as a module (or do the equivalent via headers if modules are not available). -## Building and running `hello.cpp` with any recent C++ compiler +## Building and running `hello.cpp` with any recent C++ compiler Finally, just build `hello.cpp` using your favorite C++20 compiler, where `CPPFRONT_INCLUDE` is the path to `/cppfront/include`: diff --git a/docs/welcome/integration.md b/docs/welcome/integration.md index df3279f3d9..f88015acf5 100644 --- a/docs/welcome/integration.md +++ b/docs/welcome/integration.md @@ -10,7 +10,7 @@ That's it... The result Just Works with every C++20 or higher compiler and all e The following uses Visual Studio as an example, but others have done the same in Xcode, Qt Creator, CMake, and other IDEs. -## 1. Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode +## 1. Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode For Visual Studio: In the Solution Explorer, right-click on Source Files and pick Add to add the file to the project. @@ -21,7 +21,7 @@ Also in Solution Explorer, right-click on the `.cpp` file Properties and make su

-## 2. Tell the project system to build that file using a custom build tool to invoke cppfront, and add `cppfront/include` to the include path +## 2. Tell the project system to build that file using a custom build tool to invoke cppfront, and add `cppfront/include` to the include path For Visual Studio: In Solution Explorer, right-click on the `.cpp2` file and select Properties, and add the custom build tool. Remember to also tell it that the custom build tool produces the `.cpp` file, so that it knows about the build dependency: @@ -31,7 +31,8 @@ Finally, put the `/cppfront/include` directory on your `INCLUDE` path. In Soluti

-## That's it: Error message outputs, debuggers, visualizers, and other tools should just work + +## That's it: Error message outputs, debuggers, visualizers, and other tools should just work That's enough to enable builds, and the IDE just picks up the rest from the `.cpp` file that cppfront generated: From f12047a1d7ce1c65898306de643eb0c6f0da00ed Mon Sep 17 00:00:00 2001 From: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Date: Sun, 25 Feb 2024 08:17:23 +1000 Subject: [PATCH 47/64] Update declarations.md (#988) Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> --- docs/cpp2/declarations.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index 3b749dee67..f04a28a000 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -14,6 +14,7 @@ All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. > Note: When the type is omitted, whitespace does not matter, and writing `#!cpp x: = 0;` or `#!cpp x : = 0;` or `#!cpp x := 0;` or other whitespace is just a stylistic choice. This documentation's style uses the last one, except when there are multiple adjacent declaration lines this style lines up their `:` and `=`. +> Note: The only variation to the above is that 'constexpr' functions are written with `==` instead of `=` (e.g., `#!cpp square: (i: int) == i * i;`). ## Examples @@ -58,6 +59,9 @@ n: namespace // color is an @enum type (see Note) color: @enum type = { red; green; blue; } + + // a constexpr function is defined with `==` + calc_next_year: (year: i32) -> i32 == { return year + 1; } } ``` From 8d19818ce7a601602c919ed8ec0999ffe98de7b2 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sat, 24 Feb 2024 16:23:52 -0800 Subject: [PATCH 48/64] Follow up merge with additional examples, and update highlighted linenos --- docs/cpp2/declarations.md | 42 +++++++++++++++++++++++++-------------- docs/cpp2/types.md | 2 +- 2 files changed, 28 insertions(+), 16 deletions(-) diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index f04a28a000..fffc1b6247 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -6,35 +6,44 @@ All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. - The `:` is pronounced **"is a."** -- The `=` is pronounced **"defined as."** +- The `=` is pronounced **"defined as."** For the definition of something that will always have the same value, write `==`. - The _statement_ is typically an expression statement (e.g., `#!cpp a + b();`) or a compound statement (e.g., `#!cpp { /*...*/ return c(d) / e; }`). - Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted entirely to accept a default (e.g., `#!cpp x: int = 0;` can be equivalently written `#!cpp x: _ = 0;` or `#!cpp x := 0;` both of which deduce the type). -> Note: When the type is omitted, whitespace does not matter, and writing `#!cpp x: = 0;` or `#!cpp x : = 0;` or `#!cpp x := 0;` or other whitespace is just a stylistic choice. This documentation's style uses the last one, except when there are multiple adjacent declaration lines this style lines up their `:` and `=`. - -> Note: The only variation to the above is that 'constexpr' functions are written with `==` instead of `=` (e.g., `#!cpp square: (i: int) == i * i;`). +> Notes: +> +> - When the type is omitted, whitespace does not matter, and writing `#!cpp x: = 0;` or `#!cpp x : = 0;` or `#!cpp x := 0;` or other whitespace is just a stylistic choice. This documentation's style uses the last one, except when there are multiple adjacent declaration lines this style lines up their `:` and `=`. +> +> - `==` stresses that this name will always have the given value, to express [aliases](./aliases.md) and side-effect-free 'constexpr' functions (e.g., `#!cpp square: (i: int) == i * i;`). ## Examples -``` cpp title="Consistent declarations — name : kind = statement" hl_lines="2 5 10 17 21 25 34 40" +``` cpp title="Consistent declarations — name : kind = statement" linenums="1" hl_lines="2 6 10 15 24 28 32 43 49 53" // n is a namespace defined as the following scope n: namespace = { - // shape is a type defined as the following scope - shape: type + // shape is a templated type with one type parameter T + // (equivalent to '') defined as the following scope + shape: type = { - // points is an object of type std::vector, + // point is a type defined as being always the same as + // (i.e., an alias for) T + point_type: type == T; + + // points is an object of type std::vector, // defined as having an empty default value // (type-scope objects are private by default) - points: std::vector = (); + points: std::vector = (); // draw is a function taking 'this' and 'canvas' parameters // and returning bool, defined as the following body // (type-scope functions are public by default) - // - this is as if 'this: shape', an object of type shape - // - where is an object of type canvas + // + // this is an object of type shape (as if written 'this: shape') + // + // where is an object of type canvas draw: (this, where: canvas) -> bool = { // pen is an object of deduced (omitted) type 'color', @@ -52,20 +61,23 @@ n: namespace // count is a function taking 'this' and returning a type // deduced from its body, defined as a single-expression body + // (equivalent to '= { return points.ssize(); }' but omitting + // syntax where we're using the language defaults) count: (this) = points.ssize(); // ... } - // color is an @enum type (see Note) + // color is an @enum type (see Note) defined as having these enumerators color: @enum type = { red; green; blue; } - // a constexpr function is defined with `==` - calc_next_year: (year: i32) -> i32 == { return year + 1; } + // calc_next_year is a function defined as always returning the same + // value for the same input (i.e., 'constexpr', side effect-free) + calc_next_year: (year: i32) -> i32 == year + 1; } ``` -> Note: `@enum` is a metafunction, which provides an easy way to opt into a group of defaults, constraints, and generated functions. For details, see [`@enum`, `@flag_enum`](metafunctions.md#enum-flag_enum) +> Note: `@enum` is a metafunction, which provides an easy way to opt into a group of defaults, constraints, and generated functions. For details, see [`@enum`](metafunctions.md#enum). ## `requires` constraints diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index 88b431572f..5daa95de8d 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -251,7 +251,7 @@ For example: ``` cpp title="Writing the <=> operator" hl_lines="5-7 13" item: type = { - x: i32 = 0; + x: i32 = (); y: std::string = (); operator<=>: (this, that) -> std::strong_ordering; From 8d49525b27d922f4a2e28aaa55b190c9b4a4d07e Mon Sep 17 00:00:00 2001 From: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Date: Sun, 25 Feb 2024 10:33:16 +1000 Subject: [PATCH 49/64] Update Capture sections in expressions.md (#987) * Update expressions.md Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> * Update highlighted linenos And a couple of other fixes, including that I meant to write "ish" not "sh" (fixing my own typo!) --------- Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Signed-off-by: Herb Sutter Co-authored-by: Herb Sutter --- docs/cpp2/expressions.md | 43 ++++++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 8 deletions(-) diff --git a/docs/cpp2/expressions.md b/docs/cpp2/expressions.md index 79cdcf2756..3be6718f11 100644 --- a/docs/cpp2/expressions.md +++ b/docs/cpp2/expressions.md @@ -212,11 +212,13 @@ For more examples, see also the examples in the previous two sections on `is` an ## `$` — captures, including interpolations -Suffix `$` is pronounced **"paste the value"** and captures the value of an expression at the point when the expression where the capture is written is evaluated. Depending the complexity of the capture expression `expr$` and where it is used, parentheses `(expr)$` may be required for precedence or to show the boundaries of the expression. +Suffix `$` is pronounced **"paste the value of"** and captures the value of an expression at the point when the expression where the capture is written is evaluated. Depending on the complexity of the capture expression `expr$` and where it is used, parentheses `(expr)$` may be required for precedence or to show the boundaries of the expression. `x$` always captures `x` by value. To capture by reference, take the address and capture a pointer using `x&$`. If the value is immediately used, dereference again; for example `:(val) total&$* += val` adds to the `total` local variable itself, not a copy. -Any capture is evaluated at the point where it is written, in the function expression, contract postcondition, or string literal. The stored captured value can then be used later when the context it is in is evaluated, such as when the function expression body it's in is actually called later (one or more times), when the postcondition it's in is evaluated later when the function returns, or when the string literal it's in is read later. +Captures are evaluated at the point where they are written in function expressions, contract postconditions, and string literals. The stored captured value can then be used later when evaluating its context, such as when the function expression body containing the captured value is actually called later (one or more times), when the postcondition containing the captured value is evaluated later when the function returns, or when the string literal containing the captured value is read later. + +The design and syntax are selected so that capture is spelled the same way in all contexts. For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). ### Capture in function expressions (aka lambdas) @@ -225,19 +227,44 @@ Any capture in a function expression body is evaluated at the point where the fu For example: -``` cpp title="Capture in an unnamed function expression (aka lambda)" hl_lines="6" +``` cpp title="Capture in an unnamed function expression (aka lambda)" hl_lines="7 8 13-18" main: () = { - s := "-sh\n"; + s := "-ish\n"; vec: std::vector = (1, 2, 3, 5, 8, 13 ); - vec.std::ranges::for_each( - :(i) = { std::cout << i << s$; } - // Paste the value of `s` + std::ranges::for_each( + vec, + :(i) = std::cout << i << s$ + // Function capture: Paste the value of 's' ); } + +// prints: +// 1-ish +// 2-ish +// 3-ish +// 5-ish +// 8-ish +// 13-ish ``` -The design and syntax are selected so that capture is spelled the same way in all contexts. For details, see [Design note: Capture](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Capture). +Another example: + +``` cpp title="Capture in a named function expression (aka lambda)" hl_lines="2 4 7 12 13" +main: () = { + price := 100; + func := :() = { + std::cout << "Price = " << price$ << "\n"; + }; + func(); + price = 200; + func(); +} + +// prints: +// Price = 100 +// Price = 100 +``` ### Capture in contract postconditions From f0addd20f77e9353fa765f5a4feeb855264c5ecb Mon Sep 17 00:00:00 2001 From: gregmarr Date: Sun, 25 Feb 2024 15:29:15 -0500 Subject: [PATCH 50/64] More docs reviews (#990) --- docs/cpp2/common.md | 2 +- docs/index.md | 2 +- docs/welcome/hello-world.md | 8 ++++---- docs/welcome/integration.md | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/cpp2/common.md b/docs/cpp2/common.md index 41e848490b..2801f34cbf 100644 --- a/docs/cpp2/common.md +++ b/docs/cpp2/common.md @@ -92,7 +92,7 @@ Cpp2 supports the same fundamental types as today's Cpp1, but additionally provi | `ulonglong` | `#!cpp unsigned long long` | | `longdouble` | `#!cpp long double` | -| For compatibility/interop only,
so deliberately ugly names | Synonym for | Notes | +| For compatibility/interop only,
so deliberately ugly names | Synonym for (these multi-word
names are not allowed in Cpp2) | Notes | |---|---|---| | `_schar` | `#!cpp signed char` | Normally, prefer `i8` instead | | `_uchar` | `#!cpp unsigned char` | Normally, prefer `u8` instead | diff --git a/docs/index.md b/docs/index.md index 0df8e07143..b6b81c5c04 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,7 +9,7 @@ main: () = { ## What is Cpp2? -"Cpp2," short for "C++ syntax 2," is my ([Herb Sutter's](https://github.com/hsutter)) personal project to try to make writing ordinary C++ types/functions/objects be much **simpler and safer**, without breaking backward compatibility. Bjarne Stroustrup said it best: +"Cpp2", short for "C++ syntax 2", is my ([Herb Sutter's](https://github.com/hsutter)) personal project to try to make writing ordinary C++ types/functions/objects be much **simpler and safer**, without breaking backward compatibility. Bjarne Stroustrup said it best: > "Inside C++, there is a much smaller and cleaner language struggling to get out."
  — Bjarne Stroustrup, _The Design and Evolution of C++_ (D&E), 1994 > diff --git a/docs/welcome/hello-world.md b/docs/welcome/hello-world.md index e746612fa8..1e9ab67718 100644 --- a/docs/welcome/hello-world.md +++ b/docs/welcome/hello-world.md @@ -93,7 +93,7 @@ auto hello(cpp2::in msg) -> void { std::cout << ("Hello, " + cpp2::to_string(msg) + "!\n"); } ``` -Here we can see more of how Cpp2 makes it features work. +Here we can see more of how Cpp2 makes its features work. **How: Consistent context-free syntax.** @@ -108,15 +108,15 @@ Here we can see more of how Cpp2 makes it features work. **How: Simplicity through generality + defaults.** -- **Line 7: `in` parameters** are implemented using `#cpp2::in<>`, which is smart enough to pass by `#!cpp const` value when that's safe and appropriate, otherwise by `#!cpp const&`, so you don't have to choose the right one by hand. +- **Line 7: `in` parameters** are implemented using `#!cpp cpp2::in<>`, which is smart enough to pass by `#!cpp const` value when that's safe and appropriate, otherwise by `#!cpp const&`, so you don't have to choose the right one by hand. **How: Order-independent by default.** -- **Lines 5 and 7: Order independence** happens because cppfront generates all the type and function forward declarations for you, so you don't have to. That's why `main` can just call `hello`: Both are forward-declared, so they can both see each other. +- **Lines 5 and 7: Order independence** happens because cppfront generates all the type and function forward declarations for you, so you don't have to. That's why `main` can just call `hello`: both are forward-declared, so they can both see each other. **How: Seamless compatibility and interop.** -- **Lines 9-11 and 16: Ordinary direct calls** to existing C++ code, so there's never a need for wrapping/marshaling/thunking. +- **Lines 9-11 and 15: Ordinary direct calls** to existing C++ code, so there's never a need for wrapping/marshaling/thunking. **How: C++ standard library always available.** diff --git a/docs/welcome/integration.md b/docs/welcome/integration.md index f88015acf5..16c1151555 100644 --- a/docs/welcome/integration.md +++ b/docs/welcome/integration.md @@ -3,6 +3,7 @@ To start trying out Cpp2 syntax in any existing C++ project, just add a build step to translate the Cpp2 to Cpp1 syntax: +- Copy the `.cpp` file to the same name with a `.cpp2` extension. - Add the `.cpp2` file to the project, and ensure the `.cpp` is in C++20 mode. - Tell the IDE to build that file using a custom build tool to invoke cppfront. From ecb0031f654a7af52f364f458afd58e937e68493 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Mon, 26 Feb 2024 13:00:43 -0800 Subject: [PATCH 51/64] Add more functions material Return values Branches Loops Template parameters --- docs/cpp2/declarations.md | 19 +++++ docs/cpp2/functions.md | 156 ++++++++++++++++++++++++++++++++++++-- docs/cpp2/objects.md | 6 +- docs/cpp2/types.md | 2 +- docs/index.md | 2 +- 5 files changed, 176 insertions(+), 9 deletions(-) diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index fffc1b6247..3c72e2b715 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -6,6 +6,8 @@ All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. - The `:` is pronounced **"is a."** +- The _kind_ can start with [template parameters](#template-parameters). + - The `=` is pronounced **"defined as."** For the definition of something that will always have the same value, write `==`. - The _statement_ is typically an expression statement (e.g., `#!cpp a + b();`) or a compound statement (e.g., `#!cpp { /*...*/ return c(d) / e; }`). @@ -18,6 +20,23 @@ All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. > > - `==` stresses that this name will always have the given value, to express [aliases](./aliases.md) and side-effect-free 'constexpr' functions (e.g., `#!cpp square: (i: int) == i * i;`). + +## Template parameters + +A template parameter list is enclosed by `<` `>` angle brackets, and the parameters separated by commas. Each parameter is declared using the [same syntax as any type or object](declarations.md). If a parameter's **`:`** ***kind*** is not specified, the default is `: type`. + +For example: + +``` cpp title="Declaring template parameters" hl_lines="1-3" +array: type + // parameter T is a type + // parameter size is a 32-bit int += { + // ... +} +``` + + ## Examples ``` cpp title="Consistent declarations — name : kind = statement" linenums="1" hl_lines="2 6 10 15 24 28 32 43 49 53" diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index 227010b3f8..61beac6a19 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -3,11 +3,30 @@ ## Overview -TODO +A function is defined by writing a function signature after the `:` and a statement (expression or `{` `}` compound statement) after the `=`. After the optional [template parameters](declarations.md#template-parameters) available for all declarations, a function signatures consists of a possibly-empty [parameter list](#parameters), and an optional function [return values](#return-values). + +For example, the minimal function named `func` that takes no parameters and returns nothing (`#!cpp void`) is: + +``` cpp title="A minimal function" +func: ( /* no parameters */ ) = { /* empty body */ } +``` ## Parameters +The parameter list is enclosed by `(` `)` parentheses, and the parameters separated by commas. Each parameter is declared using the [same syntax as any object](declarations.md). For example: + +``` cpp title="Declaring parameters" hl_lines="2-4" +func: ( + x: i32, // parameter x is a 32-bit int + y: std::string, // parameter y is a std::string + z: std::map // parameter z is a std::map + ) += { + // ... +} +``` + There are six ways to pass parameters that cover all use cases: | Parameter ***kind*** | "Pass me an `x` I can ______" | Accepts arguments that are | Special semantics | ***kind*** `x: X` Compiles to Cpp1 as | @@ -25,7 +44,58 @@ There are six ways to pass parameters that cover all use cases: ## Return values -TODO +A function can return either of the following. The default is `#!cpp -> void`. + +(1) **`#!cpp -> X`** to return a single unnamed value of type `X`, which can be `#!cpp void` to signify the function has no return value. If `X` is not `#!cpp void`, the function body must have a `#!cpp return /*value*/;` statement that returns a value of type `X` on every path that exits the function. For example: + +``` cpp title="Functions with an unnamed return value" hl_lines="2 4 7 9 12 14" +// A function returning no value (void) +increment_in_place: (inout a: i32) -> void = { a++; } +// Or, using syntactic defaults, the following has identical meaning: +increment_in_place: (inout a: i32) = a++; + +// A function returning a single value of type i32 +add_one: (a: i32) -> i32 = { return a+1; } +// Or, using syntactic defaults, the following has identical meaning: +add_one: (a: i32) -> i32 = a+1; + +// A generic function returning a single value of deduced type +add: (a:T, b:U) -> decltype(a+b) = { return a+b; } +// Or, using syntactic defaults, the following has identical meaning: +add: (a, b) -> _ = a+b; +``` + +(2) **`#!cpp -> ( /* parameter list */ )`** to return a list of named return parameters using the same [parameters](#parameters) syntax, but where the only passing styles are `out` (the default, which moves where possible) or `forward`. The function body must [initialize](objects.md#init) the value of each return-parameter `ret` in its body the same way as any other local variable. An explicit return statement is written just `#!cpp return;` and returns the named values; the function has an implicit `#!cpp return;` at the end. For example: + +``` cpp title="Functions with multiple/named return values" hl_lines="7 9 10 22-24" +set: type = { + container: std::set; + iterator : type == std::set::iterator; + + // A std::set::insert-like function using named return values + // instead of just a std::pair/tuple + insert: (inout this, value: Key) -> (where: iterator, inserted: bool) = { + set_returned := container.insert(value); + where = set_returned.first; + inserted = set_returned.second; + } + + ssize: (this) -> i64 = std::ssize(container); + + // ... +} + +use_inserted_position: (_) = { } + +main: () = { + m: set = (); + ret := m.insert("xyzzy"); + if ret.inserted { + use_inserted_position( ret.where ); + } + assert( m.ssize() == 1 ); +} +``` ### Function outputs are not implicitly discardable @@ -34,7 +104,7 @@ A function's outputs are its return values, and the "out" state of any `out` and Function outputs cannot be silently discarded. To explicitly discard a function output, assign it to `_`. For example: -``` cpp title="No silent discard" hl_lines="10 11 22 27" +``` cpp title="No silent discard" hl_lines="9 11 13 17-18 23-24 29-30" f: () -> void = { } g: () -> int = { return 10; } h: (inout x: int) -> void = { x = 20; } @@ -44,7 +114,9 @@ main: () f(); // ok, no return value std::cout << g(); // ok, use return value + _ = g(); // ok, explicitly discard return value + g(); // ERROR, return value is ignored { @@ -78,15 +150,87 @@ main: () ## `#!cpp if`, `#!cpp else` — Branches -TODO +`if` and `else` are like always in C++, except that `(` `)` parentheses around the condition are not required. Instead, `{` `}` braces around a branch body *are* required. For example: + +``` cpp title="Using if and else" hl_lines="1 4" +if vec.ssize() > 100 { + do_general_algorithm( container ); +} +else { + do_linear_scan( vec ); +} +``` + ## `#!cpp for`, `#!cpp while`, `#!cpp do` — Loops -TODO +**`#!cpp do`** and **`#!cpp while`** are like always in C++, except that `(` `)` parentheses around the condition are not required. Instead, `{` `}` braces around the loop body *are* required. + +**`#!cpp for range do (e)`** ***statement*** says "for each element in `range`, call it `e` and perform the statement." The loop parameter `(e)` is an ordinary parameter that can be passed isoing any [parameter passing style](#parameters); as always, the default is `in`, which is read-only and expresses a read-only loop. The statement is not required to be enclosed in braces. + +Every loop can have a `next` clause, that is performed at the end of each loop body execution. This makes it easy to have a counter for any loop, including a range `#!cpp for` loop. + +> Note: Whitespace is just a stylistic choice. This documentation's style generally puts each keyword on its own line and lines up what follows. + +For example: + +``` cpp title="Using loops" hl_lines="4 5 13 16 17 22-24" +words: std::vector = ("Adam", "Betty"); +i := 0; + +while i < words.ssize() // while this condition is true +next i++ // and increment i after each loop body is run +{ // do this loop body + std::cout << "word: (words[i])$\n"; +} +// prints: +// word: Adam +// word: Betty + +do { // do this loop body + std::cout << "**\n"; +} +next i-- // and decrement i after each loop body is run +while i > 0; // while this condition is true +// prints: +// ** +// ** + +for words // for each element in 'words' +next i++ // and increment i after each loop body is run +do (inout word) // declare via 'inout' the loop can change the contents +{ // do this loop body + word = "[" + word + "]"; + std::cout << "counter: (i)$, word: (word)$\n"; +} +// prints: +// counter: 0, word: [Adam] +// counter: 1, word: [Betty] +``` + +There is no special "select" or "where" to perform the loop body for only a subset of matches, because this can naturally be expressed with `if`. For example: + +``` cpp title="Using loops + if" hl_lines="7" +// Continuing the previous example +i = 0; + +for words +next i++ +do (word) +if i % 2 == 1 // if i is odd +{ // do this loop body + std::cout << "counter: (i)$, word: (word)$\n"; +} +// prints: +// counter: 1, word: [Betty] +``` + + +### Loop names, `#!cpp break`, and `#!cpp continue` Loops can be named using the usual **name `:`** syntax that introduces all names, and `#!cpp break` and `#!cpp continue` can refer to those names. For example: -``` cpp title="Using named break and continue" hl_lines="6 10" +``` cpp title="Using named break and continue" hl_lines="1 3 6 10" outer: while i, // defined as having the initial contents 1, 2, 3 numbers: std::vector = (1, 2, 3); @@ -23,6 +23,10 @@ numbers: std::vector = (1, 2, 3); // same, deducing the vector's type count: int = -1; count: _ = -1; // same, deducing the object's type with the _ wildcard count := -1; // same, deducing the object's type by just omitting it + +// pi is a variable template; == signifies the value never changes (constexpr) +pi: T == 3.14159'26535'89793'23846L; +pi: _ == 3.14159'26535'89793'23846L; // same, deducing the object's type ``` diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index 5daa95de8d..fb611ec244 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -268,7 +268,7 @@ test: (x: item, y: item) = { } ``` -The above is the same as in Cpp1 because most of Cpp2's `#!cpp operator<=>` feature has already been merged into ISO C++ (Cpp1). In additiona, in Cpp2 comparisons with the same precedence can be safely chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: +The above is the same as in Cpp1 because most of Cpp2's `#!cpp operator<=>` feature has already been merged into ISO C++ (Cpp1). In addition, in Cpp2 comparisons with the same precedence can be safely chained, and always have the mathematically sound transitive meaning or else are rejected at compile time: - **Valid chains: All `<`/`<=`, all `>`/`>=`, or all `==`.** All mathematically sound and safe chains like `a <= b < c` are supported, with efficient single evaluation of each term. They are "sound" because they are transitive; these chains imply a relationship between `a` and `c` (in this case, the chain implies that `a <= c` is also true). diff --git a/docs/index.md b/docs/index.md index b6b81c5c04..0df8e07143 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,7 +9,7 @@ main: () = { ## What is Cpp2? -"Cpp2", short for "C++ syntax 2", is my ([Herb Sutter's](https://github.com/hsutter)) personal project to try to make writing ordinary C++ types/functions/objects be much **simpler and safer**, without breaking backward compatibility. Bjarne Stroustrup said it best: +"Cpp2," short for "C++ syntax 2," is my ([Herb Sutter's](https://github.com/hsutter)) personal project to try to make writing ordinary C++ types/functions/objects be much **simpler and safer**, without breaking backward compatibility. Bjarne Stroustrup said it best: > "Inside C++, there is a much smaller and cleaner language struggling to get out."
  — Bjarne Stroustrup, _The Design and Evolution of C++_ (D&E), 1994 > From 270fe8f211e35d8cfddd3f72122d432d9cc98c39 Mon Sep 17 00:00:00 2001 From: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Date: Tue, 27 Feb 2024 12:14:57 +1000 Subject: [PATCH 52/64] Changes to functions.md (#998) * Update functions.md Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> * Tweak comments for divide example --------- Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Co-authored-by: Herb Sutter --- docs/cpp2/functions.md | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index 61beac6a19..6b1b6090b1 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -3,7 +3,7 @@ ## Overview -A function is defined by writing a function signature after the `:` and a statement (expression or `{` `}` compound statement) after the `=`. After the optional [template parameters](declarations.md#template-parameters) available for all declarations, a function signatures consists of a possibly-empty [parameter list](#parameters), and an optional function [return values](#return-values). +A function is defined by writing a function signature after the `:` and a statement (expression or `{` `}` compound statement) after the `=`. After the optional [template parameters](declarations.md#template-parameters) available for all declarations, a function signature consists of a possibly-empty [parameter list](#parameters), and one or more optional [return values](#return-values). For example, the minimal function named `func` that takes no parameters and returns nothing (`#!cpp void`) is: @@ -14,7 +14,7 @@ func: ( /* no parameters */ ) = { /* empty body */ } ## Parameters -The parameter list is enclosed by `(` `)` parentheses, and the parameters separated by commas. Each parameter is declared using the [same syntax as any object](declarations.md). For example: +The parameter list is enclosed by `(` `)` parentheses and the parameters are separated by commas. Each parameter is declared using the [same unified syntax](declarations.md) as used for all declarations. For example: ``` cpp title="Declaring parameters" hl_lines="2-4" func: ( @@ -67,7 +67,29 @@ add: (a, b) -> _ = a+b; (2) **`#!cpp -> ( /* parameter list */ )`** to return a list of named return parameters using the same [parameters](#parameters) syntax, but where the only passing styles are `out` (the default, which moves where possible) or `forward`. The function body must [initialize](objects.md#init) the value of each return-parameter `ret` in its body the same way as any other local variable. An explicit return statement is written just `#!cpp return;` and returns the named values; the function has an implicit `#!cpp return;` at the end. For example: -``` cpp title="Functions with multiple/named return values" hl_lines="7 9 10 22-24" +``` cpp title="Function with multiple/named return values" hl_lines="1 3-4 7-8 14 16-17" +divide: (dividend: int, divisor: int) -> (quotient: int, remainder: int) = { + if divisor == 0 { + quotient = 0; // constructs quotient + remainder = 0; // constructs remainder + } + else { + quotient = dividend / divisor; // constructs quotient + remainder = dividend % divisor; // constructs remainder + } +} + +main: () = { + div := divide(11, 5); + std::cout << "(div.quotient)$, (div.remainder)$\n"; +} +// Prints: +// 2, 1 +``` + +This next example declares a [member function](types.md#this-parameter) with multiple return values in a [type](types.md) named `set`: + +``` cpp title="Member function with multiple/named return values" hl_lines="7 9 10 22-24" set: type = { container: std::set; iterator : type == std::set::iterator; From b795f3107da77883615abdc210eab607b98bc006 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Tue, 27 Feb 2024 14:51:56 -0800 Subject: [PATCH 53/64] Fill in some TODO's --- docs/cpp2/metafunctions.md | 2 +- docs/cpp2/types.md | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/cpp2/metafunctions.md b/docs/cpp2/metafunctions.md index b40a9e259d..c29abc5c9d 100644 --- a/docs/cpp2/metafunctions.md +++ b/docs/cpp2/metafunctions.md @@ -139,7 +139,7 @@ file_attributes: @flag_enum type = { `@union` declaratively opts into writing a safe discriminated union/variant dynamic type. For example: -``` cpp title="Using the @union metafunction when writing a type" hl_lines="9" +``` cpp title="Using the @union metafunction when writing a type" hl_lines="10 18-20 25 26" // 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 diff --git a/docs/cpp2/types.md b/docs/cpp2/types.md index fb611ec244..39cfd1dfae 100644 --- a/docs/cpp2/types.md +++ b/docs/cpp2/types.md @@ -120,7 +120,7 @@ All value operations are spelled `#!cpp operator=`, including construction, assi Unifying `#!cpp operator=` enables usable `out` parameters, which is essential for composable guaranteed initialization. We want the expression syntax `#!cpp x = value` to be able to call a constructor or an assignment operator, so naming them both `#!cpp operator=` is consistent. -TODO Return type of assignment operator? +An assignment operator always returns the same type as `#!cpp this` and automatically performs `#!cpp return this;`. > Note: Writing `=` always invokes an `#!cpp operator=` (in fact for a Cpp2-authored type, and semantically for a Cpp1-authored type). This avoids the Cpp1 inconsistency that "writing `=` calls `#!cpp operator=`, except when it doesn't" (such as in a Cpp1 variable initialization). Conversely, `#!cpp operator=` is always invoked by `=` in Cpp2. @@ -188,7 +188,7 @@ In a hand-written `#!cpp operator=`: - If the body does not mention a member in the appropriate place in the beginning section, by default the member's default initializer is used. -- In an assignment operator (`#!cpp inout this`), you can explicitly skip setting a member by writing `member = _;` where it would normally be set if you know you have a reason to set its value later instead or if the existing value needs to be preserved. +- In an assignment operator (`#!cpp inout this`), you can explicitly skip setting a member by writing `member = _;` where it would normally be set if you know you have a reason to set its value later instead or if the existing value needs to be preserved. (This is rare; for an example, see the generated implementation of the [`union` metafunction](metafunctions.md#union).) For example: @@ -236,8 +236,6 @@ main: () = { > Note: This makes memberwise semantics symmetric for construction and assignment. In Cpp1, only non-copy/move constructors have a default, which is to initialize a member with its default initializer. In Cpp2, both constructors and assignment operators default to using the default initializer if it's a conversion function (non-`that`, aka non-copy/move), and using memberwise `member = that.member;` for copy/move functions. -TODO: include an example of using `member = _;` - ## `#!cpp operator<=>` — Unified comparisons From 74fb7162064bb6c7d017088b8fd3f2cf155d9516 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Wed, 28 Feb 2024 10:34:41 -0800 Subject: [PATCH 54/64] Add requires, namespaces, using, and namespace/type/function/object aliases --- docs/cpp2/aliases.md | 86 +++++++++++++++++++++++++++++++++++++-- docs/cpp2/declarations.md | 40 +++++++++++++----- docs/cpp2/namespaces.md | 55 +++++++++++++++++++++++-- 3 files changed, 162 insertions(+), 19 deletions(-) diff --git a/docs/cpp2/aliases.md b/docs/cpp2/aliases.md index 30c53a603c..17e8104ef6 100644 --- a/docs/cpp2/aliases.md +++ b/docs/cpp2/aliases.md @@ -1,19 +1,97 @@ # Aliases +Aliases are pronounced **"synonym for**, and written using the same **name `:` kind `=` value** [declaration syntax](../cpp2/declarations.md) as everything in Cpp2: + +- **name** is declared to be a synonym for **value**. + +- **kind** can be any of the kinds: `namespace`, `type`, a function signature, or a type. + +- **`==`**, pronounced **"defined as a synonym for"**, always precedes the value. The `==` syntax stresses that during compilation every use of the name could be equivalently replaced with the value. + +- **value** is the expression that the **name** is a synonym for. + + ## Namespace aliases -TODO +A namespace alias is written the same way as a [namespace](namespaces.md), but using `==` and with the name of another namespace as its value. For example: + +``` cpp title="Namespace aliases" hl_lines="1 2 4 5 8 12 16" +// 'chr' is a namespace defined as a synonym for 'std::chrono' +chr : namespace == std::chrono; + +// 'chrlit' is a namespace defined as a synonym for 'std::chrono_literals' +chrlit : namespace == std::chrono_literals; + +main: () = { + using namespace chrlit; + + // The next two lines are equivalent + std::cout << "1s is (std::chrono::nanoseconds(1s).count())$ns\n"; + std::cout << "1s is (chr::nanoseconds(1s).count())$ns\n"; +} +// Prints: +// 1s is 1000000000ns +// 1s is 1000000000ns +``` + ## Type aliases -TODO +A namespace alias is written the same way as a [type](types.md), but using `==` and with the name of another type as its value. For example: + +``` cpp title="Type aliases" hl_lines="1 2 7 10" +// 'imap' is a type defined as a synonym for 'std::map' +imap : type == std::map; + +main: () = { + // The next two lines declare two objects with identical type + map1: std::map = (); + map2: imap = (); + + // Assertion they are the same type, using the same_as concept + assert( std::same_as< decltype(map1), decltype(map2) > ); +} +``` + ## Function aliases -TODO +A function alias is written the same way as a [function](functions.md), but using `==` and with a side-effect-free body as its value; the body must always return the same value for the same input arguments. For example: + +``` cpp title="Function aliases" hl_lines="1 2 6 9 12 15" +// 'square' is a function defined as a synonym for the value of 'i * i' +square: (i: i32) -> _ == i * i; + +main: () = { + // It can be used at compile time, with compile time values + ints: std::array = (); + + // Assertion that the size is the square of 4 + assert( ints.size() == 16 ); + + // And if can be used at run time, with run time values + std::cout << "the square of 4 is (square(4))$\n"; +} +// Prints: +// the square of 4 is 16 +``` + +> Note: A function alias is compiled to a Cpp1 `#!cpp constexpr` function. + ## Object aliases -TODO +An object alias is written the same way as an [object](objects.md), but using `==` and with a side-effect-free value. For example: + +``` cpp title="Function aliases" hl_lines="1 2 5 6" +// 'BufferSize' is an object defined as a synonym for the value 1'000'000 +BufferSize: i32 == 1'000'000; + +main: () = { + buf: std::array = (); + assert( buf.size() == BufferSize ); +} +``` +> Note: An object alias is compiled to a Cpp1 `#!cpp constexpr` object. diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index 3c72e2b715..9099108465 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -4,21 +4,23 @@ All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. +- The _name_ must be a valid identifier (start with a letter, and consist of letters, digits, or `_`). The name can be variadic (be a name for a list of zero or more things) by writing a `...` suffix at the end of the name. + - The `:` is pronounced **"is a."** -- The _kind_ can start with [template parameters](#template-parameters). +- The _kind_ can start with [template parameters](#template-parameters) and end with [`#!cpp requires` constraints](#requires). -- The `=` is pronounced **"defined as."** For the definition of something that will always have the same value, write `==`. +- The `=` is pronounced **"defined as."** For the definition of something that will always have the same value, write `==`, pronounced **"defined as a synonym for"**. - The _statement_ is typically an expression statement (e.g., `#!cpp a + b();`) or a compound statement (e.g., `#!cpp { /*...*/ return c(d) / e; }`). -- Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted entirely to accept a default (e.g., `#!cpp x: int = 0;` can be equivalently written `#!cpp x: _ = 0;` or `#!cpp x := 0;` both of which deduce the type). +Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted entirely to accept a default (e.g., `#!cpp x: int = 0;` can be equivalently written `#!cpp x: _ = 0;` or `#!cpp x := 0;` both of which deduce the type). > Notes: > > - When the type is omitted, whitespace does not matter, and writing `#!cpp x: = 0;` or `#!cpp x : = 0;` or `#!cpp x := 0;` or other whitespace is just a stylistic choice. This documentation's style uses the last one, except when there are multiple adjacent declaration lines this style lines up their `:` and `=`. > -> - `==` stresses that this name will always have the given value, to express [aliases](./aliases.md) and side-effect-free 'constexpr' functions (e.g., `#!cpp square: (i: int) == i * i;`). +> - `==` stresses that this name will always have the given value, to express [aliases](aliases.md) and side-effect-free 'constexpr' [function aliases](aliases.md/#function-aliases). ## Template parameters @@ -27,13 +29,35 @@ A template parameter list is enclosed by `<` `>` angle brackets, and the paramet For example: -``` cpp title="Declaring template parameters" hl_lines="1-3" +``` cpp title="Declaring template parameters" hl_lines="1-3 8-9" array: type // parameter T is a type // parameter size is a 32-bit int = { // ... } + +tuple: type + // parameter Ts is variadic list of zero or more types += { + // ... +} +``` + + +## `#!cpp requires` constraints + +A `#!cpp requires` ***condition*** constraint appears at the end of the ***kind*** of a templated declaration. If the condition evaluates to `#!cpp false`, that specialization of the template is ignored as if not declared. + +For example: + +``` cpp title="A requires constraint on a variadic function" hl_lines="3" +print: + (inout out: std::ostream, args...: Args) + requires sizeof...(Args) >= 1u += { + (out << ... << args); +} ``` @@ -97,9 +121,3 @@ n: namespace ``` > Note: `@enum` is a metafunction, which provides an easy way to opt into a group of defaults, constraints, and generated functions. For details, see [`@enum`](metafunctions.md#enum). - - -## `requires` constraints - -TODO - diff --git a/docs/cpp2/namespaces.md b/docs/cpp2/namespaces.md index 8058cacc81..43d9e75462 100644 --- a/docs/cpp2/namespaces.md +++ b/docs/cpp2/namespaces.md @@ -3,11 +3,58 @@ ## Overview -TODO +A namespace `N` can contain declarations that are then accessed by writing `N::` or [`using`](#using) the namespace or declaration. For example: -For details, see [Design note: Namespaces](https://github.com/hsutter/cppfront/wiki/Design-note%3A-Namespaces). +``` cpp title="Declaring some things in a namespace" hl_lines="2 8" +// A namespace to put all the names provided by a widget library +widgetlib: namespace = { + widget: type = { /*...*/ } + // ... more things ... +} +main: () = { + w: widgetlib::widget = /*...*/; +} +``` + + +## `using` + +A `#!cpp using` statement brings names declared in another namespace into the current scope as if they had been declared in the current scope. It has two forms: + +- `#!cpp using a_namespace::a_name;` brings the single name `a_name` into scope. + +- `#!cpp using namespace a_namespace;` brings all the namespace's names into scope. + +For example: + +``` cpp title="using statements" hl_lines="13 14 20 21" +// A namespace to put all the names provided by a widget library +widgetlib: namespace = { + widget: type = { /*...*/ } + // ... more things ... +} + +main: () = { + // Explicit name qualification + w: widgetlib::widget = /*...*/; + + { + // Using the name, no qualification needed + using widgetlib::widget; + w2: widget = /*...*/; + // ... + } + + { + // Using the whole namespace, no qualification needed + using namespace widgetlib; + w3: widget = /*...*/; + // ... + } + + // ... +} +``` -## `using` -TODO From aa9c168b4f7a1181bd7f2b54d011ac8dfa1087dc Mon Sep 17 00:00:00 2001 From: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Date: Thu, 29 Feb 2024 12:08:14 +1000 Subject: [PATCH 55/64] Update aliases.md (#1005) Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> --- docs/cpp2/aliases.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/cpp2/aliases.md b/docs/cpp2/aliases.md index 17e8104ef6..501c02882a 100644 --- a/docs/cpp2/aliases.md +++ b/docs/cpp2/aliases.md @@ -1,6 +1,6 @@ # Aliases -Aliases are pronounced **"synonym for**, and written using the same **name `:` kind `=` value** [declaration syntax](../cpp2/declarations.md) as everything in Cpp2: +Aliases are pronounced **"synonym for"**, and written using the same **name `:` kind `=` value** [declaration syntax](../cpp2/declarations.md) as everything in Cpp2: - **name** is declared to be a synonym for **value**. @@ -37,7 +37,7 @@ main: () = { ## Type aliases -A namespace alias is written the same way as a [type](types.md), but using `==` and with the name of another type as its value. For example: +A type alias is written the same way as a [type](types.md), but using `==` and with the name of another type as its value. For example: ``` cpp title="Type aliases" hl_lines="1 2 7 10" // 'imap' is a type defined as a synonym for 'std::map' @@ -49,7 +49,7 @@ main: () = { map2: imap = (); // Assertion they are the same type, using the same_as concept - assert( std::same_as< decltype(map1), decltype(map2) > ); + static_assert( std::same_as< decltype(map1), decltype(map2) > ); } ``` @@ -67,7 +67,7 @@ main: () = { ints: std::array = (); // Assertion that the size is the square of 4 - assert( ints.size() == 16 ); + static_assert( ints.size() == 16 ); // And if can be used at run time, with run time values std::cout << "the square of 4 is (square(4))$\n"; @@ -89,7 +89,7 @@ BufferSize: i32 == 1'000'000; main: () = { buf: std::array = (); - assert( buf.size() == BufferSize ); + static_assert( buf.size() == BufferSize ); } ``` From 90f1c83f2bb9ded1612c15be2a7400503409ba1a Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Thu, 7 Mar 2024 14:52:57 -0800 Subject: [PATCH 56/64] Add generality notes: Function defaults, and function <-> block/stmt unification --- docs/cpp2/aliases.md | 97 --------------------- docs/cpp2/declarations.md | 171 ++++++++++++++++++++++++++++++++++++-- docs/cpp2/functions.md | 115 +++++++++++++++++++++++-- mkdocs.yml | 3 +- 4 files changed, 275 insertions(+), 111 deletions(-) delete mode 100644 docs/cpp2/aliases.md diff --git a/docs/cpp2/aliases.md b/docs/cpp2/aliases.md deleted file mode 100644 index 17e8104ef6..0000000000 --- a/docs/cpp2/aliases.md +++ /dev/null @@ -1,97 +0,0 @@ -# Aliases - -Aliases are pronounced **"synonym for**, and written using the same **name `:` kind `=` value** [declaration syntax](../cpp2/declarations.md) as everything in Cpp2: - -- **name** is declared to be a synonym for **value**. - -- **kind** can be any of the kinds: `namespace`, `type`, a function signature, or a type. - -- **`==`**, pronounced **"defined as a synonym for"**, always precedes the value. The `==` syntax stresses that during compilation every use of the name could be equivalently replaced with the value. - -- **value** is the expression that the **name** is a synonym for. - - -## Namespace aliases - -A namespace alias is written the same way as a [namespace](namespaces.md), but using `==` and with the name of another namespace as its value. For example: - -``` cpp title="Namespace aliases" hl_lines="1 2 4 5 8 12 16" -// 'chr' is a namespace defined as a synonym for 'std::chrono' -chr : namespace == std::chrono; - -// 'chrlit' is a namespace defined as a synonym for 'std::chrono_literals' -chrlit : namespace == std::chrono_literals; - -main: () = { - using namespace chrlit; - - // The next two lines are equivalent - std::cout << "1s is (std::chrono::nanoseconds(1s).count())$ns\n"; - std::cout << "1s is (chr::nanoseconds(1s).count())$ns\n"; -} -// Prints: -// 1s is 1000000000ns -// 1s is 1000000000ns -``` - - -## Type aliases - -A namespace alias is written the same way as a [type](types.md), but using `==` and with the name of another type as its value. For example: - -``` cpp title="Type aliases" hl_lines="1 2 7 10" -// 'imap' is a type defined as a synonym for 'std::map' -imap : type == std::map; - -main: () = { - // The next two lines declare two objects with identical type - map1: std::map = (); - map2: imap = (); - - // Assertion they are the same type, using the same_as concept - assert( std::same_as< decltype(map1), decltype(map2) > ); -} -``` - - -## Function aliases - -A function alias is written the same way as a [function](functions.md), but using `==` and with a side-effect-free body as its value; the body must always return the same value for the same input arguments. For example: - -``` cpp title="Function aliases" hl_lines="1 2 6 9 12 15" -// 'square' is a function defined as a synonym for the value of 'i * i' -square: (i: i32) -> _ == i * i; - -main: () = { - // It can be used at compile time, with compile time values - ints: std::array = (); - - // Assertion that the size is the square of 4 - assert( ints.size() == 16 ); - - // And if can be used at run time, with run time values - std::cout << "the square of 4 is (square(4))$\n"; -} -// Prints: -// the square of 4 is 16 -``` - -> Note: A function alias is compiled to a Cpp1 `#!cpp constexpr` function. - - -## Object aliases - -An object alias is written the same way as an [object](objects.md), but using `==` and with a side-effect-free value. For example: - -``` cpp title="Function aliases" hl_lines="1 2 5 6" -// 'BufferSize' is an object defined as a synonym for the value 1'000'000 -BufferSize: i32 == 1'000'000; - -main: () = { - buf: std::array = (); - assert( buf.size() == BufferSize ); -} -``` - -> Note: An object alias is compiled to a Cpp1 `#!cpp constexpr` object. - diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index 9099108465..eaa4d461e6 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -1,6 +1,6 @@ -# Declaration syntax +# Declarations and aliases -## Overview: Unified declaration syntax +## Unified declarations All Cpp2 declarations are written as **"_name_ `:` _kind_ `=` _statement_"**. @@ -20,10 +20,70 @@ Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted > > - When the type is omitted, whitespace does not matter, and writing `#!cpp x: = 0;` or `#!cpp x : = 0;` or `#!cpp x := 0;` or other whitespace is just a stylistic choice. This documentation's style uses the last one, except when there are multiple adjacent declaration lines this style lines up their `:` and `=`. > -> - `==` stresses that this name will always have the given value, to express [aliases](aliases.md) and side-effect-free 'constexpr' [function aliases](aliases.md/#function-aliases). +> - `==` stresses that this name will always have the given value, to express [aliases](#aliases) and side-effect-free 'constexpr' [function aliases](#function-aliases). -## Template parameters +### Unnamed declaration expressions + +In an expression, most declarations can be written without a name (just starting with `:`). Such unnamed declaration expressions are useful for single-use temporary variables or 'lambda' functions that don't need a name to be reused elsewhere. For example: + +- `#!cpp :widget = 42` is an unnamed expression-local (aka temporary) object of type `widget` defined as having the initial value `#!cpp 42`. It uses the same general syntax, just + +- `#!cpp :(x) = std::cout << x` is an unnamed expression-local generic function expression (aka lambda) defined as having the given one-statement body. The body can include [captures](expressions.md/#captures) + +Both just omit the name and make the final `;` optional. Otherwise, they have the identical syntax and meaning as if you declared the same thing with a name outside expression scope (e.g., `w: widget = 42;` or `f: (x) = std::cout << x;`) and then used the name in the expression. + +> Note: Throughout Cpp2, every declaration is written with `:`, and every use of `:` is a declaration. + + + +### From functions to local scopes, and back again + +The function syntax is deliberately designed to be general, so you can omit parts. This means Cpp2 has no special "lambda function" syntax for unnamed functions; an unnamed function is really an unnamed function, written using the ordinary function just without a name. This scales all the way down to ordinary blocks and statements, which are written the same as functions that have no name or parameters. + +We can illustrate this in two directions. First, let's start with a full function, and successively omit optional parts that we aren't currently using: + +``` cpp title="Start with a full function, and successively omit optional parts if unused" hl_lines="1 5 9 13" +// Full named function +f:(x: int = init) = { /*...*/ } // x is a parameter to the function +f:(x: int = init) = statement; // same, { } is implicit + +// Omit name => anonymous function (aka 'lamba') + :(x: int = init) = { /*...*/ } // x is a parameter to the function + :(x: int = init) = statement; // same, { } is implicit + +// Omit declaration => local and immediate (aka 'let' in other languages) + (x: int = init) { /*...*/ } // x is a parameter to this + (x: int = init) statement; // compound or single-statement + +// Omit parameters => ordinary block or statement + { /*...*/ } // ordinary compound statement + statement; // ordinary statement +``` + +Conversely, we can start with an ordinary block or statement, and successively build it up to make it more powerful: + +``` cpp title="Start with an ordinary block or statement, and successively add parts" hl_lines="1 5 9 13" +// Ordinary block or statement + { /*...*/ } // ordinary compound statement + statement; // ordinary statement + +// Add parameters => more RAII locally-scoped variables + (x: int = init) { /*...*/ } // x is destroyed after this + (x: int = init) statement; // compound or single-statement + +// Add declaration => treat the code as a callable object + :(x: int = init) = { /*...*/ } // x is a parameter to the function + :(x: int = init) = statement; // same, { } is implicit + +// Add name => full named function +f:(x: int = init) = { /*...*/ } // x is a parameter to the function +f:(x: int = init) = statement; // same, { } is implicit + +``` + + +### Template parameters A template parameter list is enclosed by `<` `>` angle brackets, and the parameters separated by commas. Each parameter is declared using the [same syntax as any type or object](declarations.md). If a parameter's **`:`** ***kind*** is not specified, the default is `: type`. @@ -45,7 +105,7 @@ tuple: type ``` -## `#!cpp requires` constraints +### `#!cpp requires` constraints A `#!cpp requires` ***condition*** constraint appears at the end of the ***kind*** of a templated declaration. If the condition evaluates to `#!cpp false`, that specialization of the template is ignored as if not declared. @@ -61,7 +121,7 @@ print: ``` -## Examples +### Examples ``` cpp title="Consistent declarations — name : kind = statement" linenums="1" hl_lines="2 6 10 15 24 28 32 43 49 53" // n is a namespace defined as the following scope @@ -121,3 +181,102 @@ n: namespace ``` > Note: `@enum` is a metafunction, which provides an easy way to opt into a group of defaults, constraints, and generated functions. For details, see [`@enum`](metafunctions.md#enum). + + +## Aliases + +Aliases are pronounced **"synonym for**, and written using the same **name `:` kind `=` value** [declaration syntax](../cpp2/declarations.md) as everything in Cpp2: + +- **name** is declared to be a synonym for **value**. + +- **kind** can be any of the kinds: `namespace`, `type`, a function signature, or a type. + +- **`==`**, pronounced **"defined as a synonym for"**, always precedes the value. The `==` syntax stresses that during compilation every use of the name could be equivalently replaced with the value. + +- **value** is the expression that the **name** is a synonym for. + + +### Namespace aliases + +A namespace alias is written the same way as a [namespace](namespaces.md), but using `==` and with the name of another namespace as its value. For example: + +``` cpp title="Namespace aliases" hl_lines="1 2 4 5 8 12 16" +// 'chr' is a namespace defined as a synonym for 'std::chrono' +chr : namespace == std::chrono; + +// 'chrlit' is a namespace defined as a synonym for 'std::chrono_literals' +chrlit : namespace == std::chrono_literals; + +main: () = { + using namespace chrlit; + + // The next two lines are equivalent + std::cout << "1s is (std::chrono::nanoseconds(1s).count())$ns\n"; + std::cout << "1s is (chr::nanoseconds(1s).count())$ns\n"; +} +// Prints: +// 1s is 1000000000ns +// 1s is 1000000000ns +``` + + +### Type aliases + +A namespace alias is written the same way as a [type](types.md), but using `==` and with the name of another type as its value. For example: + +``` cpp title="Type aliases" hl_lines="1 2 7 10" +// 'imap' is a type defined as a synonym for 'std::map' +imap : type == std::map; + +main: () = { + // The next two lines declare two objects with identical type + map1: std::map = (); + map2: imap = (); + + // Assertion they are the same type, using the same_as concept + assert( std::same_as< decltype(map1), decltype(map2) > ); +} +``` + + +### Function aliases + +A function alias is written the same way as a [function](functions.md), but using `==` and with a side-effect-free body as its value; the body must always return the same value for the same input arguments. For example: + +``` cpp title="Function aliases" hl_lines="1 2 6 9 12 15" +// 'square' is a function defined as a synonym for the value of 'i * i' +square: (i: i32) -> _ == i * i; + +main: () = { + // It can be used at compile time, with compile time values + ints: std::array = (); + + // Assertion that the size is the square of 4 + assert( ints.size() == 16 ); + + // And if can be used at run time, with run time values + std::cout << "the square of 4 is (square(4))$\n"; +} +// Prints: +// the square of 4 is 16 +``` + +> Note: A function alias is compiled to a Cpp1 `#!cpp constexpr` function. + + +### Object aliases + +An object alias is written the same way as an [object](objects.md), but using `==` and with a side-effect-free value. For example: + +``` cpp title="Function aliases" hl_lines="1 2 5 6" +// 'BufferSize' is an object defined as a synonym for the value 1'000'000 +BufferSize: i32 == 1'000'000; + +main: () = { + buf: std::array = (); + assert( buf.size() == BufferSize ); +} +``` + +> Note: An object alias is compiled to a Cpp1 `#!cpp constexpr` object. + diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index 6b1b6090b1..e324289dfb 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -270,15 +270,118 @@ outer: while i Unnamed function expressions (aka lambdas) - -TODO ## Move/forward from definite last use -TODO +In a function body, a **definite last use** of a local name is a single use of that name in a statement that is not in a loop, where no control flow path after that statement mentions the name again. + +For each definite last use: + +- If the name is a local object or a `copy` or `move` parameter, we know the object will not be used again before being destroyed, and so the object is automatically treated as an rvalue (move candidate). If the expression that contains the last use is able to move from the rvalue, the move will happen automatically. + +- If the name is a `forward` parameter, the object is automatically forwarded to preserve its constness and value category (`std::forward`-ed). + +For example: + +``` cpp title="Definite last uses" linenums="1" hl_lines="13 16 19 21" +f: ( + copy x: some_type, + move y: some_type, + forward z: some_type + ) += { + w: some_type = "y"; + + prepare(x); // NOT a definite last use + + if something() { + process(y); + z.process(x); // definite last uses of x and z + } + else { + cout << z; // definite last use of z + } + + transfer(y); // definite last use of y + + offload(w); // definite last use of w +} +``` + +In this example: + +- `x` has a definite last use on one path, but not another. Line 13 is a definite last use that automatically treats `x` as an rvalue. However, if the `#!cpp else` is taken, `x` gets no special automatic handling. Line 9 is not a definite last use because `x` could be used again where it is mentioned later on line 13. + +- `y` has a definite last use on every path, in this case the same on all executions of the function. Line 19 is a definite last use that automatically treats `x` as an rvalue. -## Generality: Unifying functions and local scopes +- `z` has a definite last use on every path, but unlike `y` it can be a different last use on different executions of the function. That's fine, each of lines 13 and 16 is a definite last use that automatically forwards the constness and value category of `z`. + +- `w` has a definite last use on every path, in this case the same on all executions of the function. Line 21 is a definite last use that automatically treats `w` as an rvalue. + + +## Generality note: Summary of function defaults + +There is a single function syntax, designed so we can just omit the parts we're not currently using. + +For example, let's express in full verbose detail that `equals` is a function template that has two type parameters `T` and `U`, two ordinary `in` parameters `a` and `b` of type `T` and `U` respectively, and a deduced return type, and its body returns the result of `a == b`: + +``` cpp title="equals: A generic function written in full detail (using no defaults)" +equals: (in a: T, in b: U) -> _ = { return a == b; } +``` + +We can write all that, but we don't have to. + +First, `: type` is the default for template parameters, so we can omit it since that's what we want: + +``` cpp title="equals: Identical meaning, now using the :type default for template parameters" +equals: (in a: T, in b: U) -> _ = { return a == b; } +``` + +So far, the return type is already using one common default available throughout Cpp2: the wildcard `_` (pronounced "don't care"). Since this function's body doesn't actually use the parameter type names `T` and `U`, we can just use wildcards for the parameter types too: + +``` cpp title="equals: Identical meaning, now using the _ wildcard also for the parameter types" +equals: (in a: _, in b: _) -> _ = { return a == b; } +``` + +Next, `: _` is also the default parameter type, so we don't need to write even that: + +``` cpp title="equals: Identical meaning, now using the :_ default parameter type" +equals: (in a, in b) -> _ = { return a == b; } +``` + +Next, `in` is the default [parameter passing mode](#parameters). So we can use that default too: + +``` cpp title="equals: Identical meaning, now using the 'in' default parameter passing style" +equals: (a, b) -> _ = { return a == b; } +``` + +We already saw that `{` `}` is the default for a single-line function that returns nothing. Similarly, `{ return` and `}` is the default for a single-line function that returns something: + +``` cpp title="equals: Identical meaning, now using the { return ... } default body decoration" +equals: (a, b) -> _ = a == b; +``` + +Next, `#!cpp -> _ =` (deduced return type) is the default for single-expression functions that return something and so can be omitted: + +``` cpp title="equals: Identical meaning, now using the -> _ = default for functions that return something" +equals: (a, b) a == b; +``` + +Finally, at expression scope (aka "lamba/temporary") functions/objects aren't named, and the trailing `;` is optional: + +``` cpp title="(not) 'equals': Identical meaning, but without a name as an unnamed function at expression scope" +:(a, b) a == b +``` + +Here are some additional examples of unnamed function expressions: + +``` cpp title="Some more examples of unnamed function expressions" +std::ranges::for_each( a, :(x) = std::cout << x ); + +std::ranges::transform( a, b, :(x) x+1 ); + +where_is = std::ranges::find_if( a, :(x) x == waldo$ ); +``` -TODO +> Note: Cpp2 doesn't have a separate "lambda" syntax; you just use the regular function syntax at expression scope to write an unnamed function, and the syntactic defaults are chosen to make such function expressions convenient to write. And because in Cpp2 all local variable [capture](expressions.md#captures) (for example, `waldo$` above) is written in the body, it doesn't affect the function syntax. diff --git a/mkdocs.yml b/mkdocs.yml index 5519c7d70d..a181ee18a0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -57,14 +57,13 @@ nav: - 'Cpp2 reference': - 'Common concepts': cpp2/common.md - 'Expressions': cpp2/expressions.md - - 'Declaration syntax': cpp2/declarations.md + - 'Declarations and aliases': cpp2/declarations.md - 'Objects, initialization, and memory': cpp2/objects.md - 'Functions, branches, and loops': cpp2/functions.md - 'Contracts and assertions': cpp2/contracts.md - 'Types and inheritance': cpp2/types.md - 'Metafunctions and reflection': cpp2/metafunctions.md - 'Namespaces': cpp2/namespaces.md - - 'Aliases': cpp2/aliases.md - 'Modules': cpp2/modules.md - 'Cppfront reference': - 'Using Cpp1 (today''s syntax) and Cpp2 in the same source file': cppfront/mixed.md From c62fbbdacb223a8b7eea08e51e9c9b0779316eae Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Thu, 7 Mar 2024 14:56:13 -0800 Subject: [PATCH 57/64] Merge previous commit --- docs/cpp2/declarations.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index eaa4d461e6..d8bfe88e94 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -185,7 +185,7 @@ n: namespace ## Aliases -Aliases are pronounced **"synonym for**, and written using the same **name `:` kind `=` value** [declaration syntax](../cpp2/declarations.md) as everything in Cpp2: +Aliases are pronounced **"synonym for"**, and written using the same **name `:` kind `=` value** [declaration syntax](../cpp2/declarations.md) as everything in Cpp2: - **name** is declared to be a synonym for **value**. @@ -222,7 +222,7 @@ main: () = { ### Type aliases -A namespace alias is written the same way as a [type](types.md), but using `==` and with the name of another type as its value. For example: +A type alias is written the same way as a [type](types.md), but using `==` and with the name of another type as its value. For example: ``` cpp title="Type aliases" hl_lines="1 2 7 10" // 'imap' is a type defined as a synonym for 'std::map' @@ -234,7 +234,7 @@ main: () = { map2: imap = (); // Assertion they are the same type, using the same_as concept - assert( std::same_as< decltype(map1), decltype(map2) > ); + static_assert( std::same_as< decltype(map1), decltype(map2) > ); } ``` @@ -252,7 +252,7 @@ main: () = { ints: std::array = (); // Assertion that the size is the square of 4 - assert( ints.size() == 16 ); + static_assert( ints.size() == 16 ); // And if can be used at run time, with run time values std::cout << "the square of 4 is (square(4))$\n"; @@ -274,7 +274,7 @@ BufferSize: i32 == 1'000'000; main: () = { buf: std::array = (); - assert( buf.size() == BufferSize ); + static_assert( buf.size() == BufferSize ); } ``` From 11403c86d7aa7ae90a7dd758cdce17616691907a Mon Sep 17 00:00:00 2001 From: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Date: Sat, 9 Mar 2024 03:49:52 +1000 Subject: [PATCH 58/64] Update functions.md (#1011) Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> --- docs/cpp2/functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/cpp2/functions.md b/docs/cpp2/functions.md index e324289dfb..59f7708ecc 100644 --- a/docs/cpp2/functions.md +++ b/docs/cpp2/functions.md @@ -383,5 +383,5 @@ std::ranges::transform( a, b, :(x) x+1 ); where_is = std::ranges::find_if( a, :(x) x == waldo$ ); ``` -> Note: Cpp2 doesn't have a separate "lambda" syntax; you just use the regular function syntax at expression scope to write an unnamed function, and the syntactic defaults are chosen to make such function expressions convenient to write. And because in Cpp2 all local variable [capture](expressions.md#captures) (for example, `waldo$` above) is written in the body, it doesn't affect the function syntax. +> Note: Cpp2 doesn't have a separate "lambda" syntax; you just use the regular function syntax at expression scope to write an unnamed function, and the syntactic defaults are chosen to make such function expressions convenient to write. And because in Cpp2 every local variable [capture](expressions.md#captures) (for example, `waldo$` above) is written in the body, it doesn't affect the function syntax. From 5b930e8a7f3204849888b519a2b3f7fdc2885ac1 Mon Sep 17 00:00:00 2001 From: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Date: Sat, 9 Mar 2024 03:50:40 +1000 Subject: [PATCH 59/64] Update declarations.md (#1010) Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> --- docs/cpp2/declarations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cpp2/declarations.md b/docs/cpp2/declarations.md index d8bfe88e94..b829b0b12e 100644 --- a/docs/cpp2/declarations.md +++ b/docs/cpp2/declarations.md @@ -27,9 +27,9 @@ Various parts of the syntax allow a `_` "don't care" wildcard or can be omitted In an expression, most declarations can be written without a name (just starting with `:`). Such unnamed declaration expressions are useful for single-use temporary variables or 'lambda' functions that don't need a name to be reused elsewhere. For example: -- `#!cpp :widget = 42` is an unnamed expression-local (aka temporary) object of type `widget` defined as having the initial value `#!cpp 42`. It uses the same general syntax, just +- `#!cpp :widget = 42` is an unnamed expression-local (aka temporary) object of type `widget` defined as having the initial value `#!cpp 42`. It uses the same general syntax, just without declaring a name. -- `#!cpp :(x) = std::cout << x` is an unnamed expression-local generic function expression (aka lambda) defined as having the given one-statement body. The body can include [captures](expressions.md/#captures) +- `#!cpp :(x) = std::cout << x` is an unnamed expression-local generic function expression (aka lambda) defined as having the given one-statement body. The body can include [captures](expressions.md/#captures). Both just omit the name and make the final `;` optional. Otherwise, they have the identical syntax and meaning as if you declared the same thing with a name outside expression scope (e.g., `w: widget = 42;` or `f: (x) = std::cout << x;`) and then used the name in the expression. From 55711005f12c963d59a08697ffffc6bdb064e5bd Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Fri, 8 Mar 2024 18:06:14 -0800 Subject: [PATCH 60/64] Complete the metafunctions section --- docs/cpp2/metafunctions.md | 294 +++++++++++++++++++++++++++++++++---- 1 file changed, 266 insertions(+), 28 deletions(-) diff --git a/docs/cpp2/metafunctions.md b/docs/cpp2/metafunctions.md index c29abc5c9d..8cb2270865 100644 --- a/docs/cpp2/metafunctions.md +++ b/docs/cpp2/metafunctions.md @@ -11,7 +11,7 @@ A metafunction is a compile-time function that can participate in interpreting t The most important thing about metafunctions is that they are not hardwired language features — they are compile-time library code that uses the reflection and code generation API, that lets the author of an ordinary 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. -## Applying metafunctions +## Applying metafunctions using `@` Metafunctions provide an easy way for a type author to opt into a group of defaults, constraints, and generated functions: Just write `@name` afer the `:` of a declaration, where `name` is the name of the metafunction. This lets the type author declare (and the human reader see) the intent up front: "This isn't just any `type`, this is a `@value type`" which automatically gives the type default/copy/move construction and assignment, `<=>` with `std::strong_ordering` comparisons, and guarantees that it has a public destructor and no protected or virtual functions: @@ -28,46 +28,211 @@ point2d: @value type = { ## Generating source code at compile time -TODO +A metafunction applied to a definition using `@` gets to participate in interpreting the meaning of the definition by inspecting and manipulating the definition's parse tree. For example + +``` cpp title="shape.cpp2: Using @interface @print" hl_lines="1" +shape: @interface @print type = { + draw : (this); + move_by: (this, dx: double, dy: double); +} +``` + +The above code: + +- applies `@interface`, which makes functions pure virtual by default and defines a virtual destructor with a do-nothing body if there isn't already a virtual destructor (among other things), and + +- then applies `@print`, which pretty-prints the resulting parse tree as source code to the console so that we can see the results of what the first metafunction did. + +The result of compiling this is the following cppfront output, which is the `@interface`-modified Cpp2 source code as printed by `@print`: + +``` cpp title="'cppfront shape.cpp2' output to the console, from @print" hl_lines="1" +shape:/* @interface @print */ type = +{ + public draw:(virtual in this); + + public move_by:( + virtual in this, + in dx: double, + in dy: double + ); + + operator=:(virtual move this) = + { + } +} +``` + +Finally, cppfront also emits the following in `shape.cpp`: + +``` cpp title="'cppfront shape.cpp' output to 'shape.cpp'" +class shape { + public: virtual auto draw() const -> void = 0; + public: virtual auto move_by(cpp2::in dx, cpp2::in dy) const -> void = 0; + public: virtual ~shape() noexcept; + + public: shape() = default; + public: shape(shape const&) = delete; /* No 'that' constructor, suppress copy */ + public: auto operator=(shape const&) -> void = delete; + +}; + +shape::~shape() noexcept{} +``` ## Built-in metafunctions The following metafunctions are provided in the box with cppfront. -### interface -TODO +### For regular value-like types (copyable, comparable) + + +#### `ordered`, `weakly_ordered`, `partially_ordered` + +An `ordered` (or `weakly_ordered` or `partially_ordered`) type has an `#!cpp operator<=>` three-way comparison operator that returns `std::strong_ordering` (or `std::weak_ordering` or `std::partial_ordering`, respectively). This means objects of this type can be used in all binary comparisons: `<`, `<=`, `==`, `!=`, `>=`, and `>`. + +If the user explicitly writes `operator<=>`, its return type must be the same as the one implied by the metafunction they chose. + +If the user doesn't explicitly write `operator<=>`, a default memberwise `operator<=>: (this, that) -> /* appropriate _ordering */;` will be generated for the type. + +These metafunctions will emit a compile-time error if: + +- a user-written `operator<=>` returns a different type than the one implied by the metafunction they chose + +> Notes: +> +> "... A totally ordered type ... requires `#!cpp operator<=>` that returns `std::strong_ordering`. If the function is not user-written, a lexicographical memberwise implementation is generated by default..." +> +> — P0707R4, section 3 + +> Note: This feature derived from Cpp2 was already adopted into Standard C++ via paper [P0515](https://wg21.link/p0515), so most of the heavy lifting is done by the Cpp1 C++20/23 compiler, including the memberwise default semantics. In contrast, cppfront has to do the work itself for default memberwise semantics for operator= assignment as those aren't yet part of Standard C++. + + +#### `copyable` + +A `copyable` type has (copy and move) x (construction and assignment). + +If the user explicitly writes any of the copy/move `operator=` functions, they must also write the most general one that takes `(out this, that)`. + +If the user doesn't write any of the copy/move `operator=` functions, a default general memberwise `operator=: (out this, that) = { }` will be generated for the type. + +`copyable` will emit a compile-time error if: + +- there is a user-written `operator=` but no user-written `operator=: (out this, that)` + + +#### `basic_value`, `value`, `weakly_ordered_value`, `partially_ordered_value` + +A `basic_value` type is a regular type: [`copyable`](#copyable), default constructible, and not polymorphic (no protected or virtual functions). + +A `value` (or `weakly_ordered_value` or `partially_ordered_value`) is a `basic_value` that is also [`ordered`](#ordered) (or `weakly_ordered` or `partially_ordered`, respectively). + +These metafunctions will emit a compile-time error if: +- any function is protected or virtual -### polymorphic_base +- the type has a destructor that is not public -TODO +> Notes: +> +> "A value is ... a regular type. 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)." +> +> — P0707R4, section 3 -### ordered, weakly_ordered, partially_ordered +#### `struct` -TODO +A `struct` is a type with only public bases, objects, and functions, with no virtual functions, and with no user-defined constructors (i.e., no invariants) or assignment or destructors. +`struct` is implemented in terms of [`cpp1_rule_of_zero`](#cpp1_rule_of_zero). -### copyable +`struct` will emit a compile-time error if: -TODO +- any member is non-public +- any function is virtual -### basic_value, value, weakly_ordered_value, partially_ordered_value +- there is a user-written `operator=` -TODO +> Notes: +> +> "By definition, a `struct` is a `class` in which members are by default `public`; that is, +> +> struct s { ... +> +> is simply shorthand for +> +> class s { public: ... +> +> ... Which style you use depends on circumstances and taste. I usually prefer to use `struct` for classes that have all +> data `public`." +> +> — Stroustrup (The C++ Programming Language, 3rd ed., p. 234) -### struct +### For polymorphic types (interfaces, base classes) -TODO +#### `interface` -### `enum` +An `interface` type is an abstract base class having only pure virtual functions. -Cpp2 has no `enum` feature hardwired into the language. Instead you apply the `@enum` metafunction when writing an ordinary `type`: +Cpp2 has no `interface` feature hardwired into the language, as C# and Java do. Instead you apply the `@interface` metafunction when writing an ordinary `type`. For a detailed example, see [the `shape` example above](#generating-source-code-at-compile-time). + +`interface` will emit a compile-time error if: + +- the type contains a data object + +- the type has a copy or move function (the diagnostic message will suggest a virtual `clone` function instead) + +- any function has body + +- any function is nonpublic + +> Notes: +> +> "... an abstract base class defines an interface ..." +> +> — Stroustrup (The Design and Evolution of C++, 12.3.1) + + +#### `polymorphic_base` + +A `polymorphic_base` type is a pure polymorphic base type that is not copyable, and whose destructor is either public and virtual or protected and nonvirtual. + +Unlike an [interface](#interface), it can have nonpublic and nonvirtual functions. + +`polymorphic_base` will emit a compile-time error if: + +- the type has a copy or move function (the diagnostic message will suggest a virtual `clone` function instead) + +- the type has a destructor that is not public and virtual, and also not protected and nonvirtual + +> Notes: +> +> "C.35: A base class destructor should be either public and virtual, or protected and non-virtual." +> +> "[C.43] ... a base class should not be copyable, and so does not necessarily need a default constructor." +> +> — Stroustrup, Sutter, et al. (C++ Core Guidelines) + + +### For enumeration types + + +#### `enum` + +Cpp2 has no `enum` feature hardwired into the language. Instead you apply the `@enum` metafunction when writing an ordinary `type`. + +`enum` will emit a compile-time error if: + +- any member has the reserved name `operator=` or `operator<=>`, as these will be generated by the metafunction + +- an enumerator is not public or does not have a deduced type + +For example: ``` cpp title="Using the @enum metafunction when writing a type" hl_lines="14" // skat_game is declaratively a safe enumeration type: it has @@ -111,9 +276,34 @@ janus: @enum type = { } ``` -### `flag_enum` +> Notes: +> +> "C enumerations constitute a curiously half-baked concept. ... the cleanest way out was to deem each enumeration a separate type." +> +> — Stroustrup (The Design and Evolution of C++, 11.7) +> +> "An enumeration is a distinct type ... with named constants" +> +> — ISO C++ Standard +> +> "An `enum`[...] is a totally ordered value type that stores a value of its enumerators's type, and otherwise has only public member variables of its enumerator's type, all of which are naturally scoped because they are members of a type." +> +> — P0707R4, section 3 + + +#### `flag_enum` + +`flag_enum` is a variation on `enum` that has power-of-two default enumerator values, a default signed underlying type that is large enough to hold the values, and supports bitwise operators. + +`flag_enum` will emit a compile-time error if: + +- any member has the reserved name `operator=`, `operator<=>`, `has`, `set`, `clear`, `to_string`, `get_raw_value`, or `none`, as these will be generated by the metafunction + +- an enumerator is not public or does not have a deduced type + +- the values are outside the range that can be represented by the largest default underlying type -`flag_enum` is a variation on `enum` that has power-of-two default enumerator values, a default unsigned underlying type, and supports bitwise operators: +For example: ``` cpp title="Using the @flag_enum metafunction when writing a type" hl_lines="11" // file_attributes is declaratively a safe flag enum type: @@ -134,10 +324,32 @@ file_attributes: @flag_enum type = { } ``` +> Notes: +> +> "`flag_enum` expresses an enumeration that stores values +> corresponding to bitwise-or'd enumerators. The enumerators must +> be powers of two, and are automatically generated [...] A none +> value is provided [...] Operators `|` and `&` are provided to +> combine and extract values." +> +> — P0707R4, section 3 -### `union` -`@union` declaratively opts into writing a safe discriminated union/variant dynamic type. For example: +### For dynamic types + + +#### `union` + +`@union` declaratively opts into writing a safe discriminated union/variant dynamic type. + +`union` will emit a compile-time error if: + +- any alternative is not public or has an initializer + +- any member starts with the reserved name prefix `is_` or `set_`, as these will be generated by the metafunction + + +For example: ``` cpp title="Using the @union metafunction when writing a type" hl_lines="10 18-20 25 26" // name_or_number is declaratively a safe union/variant type: @@ -200,23 +412,49 @@ main: () = { } ``` -### cpp1_rule_of_zero +> Notes: +> +> "As with void*, programmers should know that unions [...] are +> inherently dangerous, should be avoided wherever possible, +> and should be handled with special care when actually needed." +> +> — Stroustrup (The Design and Evolution of C++, 14.3.4.1) +> +> "C++17 needs a type-safe `union`... The implications of the +> consensus `variant` design are well understood and have been +> explored over several LEWG discussions, over a thousand emails, +> a joint LEWG/EWG session, and not to mention 12 years of +> experience with Boost and other libraries." +> +> — Axel Naumann, in [P0088](https://wg21.link/p0088), +> the adopted proposal for C++17 `std::variant` + + +### Helpers and utilities -TODO -### print +#### `cpp1_rule_of_zero` -TODO +A `cpp1_rule_of_zero` type is one that has no user-written copy/move/destructor functions, and for which Cpp2 should generate nothing so that the Cpp1 defaults for generated special member functions are accepted. +> Notes: +> +> C.20: If you can avoid defining default operations, do +> +> Reason: It's the simplest and gives the cleanest semantics. +> +> This is known as "the rule of zero". +> +> — Stroustrup, Sutter, et al. (C++ Core Guidelines) -## Writing your own metafunctions -TODO +#### `print` +`print` prints a pretty-printed visualization of the type to the console. -## Reflection API reference +This is most useful for debugging metafunctions, and otherwise seeing the results of applying previous metafunctions. -TODO +For a detailed example, see [the `shape` example above](#generating-source-code-at-compile-time). [^variant]: With `variant`, 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. From 663b3d043da1e40469a0754bcdf1124aa6594feb Mon Sep 17 00:00:00 2001 From: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> Date: Mon, 11 Mar 2024 09:25:52 +1000 Subject: [PATCH 61/64] Update metafunctions.md (#1015) Signed-off-by: Neil Henderson <2060747+bluetarpmedia@users.noreply.github.com> --- docs/cpp2/metafunctions.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/cpp2/metafunctions.md b/docs/cpp2/metafunctions.md index 8cb2270865..1fee0d3237 100644 --- a/docs/cpp2/metafunctions.md +++ b/docs/cpp2/metafunctions.md @@ -28,7 +28,7 @@ point2d: @value type = { ## Generating source code at compile time -A metafunction applied to a definition using `@` gets to participate in interpreting the meaning of the definition by inspecting and manipulating the definition's parse tree. For example +A metafunction applied to a definition using `@` gets to participate in interpreting the meaning of the definition by inspecting and manipulating the definition's parse tree. For example: ``` cpp title="shape.cpp2: Using @interface @print" hl_lines="1" shape: @interface @print type = { @@ -128,6 +128,17 @@ A `basic_value` type is a regular type: [`copyable`](#copyable), default constru A `value` (or `weakly_ordered_value` or `partially_ordered_value`) is a `basic_value` that is also [`ordered`](#ordered) (or `weakly_ordered` or `partially_ordered`, respectively). +```mermaid +graph TD; + value---->basic_value; + weakly_ordered_value---->basic_value; + partially_ordered_value---->basic_value; + basic_value-->copyable; + value-->ordered; + partially_ordered_value-->partially_ordered; + weakly_ordered_value-->weakly_ordered; +``` + These metafunctions will emit a compile-time error if: - any function is protected or virtual @@ -142,6 +153,7 @@ These metafunctions will emit a compile-time error if: > > — P0707R4, section 3 + #### `struct` A `struct` is a type with only public bases, objects, and functions, with no virtual functions, and with no user-defined constructors (i.e., no invariants) or assignment or destructors. @@ -187,7 +199,7 @@ Cpp2 has no `interface` feature hardwired into the language, as C# and Java do. - the type has a copy or move function (the diagnostic message will suggest a virtual `clone` function instead) -- any function has body +- any function has a body - any function is nonpublic From 21d7d43f2ccda685019e9e5bcd64bcb8e5717147 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sun, 10 Mar 2024 17:01:12 -0700 Subject: [PATCH 62/64] Add contracts documentation --- docs/cpp2/contracts.md | 117 +++++++++++++++++++++++++++++++++++-- docs/cpp2/metafunctions.md | 95 ++---------------------------- mkdocs.yml | 2 +- 3 files changed, 118 insertions(+), 96 deletions(-) diff --git a/docs/cpp2/contracts.md b/docs/cpp2/contracts.md index 627d2cf2f3..b72e003773 100644 --- a/docs/cpp2/contracts.md +++ b/docs/cpp2/contracts.md @@ -3,14 +3,123 @@ ## Overview -TODO +Cpp2 currently supports three kinds of contracts: + +- **Preconditions and postconditions.** A function declaration can include `pre(condition)` and `post(condition)` before the `= /* function body */`. Before entering the function body, preconditions are fully evaluated and postconditions are captured (and performs their captures, if any). Immediately before exiting the function body via a normal return, postconditions are evaluated. If the function exits via an exception, postconditions are not evaluated. + +- **Assertions.** Inside a function body, writing `assert(condition)` assertion statements. Assertions are evaluated when control flow passes through them. + +Notes: + +- `condition` is an expression that evaluates to `#!cpp true` or `#!cpp false`. + +- Optionally, `condition` may be followed by `, "message"`, a message to include if a violation occurs. For example, `pre(condition, "message")`. + +- Optionally, a `` can be written inside `<` `>` angle brackets immediately before the `(`, to designate that this test is part of the [contract group](#contract-groups) named `Group`. If a violation occurs, `Group.report_violation()` will be called. For example, `pre(condition)`. + +For example: + +``` cpp title="Precondition and postcondition examples" hl_lines="2 3" +insert_at: (container, where: int, val: int) + pre( 0 <= where <= vec.ssize(), "position (where)$ is outside 'val'" ) + post ( container.ssize() == container.ssize()$ + 1 ) += { + _ = container.insert( container.begin()+where, val ); +} +``` + +In this example: + +- The `$` captures are performed before entering the function. + +- The precondition is part of the `Bounds` safety contract group and is checked before entering the function. If the check fails, say because `where` is `#!cpp -1`, then `#!cpp cpp2::Bounds.report_violation("position -1 is outside 'val'")` is called. + +- The postcondition is part of the `Default` safety contract group. If the check fails, then `#!cpp cpp2::Default.report_violation()` is called. ## Contract groups -TODO +Contract groups are useful to enable or disable or [set custom handlers](#violation-handlers) independently for different groups of contracts. A contract group `G` is just the name of an object that can be called with `G.report_violation()` and `G.report_violation(message)`, where `message` is a `* const char` C-style text string. + +You can create new contract groups just by creating new objects that have a `.report_violation` function. The object's name is the contract group's name. The object can be at any scope: local, global, or heap. + +For example, here are some ways to use contract groups, for convenience using [`cpp2::contract_group`](#violation_handlers) which is a convenient group type: + +``` cpp title="Using contract groups" hl_lines="1 4 6 10-12" +GroupA: cpp2::contract_group = (); // a global group + +func: () = { + GroupB: cpp2::contract_group = (); // a local group + + GroupC := new(); // a dynamically allocated group + + // ... + + assert( some && condition ); + assert( another && condition ); + assert( another && condition ); +} +``` + +You can make all the objects in a class hierarchy into a contract group by having a `.report_violation` function in a base class, and then writing contracts in that hierarchy using `` as desired. This technique used in cppfront's own reflection API: + +``` cpp title="Example of using 'this' as a contract group, from cppfront 'reflect.h2'" hl_lines="8 9" +function_declaration: @copyable type = +{ + // inherits from a base class that provides '.report_violation' + + // ... + + add_initializer: (inout this, source: std::string_view) + pre (!has_initializer(), "cannot add an initializer to a function that already has one") + pre (parent_is_type(), "cannot add an initializer to a function that isn't in a type scope") + = { /*...*/ } + + // ... + +} +``` + + +## `cpp2::contract_group`, and customizable violation handling + +The contract group object could also provide additional functionality. For example, Cpp2 comes with the `cpp2::contract_group` type which allows installing a customizable handler for each object. Each object can only have one handler at a time, but the handler can change during the course of the program. `contract_group` supports: + +- `.set_handler(pfunc)` accepts a pointer to a handler function with signature `#!cpp * (* const char)`. + +- `.get_handler()` returns the current handler function pointer, or null if none is installed. + +- `.has_handler()` returns whether there is a current handler installed. + +- `.enforce(condition, message)` evaluates `condition`, and if it is `false` then calls `.report_violation(message)`. + +Cpp2 comes with five predefined `contract group` global objects in namespace `cpp2`: + +- `Default`, which is used as the default contract group for contracts that don't specify a group. + +- `Type` for type safety checks. + +- `Bounds` for bounds safety checks. + +- `Null` for null safety checks. + +- `Testing` for general test checks. +For these groups, the default handler is `cpp2::report_and_terminate`, which prints information about the violation to `std::cerr` and then calls `std::terminate()`. But you can customize it to do anything you want, including to integrate with any third-party or in-house error reporting system your project is already using. For example: -## Customizing the violation handler +``` cpp title="Example of customized contract violation handler" hl_lines="2 8-9" +main: () -> int = { + cpp2::Default.set_handler(call_my_framework&); + assert(false, "this is a test, this is only a test"); + std::cout << "done\n"; +} -TODO +call_my_framework: (msg: * const char) = { + // You can do anything you like here, including arbitrary work + // and integration with your current error reporting libraries + std::cout + << "sending error to my framework... [" + << msg << "]\n"; + exit(0); +} +``` diff --git a/docs/cpp2/metafunctions.md b/docs/cpp2/metafunctions.md index 1fee0d3237..f7075e2888 100644 --- a/docs/cpp2/metafunctions.md +++ b/docs/cpp2/metafunctions.md @@ -1,6 +1,8 @@ # Metafunctions +## Overview + A metafunction is a compile-time function that can participate in interpreting the meaning of a declaration, and can: - apply defaults (e.g., `interface` makes functions virtual by default) @@ -100,12 +102,6 @@ These metafunctions will emit a compile-time error if: - a user-written `operator<=>` returns a different type than the one implied by the metafunction they chose -> Notes: -> -> "... A totally ordered type ... requires `#!cpp operator<=>` that returns `std::strong_ordering`. If the function is not user-written, a lexicographical memberwise implementation is generated by default..." -> -> — P0707R4, section 3 - > Note: This feature derived from Cpp2 was already adopted into Standard C++ via paper [P0515](https://wg21.link/p0515), so most of the heavy lifting is done by the Cpp1 C++20/23 compiler, including the memberwise default semantics. In contrast, cppfront has to do the work itself for default memberwise semantics for operator= assignment as those aren't yet part of Standard C++. @@ -146,14 +142,6 @@ These metafunctions will emit a compile-time error if: - the type has a destructor that is not public -> Notes: -> -> "A value is ... a regular type. 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)." -> -> — P0707R4, section 3 - - #### `struct` A `struct` is a type with only public bases, objects, and functions, with no virtual functions, and with no user-defined constructors (i.e., no invariants) or assignment or destructors. @@ -168,21 +156,6 @@ A `struct` is a type with only public bases, objects, and functions, with no vir - there is a user-written `operator=` -> Notes: -> -> "By definition, a `struct` is a `class` in which members are by default `public`; that is, -> -> struct s { ... -> -> is simply shorthand for -> -> class s { public: ... -> -> ... Which style you use depends on circumstances and taste. I usually prefer to use `struct` for classes that have all -> data `public`." -> -> — Stroustrup (The C++ Programming Language, 3rd ed., p. 234) - ### For polymorphic types (interfaces, base classes) @@ -203,12 +176,6 @@ Cpp2 has no `interface` feature hardwired into the language, as C# and Java do. - any function is nonpublic -> Notes: -> -> "... an abstract base class defines an interface ..." -> -> — Stroustrup (The Design and Evolution of C++, 12.3.1) - #### `polymorphic_base` @@ -222,14 +189,6 @@ Unlike an [interface](#interface), it can have nonpublic and nonvirtual function - the type has a destructor that is not public and virtual, and also not protected and nonvirtual -> Notes: -> -> "C.35: A base class destructor should be either public and virtual, or protected and non-virtual." -> -> "[C.43] ... a base class should not be copyable, and so does not necessarily need a default constructor." -> -> — Stroustrup, Sutter, et al. (C++ Core Guidelines) - ### For enumeration types @@ -288,24 +247,10 @@ janus: @enum type = { } ``` -> Notes: -> -> "C enumerations constitute a curiously half-baked concept. ... the cleanest way out was to deem each enumeration a separate type." -> -> — Stroustrup (The Design and Evolution of C++, 11.7) -> -> "An enumeration is a distinct type ... with named constants" -> -> — ISO C++ Standard -> -> "An `enum`[...] is a totally ordered value type that stores a value of its enumerators's type, and otherwise has only public member variables of its enumerator's type, all of which are naturally scoped because they are members of a type." -> -> — P0707R4, section 3 - #### `flag_enum` -`flag_enum` is a variation on `enum` that has power-of-two default enumerator values, a default signed underlying type that is large enough to hold the values, and supports bitwise operators. +`flag_enum` is a variation on `enum` that has power-of-two default enumerator values, a default signed underlying type that is large enough to hold the values, and supports bitwise operators to combine and test values. `flag_enum` will emit a compile-time error if: @@ -336,16 +281,6 @@ file_attributes: @flag_enum type = { } ``` -> Notes: -> -> "`flag_enum` expresses an enumeration that stores values -> corresponding to bitwise-or'd enumerators. The enumerators must -> be powers of two, and are automatically generated [...] A none -> value is provided [...] Operators `|` and `&` are provided to -> combine and extract values." -> -> — P0707R4, section 3 - ### For dynamic types @@ -424,23 +359,6 @@ main: () = { } ``` -> Notes: -> -> "As with void*, programmers should know that unions [...] are -> inherently dangerous, should be avoided wherever possible, -> and should be handled with special care when actually needed." -> -> — Stroustrup (The Design and Evolution of C++, 14.3.4.1) -> -> "C++17 needs a type-safe `union`... The implications of the -> consensus `variant` design are well understood and have been -> explored over several LEWG discussions, over a thousand emails, -> a joint LEWG/EWG session, and not to mention 12 years of -> experience with Boost and other libraries." -> -> — Axel Naumann, in [P0088](https://wg21.link/p0088), -> the adopted proposal for C++17 `std::variant` - ### Helpers and utilities @@ -449,14 +367,9 @@ main: () = { A `cpp1_rule_of_zero` type is one that has no user-written copy/move/destructor functions, and for which Cpp2 should generate nothing so that the Cpp1 defaults for generated special member functions are accepted. -> Notes: -> -> C.20: If you can avoid defining default operations, do -> +> C.20: If you can avoid defining default operations, do. > Reason: It's the simplest and gives the cleanest semantics. -> > This is known as "the rule of zero". -> > — Stroustrup, Sutter, et al. (C++ Core Guidelines) diff --git a/mkdocs.yml b/mkdocs.yml index a181ee18a0..4f6f896b43 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -60,7 +60,7 @@ nav: - 'Declarations and aliases': cpp2/declarations.md - 'Objects, initialization, and memory': cpp2/objects.md - 'Functions, branches, and loops': cpp2/functions.md - - 'Contracts and assertions': cpp2/contracts.md + - 'Contracts': cpp2/contracts.md - 'Types and inheritance': cpp2/types.md - 'Metafunctions and reflection': cpp2/metafunctions.md - 'Namespaces': cpp2/namespaces.md From a3986e77e8ccafd2bb0511039e0940b4485db4cb Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sun, 10 Mar 2024 17:02:49 -0700 Subject: [PATCH 63/64] Remove modules documentation stub since that's not supported yet --- docs/cpp2/modules.md | 16 ---------------- mkdocs.yml | 2 +- 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 docs/cpp2/modules.md diff --git a/docs/cpp2/modules.md b/docs/cpp2/modules.md deleted file mode 100644 index 9d91848ca6..0000000000 --- a/docs/cpp2/modules.md +++ /dev/null @@ -1,16 +0,0 @@ - -# Modules - -## Overview - -TODO - -## `import` - -TODO - -## `export` - -TODO - - diff --git a/mkdocs.yml b/mkdocs.yml index 4f6f896b43..cac325c0c6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,7 +64,7 @@ nav: - 'Types and inheritance': cpp2/types.md - 'Metafunctions and reflection': cpp2/metafunctions.md - 'Namespaces': cpp2/namespaces.md - - 'Modules': cpp2/modules.md + # - 'Modules': cpp2/modules.md - 'Cppfront reference': - 'Using Cpp1 (today''s syntax) and Cpp2 in the same source file': cppfront/mixed.md - 'Cppfront command line options': cppfront/options.md From 0b40bcc52dcf878c0154991677def57eb2f84dc2 Mon Sep 17 00:00:00 2001 From: Herb Sutter Date: Sun, 10 Mar 2024 17:06:48 -0700 Subject: [PATCH 64/64] Fix comment typo --- docs/stylesheets/extra.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/stylesheets/extra.css b/docs/stylesheets/extra.css index 2cbf8168ea..16198feddb 100644 --- a/docs/stylesheets/extra.css +++ b/docs/stylesheets/extra.css @@ -9,7 +9,7 @@ td { font-size: 16px; } todo: try to make the nav pane section labels larger for now, this at least adds space between sections - to section starts are easier to see + so that section starts are easier to see */ .md-nav__item { font-size: 20pt; } .md-nav__link { font-size: medium; }