Herb Sutter Launches Cppfront to Eliminate C++ Memory Safety Flaws

Can C++ be 10x Simpler & Safer?  - Herb Sutter - CppCon 2022 — CppCon

CppCon

Sutter argues C++ can eliminate 98% of memory vulnerabilities and 90% of language complexity by adopting an unambiguous second syntax that transpiles directly to standard C++.

AI-assisted summary of Can C++ be 10x Simpler & Safer? - Herb Sutter - CppCon 2022 — original source. Check the original for full context.

Source published:

Key ideas

  • Herb Sutter introduces cppfront, an experimental pre-alpha compiler translating an unambiguous second syntax (Cpp2) directly into standard ISO C++20 (Cpp1) without breaking backward compatibility. (12:35)
  • The project tests Bjarne Stroustrup's long-standing conjecture that an internal language 10% the size of C++ can achieve 50x safety (eliminating up to 98% of CVE categories) and 10x simplicity. (15:00)
  • Prompted by White House executive orders and Commerce Department warnings declaring C and C++ memory-unsafe, Cpp2 statically enforces bounds, lifetime, and initialization safety by default. (40:00)
  • An internal audit of Windows security vulnerabilities by Herb Sutter and Joe Bialek revealed that 100% of the examined bounds violations would have been prevented by bounds-checked spans. (55:10)
  • Cpp2 introduces unified declarations (`name: type = value`), six explicit parameter modes, compiler-enforced definite initialization before use, and eliminates raw owning pointers and pointer arithmetic. (17:30)

Detailed notes

## Background This keynote address was published on YouTube by the CppCon channel on November 12, 2022. It features Herb Sutter discussing experimental avenues to evolve C++ beyond its traditional incremental 10% three-year improvements to make the language fundamentally simpler, safer, and more toolable.

## TL;DR Herb Sutter presents Cpp2 and its accompanying open-source compiler cppfront, an experimental alternate syntax designed to test Bjarne Stroustrup's conjecture that C++ can be 10x simpler and 50x safer. By compiling down to standard ISO C++20 without breaking backward compatibility, Cpp2 establishes a clean bubble that eliminates legacy syntactical warts, enforces definite initialization, and replaces raw owning pointers and pointer arithmetic with safe defaults. Sutter demonstrates how features like unified declarations, automatic bounds-checking contracts, pattern matching via inspect, and standardized parameter modes dramatically slash accidental complexity and memory CVEs while preserving zero-overhead execution and seamless C++ interoperability.

## Atmosphere & style The presentation carries the electrifying energy of an ambitious, high-stakes keynote delivered to a deeply knowledgeable engineering community. Sutter speaks with passionate conviction, blending candid self-deprecation about his prototype's early limitations with rigorous technical authority. Watching it feels like witnessing an inflection point—marked by earnest audience applause when safety bugs are systematically dismantled, flashes of dry humor regarding historical language pain points, and an inspiring call for community collaboration to revitalize C++ from within.

## Bulletin - [12:35](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=755s) Herb Sutter introduces cppfront, an experimental pre-alpha compiler translating an unambiguous second syntax (Cpp2) directly into standard ISO C++20 (Cpp1) without breaking backward compatibility. - [15:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=900s) The project tests Bjarne Stroustrup's long-standing conjecture that an internal language 10% the size of C++ can achieve 50x safety (eliminating up to 98% of CVE categories) and 10x simplicity. - [40:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2400s) Prompted by White House executive orders and Commerce Department warnings declaring C and C++ memory-unsafe, Cpp2 statically enforces bounds, lifetime, and initialization safety by default. - [55:10](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3310s) An internal audit of Windows security vulnerabilities by Herb Sutter and Joe Bialek revealed that 100% of the examined bounds violations would have been prevented by bounds-checked spans. - [17:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1050s) Cpp2 introduces unified declarations (`name: type = value`), six explicit parameter modes, compiler-enforced definite initialization before use, and eliminates raw owning pointers and pointer arithmetic.

## Key takeaways - [00:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=0s) Standard ISO C++ improves incrementally by roughly 10% every three years because strict 100% syntactic backward compatibility prevents fixing historical defaults and warts. - [10:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=600s) Following the precedent of Bjarne Stroustrup's original Cfront compiler, cppfront bypasses the cost of modifying production compiler forks by compiling Cpp2 into clean modern C++20. - [17:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1050s) Cpp2 provides an unambiguous, context-free grammar with order-independent declarations, left-to-right reading order, and automatic `import std;` module inclusion. - [20:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1200s) Universal Function Call Syntax (UFCS) in Cpp2 allows non-member functions to be called via member dot notation (such as `thing.ssize()`), vastly improving IDE autocompletion and toolability. - [30:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1800s) Cpp1 and Cpp2 code can be mixed seamlessly within the exact same source file, enabling an incremental, non-breaking adoption path comparable to TypeScript over JavaScript. - [40:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2400s) Federal cybersecurity mandates and vulnerability data demand that low-level systems languages eliminate lifetime, initialization, type, and bounds defects. - [46:15](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2775s) Type queries and casts across all types (variants, optionals, anys, and polymorphic pointers) are consolidated into two unified operators: `is` and `as`. - [52:26](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3146s) Pointer arithmetic, raw pointer indexing, and naked `delete` operations are prohibited, directing developers toward bounds-checked spans and arena-allocated smart pointers (`unique.new`). - [01:00:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3600s) Cppfront automatically injects subscript bounds safety assertions (`cpp2::assert_in_bounds`) across all containers exposing `.size()` without requiring standard container wrappers. - [01:04:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3885s) Contracts in Cpp2 are segregated into distinct global groups (`bounds`, `type`, `null`), allowing critical memory checks to remain active in production while toggling heavy business logic. - [01:07:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4065s) Pointers in Cpp2 are non-null by default, and the identifier `NULL` is banned outright from the language. - [01:16:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4590s) Definite first-use analysis tracks branch flow across functions to ensure local variables are initialized before access without paying the cost of zero-initialization dummy values. - [01:25:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5100s) Cpp2 formalizes six clear parameter passing intents (`in`, `copy`, `inout`, `move`, `forward`, and `out`), eliminating manual reference choice cognitive overhead. - [01:22:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4950s) Multiple named return values lower directly into synthesized structs that remain completely ABI-compatible with C++17 structured bindings. - [01:50:47](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6647s) The distinct name 'Cpp2' serves as an educational lever to prevent instructors from introducing obsolete C idioms and unsafe defaults to new programmers.

## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=0s) Doubling Down on C++: An Experimental Vision for 10x Simplicity and Safety

Herb Sutter opens by acknowledging that while his title sounds provocative, this is actually a talk he has been delivering in pieces for seven years. He sets an affectionate tone for the conference, noting that attendees gather at CppCon because they love C++ and view it as the best language available for building what they need. However, the language faces severe challenges, prompting an industry-wide wave of major C++ evolutionary and revolutionary experiments. Sutter emphasizes upfront that this project is his own personal experiment: he does not speak for Microsoft, nor does he speak for the ISO C++ Standards Committee. Because it is an experiment, failure remains the default outcome, but any successes are intended to be fed directly into future ISO C++ proposals.

Addressing the global community of seven million C++ developers, Sutter urges everyone to push the boundaries of C++ rather than jumping ship to alternate ecosystems. While acknowledging that greenfield language projects—and established languages like Rust—offer valuable lessons in building brand-new module systems and ecosystems, he focuses on the opposite end of the spectrum: refreshing C++ itself from within. His goal is to retain C++’s existing package managers, modules, guidance, and complete compatibility, while modernizing the baseline defaults. He grounds this ambition in measurable targets:

> “To me, safer, 50x safer means 98% fewer CVEs, vulnerability exploits, and bugs in those categories. 10x simpler means I can get rid of 90% of the paper of the books I teach today.”

Reflecting on a slide he first presented at CppCon 2018, Sutter argues that the heart of C++ has always been zero-overhead abstraction—knowing where objects live and how long operations take—paired with friction-free interoperability. He directly challenges the cultural masochism common in the community, pushing back against the idea that “if it doesn't hurt a little, it's not really C++.” Security vulnerabilities, memory unsafety, and complex syntax are not fundamental to C++’s identity. The reason ISO C++ only manages incremental ~10% improvements every three-year cycle, Sutter argues, is its strict adherence to 100% syntactic backward compatibility.

[04:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=285s) The "Judo Move": Applying Zero-Overhead to Backward Source Compatibility

To break through the limitation of incremental progress, Sutter proposes an intellectual and architectural maneuver he calls a "judo move": extending the language’s defining zero-overhead philosophy directly to backward source compatibility itself.

> “What if we applied that to backward source compatibility. That means we always have backwards 100% SFINAE macros, give us all of it. Backward source compatibility available. But, we only pay for it if we use it, is that possible?”

Sutter asks the audience to entertain a thought experiment supported by an active implementation: introducing an alternate, complete second syntax for C++. If this secondary syntax is fully expressive, self-contained, and unambiguous, it creates an isolated bubble of entirely new code. Because no legacy code has ever been written in this new syntax, the committee and developers would face exactly zero backward source compatibility constraints within that bubble. Designers can clean up historical warts, increase safety, simplify rules, and enhance tooling without breaking a single existing line of code. He compares this potential shift to the arrival of C++11, which introduced smart pointers and trailing return types; it felt like a brand-new language while still undeniably remaining C++.

```mermaid flowchart TD accTitle: The Zero-Overhead Source Compatibility Model accDescr: Diagram illustrating how legacy syntax retains full compatibility while a second syntax provides clean evolution without legacy constraints.

subgraph Legacy["Legacy Syntax Domain (cpp1)"] L1["100% Source Compatibility"] L2["Macros, SFINAE, Legacy Idioms"] L3["Existing C++ Ecosystem"] end

subgraph Alternate["Clean Bubble Domain (cpp2)"] A1["Zero Legacy Constraints"] A2["Modern Safe Defaults"] A3["Reduced Concept Count"] end

Legacy -->|"Friction-Free Interop"| Alternate Alternate -->|"Compiles to / Interops with"| Legacy ```

- **Zero-overhead backward compatibility**: Full access to historical features (macros, SFINAE) remains completely available, but code in the new syntax does not pay the architectural tax unless it uses them. - **Unconstrained evolution**: An unwritten syntax has no backward compatibility shackles, allowing a 30-year evolution runway for C++.

[06:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=405s) Refactoring the Concept Count and the Paper Trail of Proposals

Sutter details how this effort began in 2015, when he spent a year of his spare time investigating whether C++ could be systematically refactored. Rather than starting from a blank sheet of paper, he examined existing C++ constructs to identify disparate features that could be unified into a smaller number of composable, general-purpose mechanisms without dumbing down the language. The primary objective was reducing the overall "concept count"—a metric that encompasses not just the raw keyword count, but also the catalog of irregularities, special cases, and surprising interactions between disparate features that force developers into tedious rote learning.

Since 2016, Sutter has quietly validated parts of this unified architecture by isolating individual features and submitting them to the ISO committee as standalone proposals written in traditional C++ syntax. This allowed him to subject each design to intense committee scrutiny, discover bugs, and verify whether both the committee and the broader community actually wanted the problem solved.

Nearly every conference talk and ISO paper Sutter delivered over the previous six years originated directly from this overarching project. The single exception to his rule of always building a prototype first was the three-way comparison "spaceship operator" (`<=>`). Because the committee unexpectedly restarted discussions on comparisons right after C++17 shipped, Sutter took the spaceship design out of his back pocket and submitted it as an opportunistic paper without a working prototype. That lack of a prototype resulted in two major design bugs, which were subsequently fixed with the help of committee colleagues. Sutter halts his talk to ask the contributors pictured on his slide to stand, prompting a warm round of applause across the auditorium.

[10:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=600s) What Would Bjarne Do? Learning from Cfront

Herb Sutter reflects on his simplification efforts, pointing out that the spaceship operator (`<=>`) was the first piece of his simplification work to actually make it into the ISO C++ standard. He brags on it slightly because, while it added roughly a dozen pages of core language wording, applying it to the standard library eliminated even more pages, making the standard as a whole smaller. This serves as concrete proof that language simplification really can work.

However, a major obstacle has been the cost of prototyping. Prototyping experimental language features directly inside a production-grade compiler like Clang or Visual Studio, or via custom mini-libraries, is prohibitively slow and expensive. Faced with this bottleneck, Sutter asks himself: "What would Bjarne do?" Not modern Bjarne Stroustrup, he jokes, but the Bjarne of forty years ago (referencing a cardboard cutout slide). Forty years ago, Stroustrup solved the adoption and prototyping problem by writing Cfront—a compiler that translated C++ (initially "C with Classes") directly into C.

```mermaid flowchart LR accTitle: Cfront to cppfront Comparison accDescr: Historical analogy showing how Cfront translated early C++ to C, and how cppfront translates syntax 2 to standard C++20. subgraph Historic["1980s: Cfront Model"] direction LR A["C with Classes / C++"] -->|"Cfront"| B["C Code"] B -->|"C Compiler"| C["Every platform with a C compiler"] end subgraph Modern["Herb Sutter's Experiment: cppfront"] direction LR D["Syntax 2 (cpp2)"] -->|"cppfront"| E["Standard C++ (cpp)"] E -->|"C++20 Compiler"| F["Every platform with a C++20 compiler"] end ```

Stroustrup succeeded because he nailed a fifteen-second elevator pitch: C is powerful, fast, and close to the machine, but it lacks abstraction, so C++ would supply that abstraction while compiling down to C. That architectural choice gave C++ three decisive advantages: - It was immediately portable to any platform that had a C compiler. - It retained 100% interoperability with existing C libraries, linkers, debuggers, and optimizers. - Even if debuggers showed mangled symbol names for overloaded functions, the existing tooling still worked seamlessly.

Sutter emphasizes that without Cfront, modern C++ would not exist. He poses the core question of his experiment: “Could we do that again?”

---

[12:35](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=755s) Introducing `cppfront`: An Experimental C++20-Targeting Compiler

To test whether the Cfront approach could rejuvenate modern C++, Sutter created an open-source project called `cppfront` (hosted under his GitHub account, `hsutter`). It is a source-to-source compiler that translates an experimental syntax—which he calls "Syntax 2" or "cpp2"—into standard modern C++ ("Syntax 1" or "cpp").

```mermaid flowchart TD accTitle: cppfront Compilation Pipeline accDescr: Compilation pipeline showing how cpp2 files are parsed by cppfront into cpp files and then built with standard compilers. subgraph Authoring["Source Files"] S1["Pure.cpp2 file"] S2["Interleaved.cpp2 +.cpp file"] end subgraph Transformation["cppfront Tool"] CF["cppfront executable<br>(Handwritten manual parser, zero dependencies)"] end subgraph NativeToolchain["Target Compiler (C++20 Mode)"] CPP["Generated.cpp file"] COMP["GCC / Clang / MSVC"] BIN["Target Binary"] end

S1 --> CF S2 --> CF CF --> CPP CPP --> COMP COMP --> BIN ```

His goals mirror Stroustrup’s original ambitions: embrace C++ rather than fleeing to another language, but directly address the reality that C++ needs to become safer, simpler, and more toolable. Just as Cfront worked wherever a C compiler existed, `cppfront` works wherever an ISO C++20 compiler is available.

Sutter is careful to set expectations: this is an individual research project and is "hilariously incomplete." At the time of the talk, it does not yet support classes (which he plans to implement over the coming winter), though he notes that one can achieve a surprising amount of language simplification focusing purely on functions. His broader goal is to spark a conversation within the C++ community about what is possible for the language's evolution before developers abandon it for alternative languages.

The build process for `cppfront` is deliberately minimal: - It requires no external build systems—no CMake, no Bazel. - It contains no third-party libraries, no parser generators (no Yacc, no Bison). - The codebase was written from scratch starting from an empty editor using only the standard library and a custom, handwritten parser. - The entire build instruction for each major platform is a single command-line invocation compiling with a standard C++20 compiler.

---

[15:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=900s) Core Principles: Conceptual Integrity and Bjarne's 10% Conjecture

Sutter explains that the foundation of this work rests on principles he has reiterated in the opening sections of all his ISO committee papers over the prior seven years. Quoting Fred Brooks from *The Mythical Man-Month*, he notes that conceptual integrity is the single most critical attribute of system design: systems should hang together as if they were the product of a single mind.

For C++, this means refactoring the language into fewer, composable, orthogonal, and general features. Sutter connects this directly to Stroustrup's long-standing conjecture: “Bjarne has conjectured publicly for decades that within C++ there is a language 10% the size, struggling to get out.” Sutter’s explicit ambition is to see if he can finally prove Stroustrup right.

To keep the project grounded, he establishes strict constraints ("stakes in the ground"): - **No drive-by improvements:** Sutter eliminated several personal language preferences that failed to carry their weight or solve concrete, existing problems. - **Laser focus on safety, simplicity, and toolability:** Every addition must measurably improve at least one of these three axes. - **Zero-overhead and developer control:** The language must strictly retain fundamental C++ values—zero abstraction penalty and the ability to drop down and take low-level control when needed.

Sutter pleads with the audience not to misunderstand the project's intent: > “Please don't make the mistake of saying, ‘Oh, yeah, Herb gave a talk and he says he wants to make C++ prettier.’ That's not the point. That would not be interesting. It's fixing the semantics, changing defaults, removing special cases. That's the meat of it. The syntax is just to get us a bubble of new code.”

The syntax acts merely as the gateway into an evolutionary space where defaults can be safely fixed without breaking backward compatibility for billions of lines of existing code.

---

[17:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1050s) Syntax 2: Unified Left-to-Right Declarations and Modules by Default

Turning to the new syntax itself, Sutter establishes baseline requirements that any modern programming language should satisfy: - Context-free parsing - Order-independent declarations - Clean left-to-right syntax, quoting Niklaus Wirth's philosophy that declarations should read left to right "as nature intended" - Complementary syntax between declarations, usages, and expressions

Existing C++ code—from `class Shape` syntax dating back to 1980 to the trailing-return syntax `auto f(int i) -> std::string` introduced in C++11—is locked in place. Any breaking changes inside those braces would break the world.

To solve this, Syntax 2 unifies every single declaration under a consistent pattern: `name: type = value`.

| Declaration Type | Syntax 2 (cpp2) Formulation | Meaning / Notes | |:--- |:--- |:--- | | **Type** | `Shape: type = {... }` | Defines `Shape` as a new type with a body | | **Function** | `f: (i: int) -> std::string = {... }` | Defines `f` as a function taking `int`, returning `std::string` | | **Variable** | `vec: std::vector<std::string> = (...)` | Defines a vector of strings with initial values | | **Deduction (CTAD)** | `view: std::span = vec` | Deduces `std::span<std::string>` automatically via C++ semantics | | **Entry Point** | `main: () -> int = {... }` | Clean main function signature without extraneous boilerplate |

Sutter demonstrates this by presenting a complete, standalone program written in Syntax 2 on a single slide. The program compiles and executes without containing a single `#include` or `import` directive. In a pure Syntax 2 translation unit, the C++23 statement `import std;` is automatically injected by default. Developers no longer need to remember which specific standard library header to include.

[20:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1200s) Modules, Defaults, and Universal Function Call Syntax

Herb Sutter addresses concerns about build performance when adopting modern features like C++20 modules and bringing in the standard library. Some developers worry about the build speed of pulling in regex, parallelism, and every other standard header, but Sutter argues that these worries are misplaced with modular designs. In current implementations, writing `import std;` compiles faster than an old-fashioned `#include <iostream>`, making the entire standard library quicker to bring in than a trivial "Hello World" build. In his experimental syntax (Cpp2), `import std;` is simply made the default, though he notes that if the experiment shows otherwise, it can be adjusted.

```mermaid flowchart TD accTitle: Unified Function and Lambda Syntax in Cpp2 accDescr: Shows how single-expression functions, full functions, and lambdas share the identical declaration grammar. subgraph Grammar["One Syntax for Functions"] Full["Full Function:<br/>name: (params) -> return = { body }"] Expr["Single-Expression Function:<br/>name: (params) = expr<br/>(omits braces and return)"] Lambda["Lambda Function:<br/>:(params) = expr<br/>(omits name at expression scope)"] end Full -->|"omit boilerplate"| Expr Expr -->|"omit name"| Lambda ```

He then walks through key language features implemented in Cpp2. Parameter passing directly realizes the design from his earlier paper (P0708), using explicit passing intents like `in` and `out` while eliminating traditional raw references entirely from the surface syntax. Underscores (`_`) function as wildcards to express implicit template parameters cleanly. Crucially, Cpp2 adheres to the core philosophy that there should be only one unified syntax for everything, enriched by good defaults rather than competing duplicate constructs: - **Brace and Return Elision:** For single-expression or single-statement functions returning a value, curly braces and explicit `return` statements can be omitted entirely without introducing a separate grammar. - **Unified Lambda Grammar:** Because there is only one function declaration syntax, lambdas do not have a dedicated, distinct syntax; a lambda is simply an unnamed function written at expression scope, starting with `:(` rather than requiring specialized capture-bracket ceremonies. - **Order Independence:** Functions can call each other freely before they are formally declared or defined—eliminating the need for manual forward declarations, which drew enthusiastic applause from the audience.

Moving to generated code, Sutter shows a screenshot from CppFront rather than running a live demo for that specific slide to conserve time. The output illustrates that `[[nodiscard]]` is emitted by default on functions. He asks the audience how many wish `[[nodiscard]]` were the default in standard C++, drawing widespread agreement. Sutter quips, “When you write the compiler, you get to make choices,” highlighting that he wrote this compiler first and foremost for himself so that he could write C++ the way he wants.

```mermaid flowchart LR accTitle: Universal Function Call Syntax Fallback accDescr: How UFCS resolves member dot syntax to non-member functions like std::ssize. Call["thing.ssize()"] --> Check{"Member.ssize()<br/>exists?"} Check -->|"Yes"| MemCall["Call thing.ssize()"] Check -->|"No"| NonMemCall["Fallback to std::ssize(thing)"] ```

The emitted code also seamlessly handles Class Template Argument Deduction (CTAD) because the front-end directly emits standard C++ syntax (referred to as "syntax 1") from Cpp2 ("syntax 2"). Furthermore, Sutter highlights calling `.ssize()` on a container. When Bjarne Stroustrup failed to persuade the ISO committee to change all `.size()` members to return signed integers or to add `.ssize()` as a member across all containers, the committee instead provided the non-member `std::ssize` function. Because Cpp2 implements Universal Function Call Syntax (UFCS), invoking `thing.ssize()` simply falls back to the non-member overload automatically. Sutter reflects on how rapidly this wins developers over: “I was surprised as I was writing my dozens of examples and tests, I was surprised how quickly I just went to `.ssize()` and never looked back.”

[25:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1500s) Readable Generated Code and Pre-Alpha Compiler Demos

A central guarantee of Sutter's experiment is preserving developer investment. He insists that if the Cpp2 experiment ultimately fails—which is the statistical default for language experiments—developers must never feel trapped. If a project needs to abandon Cpp2, it can run the tool with the `-clean-cpp1` flag to strip the generated `#line` directives and keep the clean, human-readable ISO C++ output.

To achieve complete order independence within the generated code, CppFront automatically inspects the Cpp2 definitions and emits forward declarations in the exact relative locations needed among any existing C++1 code, followed by the actual Cpp2 definitions down below where they can mutually reference one another.

```mermaid flowchart TD accTitle: Order Independence Generation Strategy accDescr: Illustrates how CppFront splits definitions into forward declarations and implementations to guarantee order independence. Cpp2Src["Cpp2 Code (unordered)"] --> FrontEnd["CppFront Lowering"] FrontEnd --> FwdDecls["1. Emit Forward Declarations<br/>(interspersed with legacy C++1 code)"] FrontEnd --> Defs["2. Emit Function Definitions<br/>(all Cpp2 implementations can see each other)"] FwdDecls --> Output["Clean Standard C++1 Source"] Defs --> Output ```

Sutter exhibits a deliberately contrived straw-man test case featuring variables named `a`, `b`, `c`, `d`, `e`, `f`, `g`, `h`, `i`, `j`, `k`, and `l` to prove how cleanly the transpiler preserves code structure and formatting: - Because the declaration syntax follows use, operators such as pointer dereferences become postfix in Cpp2, which removes the need for many nested parentheses. The compiler rewrites expressions into conventional prefix syntax (such as turning a dereference expression into `*f`) and correctly places parentheses and postfix increments (`++`). - Even though expressions and UFCS calls get moved or restructured, the emitted output retains the author's original indentation, indentation style, and exact comment locations. - He refutes the common assumption that transpiled or generated code is unavoidably messy: “The ugliness of generated code is directly proportional to the impedance mismatch. If your source is still C++, then you can make it beautiful.”

Sutter then transitions to live demos of his "pre-alpha" tool across real toolchains. He displays a mixed source file featuring traditional includes (`#include <iostream>`, `#include <string>`) and a standard `auto main()` function living alongside Cpp2 functions. Asking the audience to vote by applause on whether to test the compiler under Clang, Microsoft Visual C++ (MSVC), or GCC, GCC wins the loudest applause. Sutter shifts to his Ubuntu environment running inside the Windows Subsystem for Linux (WSL), remarking, “Thank you, Windows, for having actual Linux.” However, the terminal fails to register any keystrokes. After attempting an Ubuntu restart and finding it completely unresponsive, he jokes about the inevitable live-demo curse—“Wow, this has never happened before”—and pivots toward the audience's second-place compiler choice.

[30:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1800s) Mixing Cpp1 and Cpp2: The Tooling and Code Generation Pipeline

Herb Sutter begins a live demonstration of `cppfront` parsing `demo.cpp2`. After battling a small OS typing lag that he jokingly notes has never happened in months of testing, he runs the compiler. The tool immediately flags an error, which Sutter explains is completely intentional for the demonstration. The initial translation step passes its safety checks over the mixed syntax, but the downstream C++ compiler halts on an error traced directly back to the original `.cpp2` file: `'string' is not a member of 'std'`. Sutter explains that he deliberately wrote `strim` instead of `string` to demonstrate how error reporting functions across compiler boundaries:

> "So all the cool error messages we get today still work."

Fixing the typo allows the program to compile and link. Sutter remarks on the slight delay during the initial executable launch—attributing it to an unavoidable Windows virus scan on a fresh binary—before it runs swiftly on the second attempt. Inspecting the generated C++ source file reveals how `cppfront` bridges syntactic eras. At the top sits a generated header supplying necessary utility templates, such as parameter-passing conventions. Next, `cppfront` emits any traditional C++ (Cpp1) code exactly in sequence. Crucially, Cpp1 relies strictly on declaration order, so whenever `cppfront` encounters a definition in the new Cpp2 syntax, it hovers forward declarations at that precise location so earlier Cpp1 functions like `main` can see them. Finally, the Cpp2 implementations are emitted with proper braces and signatures appended at the bottom of the generated file. This mechanism allows developers to seamlessly interleave both syntaxes function-by-function within the very same source file.

```mermaid flowchart TD accTitle: Cppfront Compilation and Code Generation Pipeline accDescr: Flow diagram showing how cppfront parses mixed Cpp1 and Cpp2 source and produces standard Cpp1 output.

subgraph Input ["Source Input"] SRC["demo.cpp2 (Mixed Syntax)"] end

subgraph Front ["cppfront Lowering"] PARSE["Parse Mixed Syntax"] FWD["Emit Forward Declarations (for Cpp2)"] CPP1["Emit Cpp1 Blocks In-Order"] CPP2["Emit Lowered Cpp2 Bodies"] end

subgraph Output ["Target Artifacts"] GEN["Generated.cpp File"] COMP["Standard C++ Compiler (GCC / Clang / MSVC)"] end

SRC --> PARSE PARSE --> FWD PARSE --> CPP1 PARSE --> CPP2 FWD --> GEN CPP1 --> GEN CPP2 --> GEN GEN --> COMP ```

[32:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1965s) Pure Cpp2 Files and Named Memory Arenas

Sutter transitions to showing what happens when using the `-pure` compiler switch to enforce a modern-only Cpp2 file. Invoking `-pure` on a file containing legacy directives causes `cppfront` to issue an immediate diagnostic prohibiting the preprocessor altogether:

> "First thing it says is, no, no, no, CPP2, you don't get no preprocessor, you don't need no preprocessor."

Instead of `#include`, developers are instructed to use modern C++20 module semantics (`import std;`). Sutter highlights an underlying design principle: the compiler should enforce modern best practices by default, mirroring what educators already teach rather than perpetuating legacy baggage. Demonstrating this clean syntax, he rewrites `main` into Cpp2 form (`main: () = {... }`), moves it arbitrarily to the top of the file to illustrate complete order independence, and removes all manual includes because the standard library is implicitly accessible.

Moving to memory management, Sutter demonstrates heap allocation. While acknowledging that allocating a local string on the free store is bad practice, he uses it to show how dynamic memory operates without legacy raw pointers. In Cpp2, allocation occurs entirely through named memory arenas, which are objects exposing a templated `.new()` member. The runtime supplies two standard arenas out of the box: - `unique.new()` (which returns a `std::unique_ptr`) - `shared.new()` (which returns a `std::shared_ptr`)

If a developer writes an unqualified heap allocation expression without naming an arena, the compiler defaults to invoking `unique.new()`. Consequently, allocating dynamic memory creates a `std::unique_ptr<std::string>` automatically, cementing type-safe and memory-safe semantics by making `unique_ptr` the default pointer type.

[35:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2100s) UFCS, Toolability, and the Adoption Ramp

Extending the design to older APIs, Sutter demonstrates how Cpp2 interfaces with the C standard library. He shows a snippet opening a file and using `fprintf` alongside a C++ string's `.c_str()` to write the name of his dog, Lily—an Italian Greyhound—to a file named `dog`. When examining the code, he prompts the audience to catch a subtle detail: `fprintf` is being invoked directly on the file pointer using member-call syntax (`f.fprintf(...)`).

Sutter insists that Uniform Function Call Syntax (UFCS) is far more than convenient syntactic sugar:

> "It's about being more toolable, because if you call using the non-member function call syntax, IntelliSense can't help you."

Starting an expression with an in-scope object followed by a dot allows autocomplete and IntelliSense engines to discover every valid operation for that type. Blank lines and prefix functions provide no contextual anchors for developer tooling.

Reflecting on language evolution, Sutter connects this architecture to the philosophy of "don't pay for what you don't use," applying it directly to backward compatibility. He compares this gradual adoption ramp to the historic successes of Bjarne Stroustrup's original C++ design and Anders Hejlsberg's TypeScript: - **Bjarne Stroustrup's C to C++ transition:** Developers could take any `.c` file, rename it to `.cpp`, retain backwards compatibility, and introduce a single C++ class to gain immediate encapsulation benefits. - **TypeScript's JavaScript transition:** Renaming `.js` to `.ts` lets developers introduce type annotations incrementally on a single line. - **Cppfront's Cpp1 to Cpp2 transition:** Renaming `.cpp` to `.cpp2` retains 100% source compatibility with existing SFINAE tricks and preprocessor macros, completely bypassing the catastrophic breaking migration seen between Python 2 and Python 3.

```mermaid flowchart LR accTitle: Incremental Adoption Models Compared accDescr: Visual comparison showing how C++, TypeScript, and Cpp2 all share a file-renaming adoption model.

subgraph Legacy ["Legacy Baseline"] C[".c File"] JS[".js File"] CPP[".cpp File"] end

subgraph Modern ["Strict Supersets / Dialects"] CPPOUT[".cpp (Bjarne's C++)"] TSOUT[".ts (TypeScript)"] CPP2OUT[".cpp2 (Pure or Mixed)"] end

C -->|"Rename & add C++ feature"| CPPOUT JS -->|"Rename & add type annotation"| TSOUT CPP -->|"Rename & add Cpp2 syntax"| CPP2OUT ```

[38:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2280s) Targeting Safety: The Persistent "Hits" of Vulnerabilities

Sutter underscores that working entirely within a pure Cpp2 source file offers the prospect of writing in a dialect that is an order of magnitude simpler, safer, and safe by construction, while preserving seamless interoperability with legacy C++ libraries via imports. He expresses his pedagogical motivation:

> "And I can stop teaching 90% of what I personally have been teaching people for the last quarter century of my life. Because I want to teach people how to program, not how to encant a C++ detail."

Directing attention to software security, Sutter establishes his primary targets: type safety, bounds safety, lifetime safety, and initialization safety. Crediting Dave Abrahams for pointing him toward recent data, he references the MITRE 2022 CWE Top 25 Most Dangerous Software Weaknesses list. Sutter highlights that industry vulnerabilities remain dominated by the same familiar flaws: out-of-bounds writes, out-of-bounds reads, use-after-free conditions, null pointer dereferences, and integer overflows. These issues map directly onto gaps in bounds, lifetime, initialization, and type discipline that modern language evolution must eliminate by default.

[40:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2400s) The Safety Imperative: Executive Orders and Government Guidance

Herb Sutter steps away from purely theoretical discussions of vulnerability lists to focus on what programming languages can realistically solve. Looking at industry rankings of the top 13 software vulnerabilities, once you set aside issues outside a compiler’s reach—such as SQL injection or failure to sanitize input data—five of the top 13 remain squarely within the domain of language design. Year after year, the culprits remain the same four pillars: lifetime safety, initialization safety, type safety, and bounds safety. Integer overflow primarily matters in practice because overflowing integers become out-of-bounds indices, making it fundamentally a bounds safety problem.

Turning off his slides to speak candidly with the audience, Sutter addresses the dramatic shift in political and regulatory scrutiny surrounding memory safety since early 2021. He asks how many attendees, particularly those at US-based companies, have had their daily work altered by the United States executive order on cybersecurity. As hands go up across the room, Sutter recounts the catalyst: on May 8, 2021, President Joe Biden issued an executive order following a barrage of high-profile cyberattacks and ransomware incidents that shut down hospitals, crippled election infrastructure, and took critical oil pipelines offline. Cybersecurity ceased to be a niche engineering concern and became an urgent matter of state.

```mermaid flowchart TD accTitle: Government Response to Memory Unsafe Languages accDescr: Flow showing the escalation from major cyber incidents to government directives advising against C and C++ usage. subgraph Incidents["Catalysts"] A["Ransomware & Infrastructure Attacks<br/>(Hospitals, Pipelines, Elections)"] end subgraph Mandate["Executive Action"] B["May 2021: Executive Order on Cybersecurity"] C["60-Day Mandate to Dept. of Commerce & NIST"] end subgraph Guidance["Official Policy Shift"] D["July 2021 Guidance:<br/>'C and C++ are not memory safe'"] E["Recommendation:<br/>'Where possible, do not use memory unsafe languages'"] F["Active Funding to Rewrite<br/>Low-Level Infrastructure in Other Languages"] end A --> B B --> C C --> D D --> E E --> F ```

The executive order tasked the Department of Commerce and NIST with issuing recommendations within 60 days on how to secure software supply chains. By July 2021, the Department of Commerce released its official guidance. Sutter stresses the severity of their specific wording, highlighting two direct statements:

> “Some programming languages, such as C and C++, are not memory safe.”

Two sentences later, the guidance explicitly recommended:

> “Where possible, do not use memory unsafe languages.”

Sutter emphasizes that this is the first time a major government has formally advised industry against using C and C++, effectively translating to an edict to avoid them wherever possible. Governments are now actively backing that guidance with public funding to rewrite critical, low-level infrastructure in alternative languages. Sutter warns the audience against the complacency that has lingered since the 1990s and 2000s, where the community assumed that adding smart pointers and static analysis rules would be enough to skate by:

> “Call me paranoid, but when a government department starts telling you to do work and move to other languages, we should start to listen.”

- The May 8, 2021 US Executive Order on Cybersecurity responded directly to catastrophic ransomware disruptions, such as attacks on election systems, healthcare facilities, and energy pipelines. - The resulting Department of Commerce guidance from July 2021 explicitly singled out C and C++ as memory-unsafe and advised migrating away from unsafe languages where possible. - Software vulnerabilities are an active threat across all domains, especially internet-connected appliances and embedded systems that rarely receive security patches.

---

[44:31](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2671s) CPP2's Approach: Enforcing Best Practices by Default

Turning back to the mechanics of C++, Sutter frames the design philosophy of CPP2 (and the prototype compiler cppfront) around proactive protection. The guiding principle is static enforcement by default and safety by construction. Where dynamic checks are unavoidable, they must remain completely transparent and adhere to C++'s foundational "pay-for-what-you-use" model.

To illustrate safety by construction, Sutter references the earlier syntax demonstration where writing `s:= new string` automatically yields a `std::unique_ptr` rather than a raw pointer, making it structurally impossible to leak memory. Rather than inventing brand-new paradigms, cppfront enforces the rules already established in the C++ Core Guidelines. When code violates a guideline, the compiler issues diagnostics that steer the programmer directly toward modern Standard C++ alternatives. For example, if a developer attempts to declare a raw C-style `union`, cppfront rejects it with the explicit error message: `"don't use union, use std::variant instead."` By transforming existing community teachings and guidelines into hard defaults, CPP2 aims to deliver genuine safety while remaining unmistakably C++.

- Safety by construction ensures that dangerous constructs, like raw owning pointers from `new`, are transformed into safe abstractions like `std::unique_ptr` at the syntax level. - Compiler errors in cppfront are modeled after established C++ Core Guidelines, actively teaching modern idioms rather than silently permitting unsafe legacy practices. - Dynamic checks remain strictly visible and opt-in, preserving zero-overhead principles.

---

[46:15](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2775s) Unifying Type Safety and Generic Code with `is` and `as`

Sutter transitions to type safety by reviewing the C++ Core Guidelines type profile. In current C++, querying and casting types requires an array of disparate mechanisms: `dynamic_cast`, `static_cast`, `std::holds_alternative`, `std::get_if`, `std::any_cast`, and custom pointer checks. Revisiting his CppCon 2021 presentation on pattern matching, Sutter demonstrates how CPP2 unifies all type queries under the single keyword `is`, and all type casts under the single keyword `as`.

```mermaid flowchart LR accTitle: Unification of Type Queries and Casts in CPP2 accDescr: Divergent legacy C++ type query and cast spellings consolidated into the unified is and as keywords in CPP2. subgraph Legacy["Standard C++ (Divergent)"] direction TB Q1["std::holds_alternative<T>"] Q2["dynamic_cast<T*>!= nullptr"] Q3["typeid(x) == typeid(T)"] C1["std::get<T> / any_cast<T>"] C2["static_cast<T> / dynamic_cast<T&>"] C3["reinterpret_cast<T>"] end subgraph CPP2["CPP2 Unified Syntax"] direction TB UQ["x is T<br/>(Universal Type Query)"] UC["x as T<br/>(Universal Type Cast)"] end Q1 --> UQ Q2 --> UQ Q3 --> UQ C1 --> UC C2 --> UC C3 --> UC ```

This unification is essential for generic programming. Because disparate types in current C++ require wildly divergent syntaxes to inspect or extract their values, writing generic functions that operate seamlessly over built-ins, standard vocabulary types, and user types is exceedingly difficult. Sutter reminds the audience of the "big dragon" graphic from his 2021 talk, which emphasized that divergent syntaxes destroy genericity.

To demonstrate, Sutter walks through a live generic test function written in CPP2 that uses an `inspect` expression:

```cpp test: (x) -> void = { std::cout << typeid(x).name() << ": "; std::cout << inspect x -> std::string { is int = std::to_string(x as int); is std::string = x as std::string; is _ = "not an int or a string"; } << "\n"; } ```

Although full pattern matching with destructuring is not yet implemented in cppfront, this basic `inspect` syntax is fully functional today. Sutter shows that the single generic function can be invoked on diverse arguments: 1. A raw literal double (`3.14`), which matches the wildcard `_` and prints `"not an int or a string"`. 2. A `std::variant<int, double>` initialized to hold a `double`, which prints `"not an int or a string"`. 3. A `std::any` initialized with the string `"xyzzy"`, which matches `std::string` and prints `"xyzzy"`. 4. A `std::optional<int>` that is initially empty, which fails the `is int` check and falls through to `"not an int or a string"`.

When an `int` value is subsequently assigned to each of those container variables, re-running `test` on the variant, the any, and the optional cleanly inspects each one, identifies that it now holds an integer via `is int`, and converts it using `x as int`. Sutter highlights that all of this relies on existing Standard C++ dynamic typing facilities, but harmonized under a clean, unified syntax that makes generic code straightforward to write and read.

- CPP2 consolidates divergent cast syntaxes (`static_cast`, `dynamic_cast`, `any_cast`, `std::get`) into `as`, and type queries into `is`. - The `inspect` expression allows clean, functional-style matching against types, returning values directly from match arms. - Universal type querying enables a single template/generic function to inspect built-ins, `std::variant`, `std::any`, and `std::optional` without specialized boilerplate.

[50:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3000s) Lowering Pattern Matching and Rejecting Unions

Herb Sutter runs into unexpected technical friction when his Ubuntu WSL window quietly crashes and refuses to reopen, forcing him to abandon his planned GCC demonstration for the moment and joke that an unexpected update must have caused Ubuntu to give up. Moving forward on Windows, he invokes the compiler with `cppfront -p` (pure mode) so the code compiles in a standalone environment without importing or including standard headers. Inspecting the generated C++1 code, Herb highlights how Cpp2 dynamic typing lowers directly to standard library mechanisms, with `as` becoming a call to `cpp2::as`. When inspecting a generic function, the high-level `inspect` pattern match lowers into an immediately invoked lambda expression (IIFE). Pointing to the tiny horizontal scroll bar on screen, Herb presses Alt-Z to wrap the lines and reveals a massive, sprawling cascade of nested `if constexpr` branches generated under the hood. He emphasizes that while the generated C++1 plumbing is complex, the author only needs to interact with the clean, high-level Cpp2 syntax. Pivoting back to type safety, Herb tests what happens when a developer attempts to declare a raw C-style `union`: the compiler immediately halts with an explicit error urging the developer to use `std::variant` instead.

```mermaid flowchart TD accTitle: Cpp2 Lowering and Diagnostics Pipeline accDescr: High-level Cpp2 constructs lowering into verbose C++1 code or triggering modern safety diagnostics.

subgraph Input["Cpp2 Source Code"] A["inspect (pattern match)"] B["union declaration"] C["first++ (pointer math)"] end

subgraph CppFront["CppFront Processing"] D["Lower to IIFE with if constexpr"] E["Diagnostic: Reject raw union"] F["Diagnostic: Reject pointer arithmetic"] end

subgraph Output["Output & Guidance"] G["Emitted C++1 Standard Code"] H["Guidance: 'Use std::variant'"] I["Guidance: 'Use std::span / gsl::span'"] end

A --> D --> G B --> E --> H C --> F --> I ```

- In pure mode (`-p`), Cpp2 processes standalone code without injecting implicit imports or standard includes. - Pattern matching statements (`inspect`) are emitted into C++1 as immediately invoked lambdas containing nested `if constexpr` cascades. - Raw C-style unions are rejected outright by the compiler front-end, enforcing type-safe variants by default.

> “That's the code I want to write and let the rest be an implementation detail.” — Herb Sutter

---

[52:26](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3146s) Bounds Safety: Banning Pointer Arithmetic and Bitwise Operators

Herb shifts the focus to the Bounds Safety Profile defined in the C++ Core Guidelines. The core guidelines have long taught that a raw pointer must represent an address to exactly one single object rather than an unbounded array, and that developers should never perform pointer arithmetic. ISO C++ cannot simply ban pointer arithmetic because doing so would instantly break decades of existing C and C++ software. However, because Cpp2 (Syntax 2) is a clean slate without legacy code, Sutter simply makes the Core Guidelines rule the default by strictly forbidding pointer arithmetic, pointer indexing via brackets, and bizarre legacy constructs like bitwise complement operators on raw pointers. Demonstrating a loop iterating across a vector of strings (`words`), Herb shows raw pointers capturing the addresses of elements (`words.front&` and `words.back&`, noting that unary address-of is a suffix operator in Cpp2). When he attempts to increment the pointer in an idiomatic C-style loop (`first++`), the compiler immediately halts. Herb acknowledges that experienced programmers feel comfortable writing pointer increment loops because it worked for Kernighan and Ritchie, but warns that even a 1% failure rate is catastrophic.

- The C++ Core Guidelines rule that raw pointers point to single objects cannot be retrofitted into C++1 without breaking the world's C and C++ codebase. - In Cpp2, unary operators like address-of (`&`) are placed as suffixes rather than prefixes. - Banning pointer arithmetic also extends to rejecting bitwise operations (such as bitwise complement `~`) directly on raw pointer values.

> “Because if you get it wrong, your program compiles no warnings and appears to run.” — Herb Sutter

---

[55:10](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3310s) Tooling Guidance, Banning Owning Pointers, and the Windows Vulnerability Study

When CppFront encounters forbidden pointer operations, its diagnostic messages direct the developer to safe, modern idioms: incrementing a pointer triggers a diagnostic recommending `std::span` or `gsl::span`, while bitwise operations suggest using `std::bit_cast`. Sutter explains that the only non-standard elements his compiler ever recommends are `gsl::span` and `gsl::narrow`, solely because ISO C++ failed to mandate runtime bounds checking on `std::span`. Similarly, attempting to use raw `delete` produces a fatal error explaining that owning raw pointers do not exist in Cpp2, directing the user toward `unique.new`, `shared.new`, or eventually `gc.new`. Rewriting the iteration using `std::span` (leveraging Class Template Argument Deduction, CTAD) and utilizing Cpp2's universal `next` clause on loops, the code compiles cleanly and safely. To underline the real-world impact of bounds checking, Herb recounts spending a day with Microsoft security expert Joe Bialek reviewing a full year's worth of vulnerability reports across internal Windows source code. In that extensive historical dataset, every single bounds vulnerability would have been completely mitigated had the code been written using a bounds-checked span.

- CppFront diagnostics intentionally point developers to standard library mechanisms or Core Guidelines constructs (`gsl::span`, `gsl::narrow`, `std::bit_cast`). - Raw `delete` and owning raw pointers are eliminated from Cpp2; memory management requires smart factory helpers such as `unique.new` and `shared.new`. - All Cpp2 loop constructs (`for`, `do-while`, etc.) are refactored to support a uniform trailing `next` step expression. - A comprehensive audit of one year of Windows security vulnerabilities conducted by Sutter and Joe Bialek revealed that 100% of the bounds safety vulnerabilities examined would have been prevented by a bounds-checked span.

> “In that particular data set, Joe and I found every single one would have been caught if you'd just been using a bounds checked span.” — Herb Sutter

[01:00:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3600s) Automatic Subscript Bounds Checking Without Wrapping

Herb Sutter reflects on an awkward workaround often taught to developers: creating an artificial, extra `std::span` variable just to iterate over or index elements safely so static analysis tools stay satisfied. He admits that teaching people to introduce a redundant variable just to achieve safety felt a bit hokey, even if safety is well worth the trouble. That observation led him to question why the original container's subscript operator couldn't simply be bounds-checked on its own. Because one of his core design pillars for Cpp2 is seamless interoperability with C++—requiring direct calls with no thunking, no marshalling, and zero wrapping of the standard library—modifying or wrapping types like `std::vector` was completely out of the question. Herb realized that because he controls the compiler, he could solve the problem directly in code generation: “So I'm certainly not going to do anything crazy like wrap the standard library or wrap `std::vector`. But then I thought, I have a compiler. I think I know how to do this.”

```mermaid flowchart TD accTitle: Subscript Bounds Checking Flow in Cppfront accDescr: Diagram showing how cppfront intercepts indexing expressions and routes them through contract bounds checks.

subgraph Source["Cpp2 Source Code"] A["Container Subscript Expression<br/><code>words[3]</code>"] end

subgraph Compiler["Cppfront Lowering"] B{"Is argument integral<br/>AND does target have.size()?"} C["Lower directly to unchecked subscript<br/><code>words[3]</code>"] D["Wrap with bounds assert<br/><code>cpp2::assert_in_bounds(words, 3)</code>"] end

subgraph Runtime["Runtime Contracts Execution"] E["<code>cpp2::bounds.expects(index < size)</code>"] F{"Out of bounds?"} G["Access Container Element"] H["Default: Print violation & terminate<br/>OR Custom Handler via <code>set_handler</code>"] end

A --> B B -- No --> C B -- Yes --> D D --> E E --> F F -- No --> G F -- Yes --> H ```

To demonstrate, Herb writes a simple example containing a classic off-by-one bug: attempting to print the "third" word of a three-word `std::vector<std::string>` by indexing at subscript `3`. As hands slowly go up in the audience, he jokes, “Good. You scared me at first. Of course, I kind of took you by surprise. Yeah, I know. We still all do this. This is not the third word. It's the fourth word. Out of three.” Running the compiled binary initially results in an unchecked out-of-bounds access that happens not to crash, illustrating how silent undefined behavior easily masks bugs in ordinary C++.

- Herb emphasizes that true interoperability means direct execution against ISO Standard C++ types without foreign-function interface (FFI) wrappers. - The standard subscript operation on uninspected standard containers regularly lets memory bugs pass undetected unless explicit checking or tooling intervenes.

---

[01:03:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3780s) Source Location Diagnostics and Generated Assertions

Herb introduces cppfront's subscript bounds safety feature via compiler switches. Compiling the exact same buggy code with the `-s` switch (enable subscript bound safety contract checks) immediately changes the outcome: running the executable traps the bug and prints a clear message: `Bound safety violation, out-of-bounds access attempt detected`. Taking it a step further, combining `-s` with the `-a` switch (add source info)—which Herb jokes was an accidental pun on "public service announcement" (PSA)—injects exact source diagnostics. Running `demo.exe` with `-s -a` pinpoints the fault directly in the Cpp2 source file: `demo.cpp2: Line 5 in function main, bound safety violation`.

Herb explains that cppfront achieves this without altering standard library implementations. When translating Cpp2 syntax to standard C++, the compiler inspects every subscript expression where the index has an integral type and the target container exposes a `.size()` member function. If both conditions match, cppfront wraps the subscript invocation in a helper called `cpp2::assert_in_bounds`. Because it relies solely on the `.size()` convention, this mechanism works uniformly across `std::vector`, `std::array`, `std::string`, `std::span`, and user-defined containers. Beneath the surface, `cpp2::assert_in_bounds` delegates its check to a contract expression structured as `bounds.expects(condition, message)`.

- The `-s` compiler flag enables runtime bounds safety contract checks on indexed containers. - The `-a` compiler flag decorates checks with line numbers and file names (utilizing standard library source location support where available, such as MSVC). - Container checking works polymorphically across any type providing an integral subscript operator and a `.size()` method.

---

[01:04:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3885s) Granular Contract Groups and Custom Error Handlers

Expanding on the design of runtime verification, Herb describes how contract handling is organized inside cppfront. Aligning with ongoing design efforts in the ISO C++ Contracts study group (SG21), Herb implemented pre- and post-conditions alongside assertions. In cppfront, contracts are partitioned into distinct, named contract group instances rather than handled as an all-or-nothing switch. Concrete built-in group objects include `bounds`, `type`, and `null`. Each group exposes an `.expects(...)` method and maintains its own customizable failure handler.

Herb highlights the engineering reality that motivated this design, crediting discussions with Joe Bialik and feedback from major product teams such as Windows: “One of the reasons is as we talk to different customers, including Windows, but also including others, one of the requirements is that certain errors like bound errors have to be checked all the time. Like there is no turning those off. But contracts can be used for other things. They can be used for business logic, and they don't want those contracts turned on all the time.” By representing contract groups as distinct global objects of type `contract_group`, applications can selectively enforce strict, non-negotiable checks (like memory and bounds safety) in release builds while disabling expensive business-logic contracts.

To illustrate handling custom errors, Herb demonstrates how an application can install its own diagnostic callback using `cpp2::bounds.set_handler(...)`, analogous to `std::set_terminate`. He writes a custom hook function, `call_my_framework(const char* message)`, and registers its address in `main`. When an out-of-bounds indexing occurs, control transfers directly to the custom handler rather than triggering an unceremonious crash to the console, allowing enterprise logging frameworks to intercept security-critical violations gracefully.

- Contract categories are represented as first-class, named C++ objects (`bounds`, `type`, `null`), enabling separate toggle states and severity policies. - Custom handlers can be plugged in per group via `set_handler`, avoiding mandatory program termination if telemetry or structured crash-reporting is required.

---

[01:07:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4065s) Re-evaluating Null: Enforcing Non-Null Pointers in Cpp2

Herb transitions from bounds safety to lifetime and memory safety. While the C++ Core Guidelines define a comprehensive lifetime profile based on static analysis, Herb has not yet implemented that full engine in cppfront. Instead, he chose to run a targeted "experiment within an experiment" centered on null pointers. He questions whether general-purpose code genuinely requires null pointers at all, asking what programming would feel like if pointers were inherently non-null by default in Cpp2. Because Cpp2 interacts directly with Cpp1 code, null pointers entering from legacy APIs must still be handled, but within pure Cpp2, Herb seeks to eliminate optional nullability from raw pointers.

He laments how historical defaults in C and C++ set programmers up for failure: “In fact, null is the worst value for a pointer, and it's the default value. Life is just so hard sometimes.” He references Kate Gregory’s celebrated talk, *"What You Say When You Say Nothing at All,"* noting that omitting an explicit initializer on a pointer either leaves it completely uninitialized (if it is a local variable) or zero-initialized to `nullptr` (if it is static/global)—neither of which provides a valid, dereferenceable target. To break this cycle, Herb demonstrates what happens in Cpp2 when attempting to create a pointer variable and assign it `nullptr` or zero: the compiler rejects the declaration with an explicit compilation error: `Pointer cannot be initialized to null or int`.

- The Cpp2 pointer model fundamentally disallows assigning `nullptr` or integral `0` during pointer initialization. - By banishing null as the default pointer state, Cpp2 aims to eradicate null-dereference bugs at compile time while reserving nullable semantics for dedicated optional/nullable wrapper types.

[01:10:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4200s) Banning Null and Enforcing Initialization Before Use

Herb Sutter demonstrates what happens when a programmer tries to cheat the compiler into accepting a null pointer. Attempting to assign `nullptr`, `0`, or even `42` fails immediately. Furthermore, trying to write the old C-style macro `NULL` triggers a unique diagnostic. As Sutter explains to the laughing audience: “You can't even say null anywhere in CPP2. That's not because it's pointer initialization. That's because it's a bad word.” In Cpp2, local variables are uninitialized by default if declared without an explicit assignment, but the compiler strictly guarantees initialization before use. Merely declaring a variable allocates space and defers construction until a value is supplied.

When Sutter writes code that declares an uninitialized pointer `p` to a string and immediately attempts to dereference it, the Cppfront compiler halts compilation with a hard error: `Local variable p is used before it was initialized. Line 14:25, program violates initialization safety guarantee.` To demonstrate branch analysis, he wraps the initialization inside an `if (std::rand() % 2)` coin-flip block, leaving the path uninitialized whenever the condition evaluates to false. The compiler flags the uninitialized use down the missing path. Sutter asks the room for a round of applause if they would like their current C++ compilers to provide diagnostics like that today. When the room claps, he quips: “The reason I asked for applause was not ego, but so that I can see your hands, is so that the compiler implementers watching this video can hear you.” Hearing Dietmar Kühl heckle from the front row, Sutter jokes that he accidentally stole Dietmar's line. Once he adds the balancing `else` branch—initializing `p` to the first word on one branch and the last word on the other—the code passes the safety checks cleanly and executes without error.

- Assigning `nullptr`, integer literals (`0`, `42`), or the literal keyword/macro `NULL` is prohibited for pointers in Cpp2. - Uninitialized local variables allocate storage without executing default constructors until explicit initialization occurs. - The Cppfront compiler tracks control flow to verify that variables are definitely initialized along all paths before any dereference or read.

```mermaid flowchart TD accTitle: Initialization Safety Branch Checking accDescr: Flowchart showing how Cppfront analyzes conditional branches to ensure a variable is initialized on all execution paths.

subgraph BranchCheck ["Branch Analysis: if (rand() % 2)"] Cond{"Condition Check"} ThenPath["'then' branch:<br/>p = &first_word"] ElsePath["'else' branch:<br/>Missing initialization"] end

Cond -->|"true"| ThenPath Cond -->|"false"| ElsePath ThenPath --> MergeNode{"Merge Point"} ElsePath --> MergeNode

MergeNode -->|"p is read / dereferenced"| Result["Compile Error:<br/>'p is used before it was initialized'"] ```

---

[01:14:35](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4475s) Essential vs. Accidental Complexity: The 600-Rule Spreadsheet

Sutter transitions from raw pointer safety to general initialization safety. Two years prior, he gave a talk emphasizing Fred Brooks’ distinction between essential complexity (problems inherent to the domain, such as needing polymorphic dispatch) and accidental complexity (the cumbersome ceremony required by a specific language design). To quantify this distinction, Sutter spent months compiling a massive spreadsheet cataloging over 600 specific rules and recommendations drawn from foundational C++ literature, including Scott Meyers’ *Effective C++* series, Sutter and Andrei Alexandrescu’s *C++ Coding Standards*, *High Integrity C++*, and Google's Abseil guidelines.

He reveals for the first time the hidden criterion behind that project: “What I'll now tell you is, and I also cataloged which ones... I did this by saying which ones would be fixed by CPP2.” Rather than acting as an armchair critic, he only marked a rule as accidental and improvable if he had a concrete technical design to eliminate it. Across the entire corpus of best-practice literature, nearly one-quarter of all published rules were devoted strictly to parameter passing and initialization safety, bloated by the sheer volume of repetitive guidance required to keep programmers safe from C++ defaults.

- The spreadsheet contains over 600 rows detailing specific rules, sub-rules, and advice from major C++ style guides and textbooks. - Literature analyzed includes works by Scott Meyers, Herb Sutter, Andrei Alexandrescu, High Integrity C++, and Abseil. - Initialization safety and parameter passing account for almost 25% of all rules in the surveyed C++ guidelines literature.

---

[01:16:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4590s) Definite First and Last Use Analysis

Sutter revisits the formal concepts of *definite first use* and *definite last use*, which form the backbone of Cpp2's safety analysis. A definite first use is an operation that is guaranteed to be the initial access to a variable on every execution path entering that point. Similarly, a definite last use guarantees that control flowing through that operation will never touch the variable again before exiting the scope. While languages like C# and Ada apply comparable data-flow analysis, Cpp2 applies it strictly to guarantee memory and lifetime safety without runtime overhead. Loops are treated conservatively: because a loop body might execute zero times, an initialization inside a loop body cannot satisfy a definite first use requirement for code following the loop.

To demonstrate, Sutter switches from pointers to a non-trivial type, `std::string x`. Leaving `x` uninitialized and attempting to access it immediately triggers the expected compiler error. When initializing inside conditional code, Sutter emphasizes the recursive nature of the rule: “You either initialize on both branches or neither branch. It's a delightfully recursive rule once you think about it.” While running live tests, he runs into a temporary tooling glitch with MSVC modules where importing standard library modules fails to expose `<chrono>` / `time`, forcing him to swap back to `rand() % 2` rather than alternating based on the system clock. Once both branches are covered, Cppfront compiles the code cleanly.

- Definite first use guarantees that an uninitialized variable receives a valid value across every incoming branch path before it can be read. - Loops do not count toward definite first use because loop conditions may evaluate to false on entry. - Branch analysis applies recursively across arbitrary levels of nested `if`/`else` structures.

---

[01:19:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4740s) Under the Hood: `cpp2::deferred_init` and `out` Parameters

Sutter opens the generated C++1 source side-by-side with the Cpp2 code to reveal the underlying lowering mechanisms. When a local variable is declared without an immediate initializer, Cppfront emits a helper template called `cpp2::deferred_init<T>`. This wrapper reserves properly aligned, uninitialized storage suitable for `T` on the stack without invoking any default constructor. When the user assigns to the variable later in the flow using the `=` operator, the compiler transforms that assignment into an in-place constructor call on the deferred storage.

Furthermore, Sutter shows how initialization interoperates with function calls. Instead of requiring functions to return objects by value or relying on raw pointers and references, Cpp2 supports explicit `out` parameter semantics. In the generated C++1 code, an `out` parameter is realized as a `cpp2::out<T>` wrapper, which accepts an uninitialized `cpp2::deferred_init<T>` storage location and guarantees that the callee constructs the object before returning.

- Uninitialized declarations in Cpp2 lower to `cpp2::deferred_init<T>`, allocating aligned raw storage without constructing an instance. - Subsequent `=` assignments down valid branches translate directly to explicit in-place constructor invocations. - Out-parameters are implemented via `cpp2::out<T>`, allowing functions to directly initialize deferred storage passed in from the caller scope.

[01:20:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4800s) Composable Initialization Safety and the Power of Out Parameters

Herb Sutter highlights the underlying mechanics of `cpp2::out`: the compiler tracks whether an out parameter is constructed with a fully realized value or with deferred initialization. Inside the function body, assigning to that variable triggers `construct`—which either initializes uninitialized storage on its first assignment or acts as a normal assignment if the variable is already constructed. Sutter emphasizes how critical this design is for composable initialization safety in real-world systems programming. Developers frequently allocate a stack buffer or a `std::array` of bytes and immediately hand it off to an operation like `memset`, `memcpy`, or a database reader. In standard C++, programmers are forced to write dead zero-initializations to satisfy safety tools or avoid undefined behavior, even though that memory will be instantly overwritten. Compilers struggle to eliminate these redundant dead writes. In Sutter's model, a unified set of initialization rules governs everything—pointers, strings, integers, large buffers, and plain old data (POD) structures—making safe initialization fully composable across parameter boundaries.

```mermaid flowchart TD accTitle: Composable Initialization Flow via Out Parameters accDescr: Flowchart illustrating how cpp2 out parameters act as delegated constructors without dummy writes.

subgraph CallerScope["Caller Scope"] A["Uninitialized Storage<br>(Stack Buffer / POD)"] B["Call: fill(out buffer)"] end

subgraph CalleeScope["Callee Scope: fill()"] C{"Static Analysis:<br>Is parameter constructed?"} D["construct: In-place initialization<br>(Zero dead writes)"] E["Normal assignment / mutation"] end

A --> B B --> C C -- "First assignment (deferred)" --> D C -- "Subsequent assignment" --> E ```

Addressing audience skepticism, Sutter remarks that whenever he discusses parameter-passing semantics with peers, people often say they love the system except for `out` parameters, questioning their true necessity. Sutter insists that `in` and `out` are actually tied for being the most crucial parameter modes of all six options. Unlike dummy initialization patterns—where developers defensively write placeholder values that accidentally mask uninitialized memory bugs from sanitizers—`out` parameters ensure objects receive programmatically meaningful values with compile-time guarantees. He observes: “Any function that has an out parameter can be a constructor.” By delegating initialization to an external function while having the compiler statically verify that dynamic construction is executed on every code path, Cpp2 generalizes construction into a composable concept.

- Compilers are notoriously bad at optimizing away dead writes inserted purely to satisfy zero-initialization idioms. - Dummy placeholder values hide uninitialized use bugs from runtime sanitizers like MemorySanitizer (MSan). - Functions taking `out` parameters conceptually act as delegated constructors, verified by compile-time definite-initialization analysis.

---

[01:22:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4950s) Named Multiple Return Values Interoperating with C++1

Sutter transitions to a live demonstration showcasing mixed C++1 (today's C++) and C++2 code. After scrolling past traditional header includes, he introduces a Cpp2 function `f` that takes no arguments and returns two named values: an `int` named `i` and a `std::string` named `s`. When asked how many developers have wished for this capability in standard C++ over the years, a large portion of the audience raises their hands. Sutter demonstrates that Cpp2 generates a normal C++ return structure decorated with leading double underscores (e.g., `__f_return`). Because implementers are reserved double-underscore identifiers, this synthesized return type does not pollute the user's namespace. In standard C++, writing such helpers manually forces developers to define a separate `struct` declaration immediately before the function definition, cluttering the enclosing scope.

```mermaid flowchart LR accTitle: Multiple Named Returns Lowering and Interoperability accDescr: Diagram showing Cpp2 named return values compiling to a decorated struct consumed by Cpp1 structured bindings.

subgraph CPP2["Cpp2 Function Declaration"] F["f: () -> (i: int, s: std::string)"] L["Local variables 'i' and 's'<br>Constructed on first assignment"] end

subgraph GeneratedCPP1["Generated C++1 AST"] S["struct __f_return { int i; std::string s; }"] M["std::move on return"] end

subgraph Consumer["Consumer: C++1 Caller"] B["auto [val, text] = f();<br>(Structured Binding)"] end

F --> L L --> S S --> M M --> B ```

Inside the function body, the return elements exist as regular local variables governed by standard definite-assignment rules: they must be assigned before use, and returning from the function automatically generates `std::move` from those locals. The developer merely writes `return;` without repeating the variable names or writing extra semicolons, because the names are already bound in scope. Running the compiled executable, Sutter highlights that the caller is an ordinary C++1 `main` function using modern structured bindings:

```cpp auto [i, s] = f(); ```

The C++1 caller unpacks the returned struct directly without friction. Sutter points out that consumers can also access the fields by their emitted names, making this drastically superior to anonymous `std::tuple` returns or boilerplate-heavy custom types.

- Multiple return values lower into a reserved `__` helper struct to prevent namespace pollution. - Local return variables are subject to identical initialization analysis and automatically moved upon return. - Functions with multiple returns are 100% ABI-compatible with C++17 structured bindings.

---

[01:25:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5100s) The Six Parameter Modes in Cpp2

Sutter moves from `out` parameters to the remaining five parameter passing conventions in Cpp2: `in` (the default), `copy`, `inout`, `move`, and `forward`. Inspecting the C++ code emitted by the Cpp2 compiler, he walks through how each mode is realized:

| Parameter Mode | Cpp2 Syntax | Emitted C++ Semantics | Behavioral Details | |:--- |:--- |:--- |:--- | | **`in`** | `x: in T` (or default) | `cpp2::in<T>` | Immutable const; passes by value if trivially copyable and $\le 2$ pointers in size, otherwise by `const&`. | | **`copy`** | `x: copy T` | By value (`T`) | Caller passes a copy; the parameter is automatically moved from its definite last use if valid. | | **`inout`** | `x: inout T` | `T&` | Explicitly documents the intent to mutate the caller's argument in-place. | | **`move`** | `x: move T` | `T` (via move) | Implicitly moved from on definite last use; call sites must explicitly prefix arguments with `move`. | | **`forward`** | `x: forward T` | `auto&&` + `requires` | Concept-constrained universal reference enabling perfect forwarding for concrete types. | | **`out`** | `x: out T` | `cpp2::out<T>` | Uninitialized target; compiler mandates definite assignment before completion. |

Examining `in`, Sutter points out that using `cpp2::in` completely eliminates the classic C++ dilemmas over whether to pass `T` versus `const T&`. If a type is trivially copyable and at most the size of two pointers, the compiler passes it by value; otherwise, it passes it by reference to `const`. For `copy` parameters, Sutter’s code generator inspects the function body to automatically apply `std::move` to the definite last use of the copy, provided taking an rvalue is valid. For instance, the compiler intentionally avoids inserting a move if the variable's address is taken (`&b`) or if increment/decrement operators are invoked, as that would cause compiler diagnostics. Sutter admits with a smile that he added these exceptions after his early generator unconditionally emitted moves that failed to compile.

Moving to `move` parameters, Sutter explains that the compiler requires the caller to opt in by writing `move arg` at the call site, generating the underlying move automatically. However, calling `std::move` directly inside Cpp2 source code is explicitly made illegal. Sutter reflects on the relief this brings to teaching modern C++:

> “I never again, I hope, have to teach: Why does `std::move` not move? Well, it does now. Why shouldn't I say `return std::move`? Well, no, you can't, so I don't have to teach that. That's the kind of world I want to live in through my C++ code.”

Finally, Sutter analyzes the `forward` parameter. In the generated C++, forwarding a concrete type generates an `auto&&` parameter paired with a C++20 `requires std::same_as<...>` concept constraint. While standard C++ perfect forwarding is almost exclusively restricted to generic template parameters, this idiom makes perfect forwarding just as effortless for concrete types like `std::string`. The compiler then automatically invokes `std::forward` on the parameter's definite last use. Concluding the section, Sutter notes the unified syntax: in Cpp2, there is only one universal syntax for defining functions, whether they appear as named declarations or as inline expressions and lambdas passed directly as arguments.

[01:30:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5400s) Unifying Function Expressions, Loops, and Captures

Herb Sutter explains that in Cpp2, constructs traditionally treated as separate grammar productions—such as lambdas passed to `std::ranges::for_each` or the body of a range-for loop—are conceptually and syntactically identical. In his compiler, they are simply function expressions that flow through the exact same parser logic. A range-for loop body is fundamentally a function: it is the primary construct in the language designed to repeatedly invoke a block of code with arguments that change on each iteration.

Anticipating an obvious objection from the audience after spending an hour and a half claiming all declaration syntax is uniform, Sutter addresses lambda capture. People often assume that needing lambda capture forces a language to provide a dedicated, distinct syntax. Crediting Epic Games founder Tim Sweeney, Sutter shares an insight from a conversation in Sweeney's office years earlier:

> “And he said, well, for Lambda, why don't you just always capture in the body? And by the way, always capture by value. If you want a pointer, take the address and capture that.”

Because Cpp2 already enforces pointer safety and initialization safety, Sweeney’s proposal works seamlessly. Rather than introducing a dedicated capture block before the parameter list, captures are written directly inside the expression body. If code requires a variable's value later when the lambda is evaluated, it captures that value explicitly where needed.

```mermaid flowchart TD accTitle: Unified Capture Model in Cpp2 accDescr: Diagram showing how Cpp2 uses identical capture-by-value syntax across lambdas, postconditions, and string interpolation.

subgraph CoreSyntax["Unified Capture Grammar: capture $name"] direction TB C["Capture by value inside body"] end

subgraph UseCases["Applied Scenarios"] direction TB L["Function Expressions / Lambdas<br/>(evaluate on invocation)"] P["Contract Postconditions<br/>(capture on entry, check on exit)"] S["String Interpolation<br/>(evaluate expressions into text)"] end

CoreSyntax --> L CoreSyntax --> P CoreSyntax --> S ```

Sutter asks the audience where else in current or proposed C++ a programmer captures a state now to execute or check it later. When someone suggests coroutines, Sutter agrees, but highlights another major application that already works in Cpp2: contract postconditions.

When specifying a postcondition on a mutating operation (such as `push_back`), one writes the contract at the top of the function—conceptually at entry—but it executes on return. To assert that the new size equals the previous size plus one, the function captures the starting size upon entry using the identical capture syntax. Furthermore, string interpolation in Cpp2 reuses this exact same mechanism. Sutter stresses the power of this syntactic economy:

> “And I think it's really important that there's one way to capture, there is one way to declare functions, there is one way to declare anything, because variables, types, and functions are all declared the same way.”

[01:33:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5580s) Familiar Vocabulary and the Implementation Roadmap

Taking a step back, Sutter observes that over the entire presentation, he has managed to avoid alienating jargon:

> “I have not said the word monad once.”

The audience laughs and applauds. Sutter emphasizes that none of the concepts introduced in Cpp2 are exotic or imported haphazardly from foreign programming paradigms. Instead, they reflect the way C++ programmers already think, talk, and reason about systems. His goal is not to reinvent software development, but to make those familiar daily idioms dramatically simpler and nicer to use.

Sutter outlines the remaining roadmap of features he must still implement, sorted roughly in ascending order of difficulty: - **Explicit Parameter Lists:** Refining the handling of parameter list definitions. - **Unified Classes, Constructors, and Assignment:** Implementing the unified object model proposed in his paper P0708 (referenced as D708), where constructors and assignment operators are unified through `out` parameters. With `out` parameters already functioning, Sutter hopes to tackle this over the upcoming Christmas break. - **Reflection, Generation, and Metaclasses:** Generating new code directly from the parse tree in a structured, disciplined way, avoiding raw token/textual substitution. - **Deterministic Lightweight Exceptions:** The most challenging task on the roadmap. It requires deep integration with stack-unwinding machinery, with the ambition of delivering deterministic-time `throw` and `catch` paths in collaboration with compiler vendors.

[01:35:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5700s) Call for Community Collaborators

Sutter invites the community to contribute to several exploratory projects surrounding the Cpp2 ecosystem:

- **Editor and Syntax Support:** Writing syntax highlighting for VS Code proved unexpectedly intractable. Sutter reached out to the maintainer of Microsoft's default C++ VS Code grammar to ask how to highlight additional keywords via regular expressions, only to be told that the underlying TextMate/regex engine is notoriously difficult and to seek help in specialized discussion forums. Sutter welcomes any developer with expertise in syntax highlighters to step in. - **Universal Function Call Syntax (UFCS) Autocomplete:** Designing editor tooling that can discover non-member functions and surface them through dot-autocomplete (`my_object.`). - **Compiler Explorer (Godbolt) Integration:** Hosting the transpiler on Compiler Explorer, including the ability to select the backend second-stage C++ compiler. - **Garbage-Collected Arenas:** Sutter notes that in Cpp2, all dynamic memory allocation occurs within explicit arenas. While general usage relies on deterministic arenas, an opt-in, zero-overhead-if-unused tracing garbage-collected arena remains an active design goal. Referencing his six-year-old `GCPP` proof of concept, Sutter jokes: > “I wrote a prototype implementation, but I am not a garbage collection expert, so it don't scale. But it's a proof of concept. If someone who knows what they're doing would like to collaborate, please send me mail.” - **`cpp2util` Minimal Drawing Library (`cpp2draw`):** Building a 21st-century, header-only equivalent to `curses` or `<conio.h>` providing a basic canvas, text, and keyboard input API. - **Source Migration Tooling (`fromcpp`):** In the longer term, hooking into an existing C++ frontend's AST pretty-printer to automatically analyze legacy C++ (Cpp1), detect recurring idioms, and translate them directly into clean Cpp2 syntax.

[01:37:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5850s) A Directed Evolution for C++

Sutter brings his talk to a conclusion by posing a fundamental question about the future of the language:

> “What if we could do C++ feels like a new language again, like we did in C++11? It feels like it, but it is still C++. But this time do it broadly across the whole language.”

He clarifies that Cpp2 is an experiment that could fail, but it exists to start a constructive industry conversation. Rather than fracturing the ecosystem or attempting to compete with the ISO C++ standard, Cpp2 is designed as a *directed evolution* that enthusiastically embraces modern standards like C++20, C++23, C++26, and modules (treating `import std;` as the default). Compiling down to standard C++ keeps the project completely honest. When ideas are proven effective, they can be upstreamed to the ISO C++ committee, which continues to own and define what C++ is.

Reviewing what developers would gain, Sutter highlights the massive cognitive burden that completely evaporates:

| Legacy C++ (Cpp1) Pain Points | Cpp2 Solution / Resolution | |:--- |:--- | | **Preprocessor & Headers** | Entirely gone. No `#include`, no tracking standard header placement, no declaration ordering dependencies. | | **Parsing Ambiguities** | Single declaration syntax eliminates the Most Vexing Parse, inside-out declarations, and East-versus-West `const` debates. | | **Function Signatures** | Eradicates disparate syntaxes between free functions, member functions, operator overloads, and lambdas. | | **Parameter Passing** | Replaces the agonizing choice between `T`, `const T&`, and `T&&` with explicit intent modifiers (`in`, `copy`, `inout`, `out`, `move`). |

[01:40:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6000s) Cutting C++ Complexity and Testing Bjarne Stroustrup's Conjecture

Herb Sutter rapidly enumerates the vast catalog of subtle rules, caveats, and syntactic traps in modern C++ that could be eliminated in an evolved syntax. He targets references, arguing that while they were originally added solely for parameter passing, they were unfortunately threaded across the entire type system, creating continuous conceptual baggage even in contexts where they should not be needed. He moves through the minefield of move semantics and forwarding: `std::move` and why it does not actually move data; why it is frequently overused; why returning `std::move` disables optimizations; why `&&` on a template parameter type is actually a forwarding reference rather than an rvalue reference; how to spell perfect forwarding with `std::forward`; and the bizarre acrobatics required to forward a non-template type. He points out that his proposed named return values make Named Return Value Optimization (NRVO) fall out automatically—because returning a consistently named return object means developers do not have to be taught how to coax the compiler into applying NRVO, nor choose between an anonymous tuple or polluting the namespace with a struct.

The list of pain points continues to mount: raw `new` and `delete`, owning raw pointers, memory leaks, `0` acting as an integer except when treated as a pointer, null pointer dereferences, pointer arithmetic running out of bounds, extra parentheses disambiguating prefix versus postfix expressions, diverse type queries, two whole paragraphs of type conversion categories, and disparate lambda capture syntaxes. Sutter polls the audience on whether these concepts are intimately familiar to them, receiving widespread hand raises, and then asks if they want an experimental language where they retain 100% of the expressiveness and performance of C++ without writing any of these error-prone patterns on the right-hand side of the board. The room responds with a sea of raised hands.

Sutter connects this directly to Bjarne Stroustrup's long-standing belief in fundamental language simplification, declaring: “Bjarne Stroustrup conjectured famously that C++ could be 10x simpler and safer. My thesis is that Bjarne is right, that he was right all along, and I'm giving it a go to see if I can prove it by actually implementing it, because I love C++.” Sutter reiterates that C++ remains the absolute best language for him to accomplish his daily work, but he wants to write it cleanly. He challenges the audience to envision where this experiment—or an alternative inspired by it—could lead the ecosystem over the next five to ten years: writing C++ with far more certainty, less teaching overhead, greater simplicity, and the modern tooling enjoyed by newer languages.

- Language friction points targeted for removal: - References throughout the general type system outside parameter passing. - `std::move`, `std::forward`, forwarding references (`&&` on template types), and move anti-patterns. - Manual optimization enabling (e.g., NRVO) versus language-automated copy elision. - Raw dynamic allocation (`new`/`delete`), owning raw pointers, and manual memory management. - Pointer/integer ambiguity around `0` and `NULL`. - Intricate type query, cast, and lambda capture permutations.

```mermaid flowchart TD accTitle: Cpp2 Complexity Elimination accDescr: Diagram contrasting legacy Cpp1 complexity burdens with the simplified model in Cpp2.

subgraph LegacyCpp["Legacy C++ (Cpp1 Complexity)"] A["Explicit std::move / std::forward"] B["Manual NRVO Gymnastics"] C["Owning Raw Pointers & new/delete"] D["Reference Leaks in Type System"] end

subgraph EvolvedCpp["Experimental Direction (Cpp2)"] E["Automatic Value Category Passing"] F["Named Return Values (Automatic NRVO)"] G["Default Safe Lifetime Management"] H["Simplified Parameter Semantics"] end

A -.->|"Eliminated by"| E B -.->|"Replaced by"| F C -.->|"Automated by"| G D -.->|"Constrained to"| H ```

---

[01:43:08](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6188s) Compiler Explorer Announcement and the Zero-Overhead Principle

As Sutter opens the floor for questions, an attendee approaches the microphone not to pose a question, but to announce breaking news: Matt Godbolt has just added official support for Sutter's `cppfront` compiler to Compiler Explorer. Sutter enthusiastically thanks Matt, calling him a personal hero for years of enabling the C++ community.

The first technical question delves into C++'s core tenet: the zero-overhead abstraction principle. The audience member observes that some features introduced in Cpp2—such as deferred initialization, which may track state using a behind-the-scenes boolean flag even for primitive types like integers—introduce runtime overhead. Sutter responds by clarifying the true formulation of the zero-overhead principle: “So the zero-overhead principle isn't zero cost, so you don't pay for what you don't use.” Sutter stresses that around 90% of the time, developers supply an initial value directly at the variable declaration site, resulting in absolute zero overhead. Only when a developer explicitly chooses deferred initialization (such as executing divergent constructor calls or complex branch-dependent logic) does the tracking boolean come into play. Because this is an experiment, Sutter notes that real-world implementation will uncover whether the trade-off is justified, but the baseline design strictly adheres to paying cost only when invoking the specific feature.

The attendee follows up by asking about replacing low-level constructs like C-style unions with `std::variant`, questioning whether forcing safe alternatives strips control away from developers demanding raw performance. Sutter points out that modern best practice already teaches developers to prefer `std::variant` over raw `union`. More fundamentally, Cpp2 provides total backward source compatibility:

- Full Cpp1 syntax remains valid and supported inside a Cpp2 translation unit. - If raw memory union semantics are genuinely required for extreme micro-optimizations, developers can explicitly write standard Cpp1 `union` syntax directly in their code.

---

[01:46:17](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6377s) Safe Defaults, Preprocessor Elimination, and Constraining Complexity

The Q&A shifts to language defaults and compiler flags. An attendee asks why `-p` (pure Cpp2 mode) is an opt-in switch rather than the default compiler behavior if the overarching philosophy is making bad code difficult to write. Sutter explains that `-p` forbids `#include`, macros, and any legacy syntax; keeping it opt-in is essential so developers can seamlessly write mixed Cpp1 and Cpp2 code during the transition. Expanding to other opt-in flags, Sutter notes that bounds checking for subscripts and null-dereference checks are currently placed behind compiler flags. While he conjectures modern branch predictors and memory architectures make these runtime checks cheap, they are left opt-in for now to collect hard benchmark data on whether their overhead is negligible or non-trivial.

A question from the online audience, asked by Alexei, touches on eliminating the C preprocessor. Alexei asks how developers can achieve logging or exception context without macros, noting that while C++23 introduces `std::source_location`, macros are historically used to capture line numbers (`__LINE__`), files (`__FILE__`), and function names (`__func__`). Sutter explains that C++ is methodically chipping away at the macro preprocessor:

1. **C++20 Modules** replace header inclusions and textual macro proliferation. 2. **`constexpr` extensions** replace compile-time macro calculations and code generation tricks. 3. Remaining compiler bookkeeping needs (like `#line` directives, which `cppfront` itself currently generates to map compiler errors back to source files) will be phased out. 4. Future standard **static reflection and code generation** represent the crucial leap that should eliminate the last major justifications for writing macros.

A final attendee asks whether the simplification drive should establish uniform file extensions, code formatting, and strict variable naming conventions to eliminate instructional friction. Sutter acknowledges that while he loves constraints—recalling the adage that constraints liberate by focusing human ingenuity where it actually matters—he remains unconvinced that styling or naming dictates move the needle on software quality. Sutter sets a rigorous bar for justifying new constraints: “If you can convince me that a naming convention for variables would get rid of 5% of the things that we currently teach people, now you've got my attention, but I would need to be able to prove that and measure it.” Without empirical evidence that a stylistic restriction eliminates a measurable percentage of Common Vulnerabilities and Exposures (CVEs) or cuts down chapters in coding standard manuals, he declines to mandate them.

[01:50:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6600s) Context-Free Parsing and Tooling Heuristics

An audience member asks about the tooling ecosystem, noting that formatters and static analysis tools frequently rely on fuzzy heuristics because they cannot definitively deduce intent—such as guessing whether an identifier should be `const` based merely on naming conventions. Herb agrees and explains that the primary reason existing C++ tools are forced to depend so heavily on fragile heuristics is simply that the language is notoriously difficult to parse.

In response, Herb highlights the parsing model he built into the `cppfront` compiler repository. To solve this foundational problem, he has implemented a standalone, context-free, and order-independent parser. While it is currently only a partial grammar, his goal is to grow it until it matches the full expressiveness of current C++ syntax while remaining cleanly reusable by other tooling authors. Once tooling can rely on an unambiguous, context-free grammar, formatters and analyzers will no longer need to lean on guesswork to understand user code.

- **Parser Design in `cppfront`**: A standalone, context-free, and order-independent grammar that eliminates the ambiguity that plagues C++ parsing. - Herb Sutter's view on parser difficulty: “One of the reasons tools need those heuristics is because it's so hard to write a C++ parser.” - **Reusability**: By designing the parser to be easily modularized and reused, external linters and formatters can process Cpp2 code reliably without heuristic workarounds.

---

[01:50:47](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6647s) Fixing C++ Pedagogy: The Power of Naming "Cpp2"

```mermaid flowchart TD accTitle: Cpp1 Compatibility Trap versus Cpp2 Clean Slate accDescr: Shows how backward source compatibility in Cpp1 enables outdated teaching habits, whereas Cpp2 enforces modern defaults by omission.

subgraph LegacyCpp ["Legacy C++ (Cpp1) Backward Compatibility"] A["Backward source compatibility mandatory"] --> B["'They teach it because they can'"] B --> C["fprintf, NULL macro, 0-as-pointer, raw owning new"] end

subgraph ModernCpp2 ["Clean-Slate Syntax (Cpp2)"] D["Distinct noun: 'Cpp2'"] --> E["Pure Cpp2 grammar omits obsolete constructs"] E --> F["Forces modern defaults, uniform initialization, and safe memory idioms"] end ```

Herb uses the tooling question as a springboard to address a persistent frustration shared by himself, Bjarne Stroustrup, and many modern developers: educators consistently teaching C++ by starting with C. Herb points out that this teaching habit is a direct artifact of C++'s unwavering commitment to backward source compatibility. Instructors introduce `fprintf` early in university curricula simply because the compiler allows it and the legacy code still compiles. Bjarne Stroustrup wrote *A Tour of C++* and authored dedicated programming textbooks precisely to combat this, begging educators to adopt modern C++ idioms rather than defaulting to C-style basics or obsolete C++98 patterns. Yet universities continually resist, replying that they stick with legacy practices simply because that is what they are accustomed to doing.

This pedagogical failure explains why Herb intentionally chose the distinct name "Cpp2" rather than referring to it abstractly as "syntax 2." By establishing a new, distinct noun, it creates an enforceable standard for education. An instructor teaching a course titled "Cpp2" cannot start the syllabus with `fprintf`, `NULL`, `0` used as a null pointer constant, or naked owning `new` pointers that leak memory by default, because pure Cpp2 simply does not include those constructs. While legacy functions can still be invoked through unified member-function call syntax for compatibility, the outdated syntax and unsafe defaults are completely absent from the pure language.

Reflecting on the need for radical improvement, Herb argues that on the current trajectory, the community would have already fixed C++ education if conventional incremental means were sufficient. Cpp2 serves as a deliberate lever to force courses to advance by a factor of 10x. Acknowledging that the project may face long odds, Herb concludes with cautious optimism before thanking the audience, wishing everyone safe travels, and inviting them back for next year's CppCon.

- Herb Sutter on university teaching habits: “One of the things that really bugs Bjarne and me and others is people teaching C++ starting with C.” - Herb Sutter on the reason legacy code is taught: “They do it because they can. Because that code works.” - Herb Sutter on the purpose of establishing a distinct name: “One of the advantages of having a name for a syntax, so one of the reasons I say not just syntax 2, but I call it CPP2, because if it succeeds, and it will probably fail, but if it succeeds, it also gives you a noun where nobody will be able to ever teach a CPP2 course and start with fprintf because that's not in pure CPP2.” - Herb Sutter on why an external lever is needed: “If we would have brought C++ courses significantly 10x forward, which is part of making teaching and learning 10x better, on the current path, we would have done it by now.” - **Constructs Banned in Pure Cpp2**: - The `NULL` macro. - Using literal `0` as a pointer. - Raw owning pointers paired with naked `new` and default leaks. - Traditional non-member `fprintf` invocation (which must be called using unified member call syntax if used at all).

## Notable quotes > [00:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=0s) To me, safer, 50x safer means 98% fewer CVEs, vulnerability exploits, and bugs in those categories. 10x simpler means I can get rid of 90% of the paper of the books I teach today. — Herb Sutter

> [40:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2400s) Call me paranoid, but when a government department starts telling you to do work and move to other languages, we should start to listen. — Herb Sutter

> [25:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1500s) The ugliness of generated code is directly proportional to the impedance mismatch. If your source is still C++, then you can make it beautiful. — Herb Sutter

> [01:50:47](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6647s) One of the things that really bugs Bjarne and me and others is people teaching C++ starting with C. — Herb Sutter

> [01:20:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4800s) Any function that has an out parameter can be a constructor. — Herb Sutter

## Mentioned Herb Sutter, Bjarne Stroustrup, Fred Brooks, Anders Hejlsberg, Niklaus Wirth, Scott Meyers, Andrei Alexandrescu, Tim Sweeney, Matt Godbolt, Joe Bialek, Joe Biden, Dave Abrahams, Kate Gregory, Dietmar Kühl, Brian Kernighan, Dennis Ritchie, Alexei, Microsoft, ISO C++ Standards Committee, Standard C++ Foundation, CppCon, United States Department of Commerce, MITRE, NIST, Windows, Google, Epic Games, Cfront, cppfront, Cpp2, Cpp1, Clang, GCC, MSVC, Windows Subsystem for Linux (WSL), Ubuntu, Compiler Explorer, Microsoft Visual Studio Code, GCPP, Rust, TypeScript, Python, The Mythical Man-Month, Effective C++, High Integrity C++, C++ Coding Standards, A Tour of C++, C++ Core Guidelines, P0708

## Video description https://cppcon.org/ https://github.com/CppCon/CppCon2022 ---

Can C++ be 10x Simpler & Safer? (Simplifying C++ #9 of N) - Herb Sutter - CppCon 2022

Since CppCon 2015, all of Herb’s talks have been about ways to evolve C++ to make it simpler, safer, and more toolable. Every release of ISO C++ has already been making regular incremental “10%” improvements in these areas. But what are the fundamental factors that limit our rate of improvement, and what would it take to make greater progress? Like every year, Herb’s talk will explore selected current pain points and describe experimental ideas to address them that might someday contribute toward C++’s long-term evolution. ---

Herb Sutter

Herb is an author, designer of several Standard C++ features, and chair of the ISO C++ committee and the Standard C++ Foundation. His current interest is simplifying C++. ---

Work at Hudson River Trading (HRT): https://tinyurl.com/safxfctf ---

Videos Filmed & Edited by Bash Films: http://www.BashFilms.com YouTube Channel Managed by Digital Medium Ltd https://events.digital-medium.co.uk

#cppcon #programming #coding

[Source](https://www.youtube.com/watch?v=ELeZAKCN4tY)

中文简报

Herb Sutter推出Cppfront以消除C++内存安全缺陷 Sutter认为,通过采用可直接转译为标准C++的无歧义第二语法,C++可以消除98%的内存漏洞以及90%的语言复杂度。 Herb Sutter介绍了Cpp2及其配套的开源编译器cppfront。这是一种实验性的替代语法,旨在验证Bjarne Stroustrup关于C++可以实现10倍简单和50倍安全的猜想。通过在不破坏向后兼容性的情况下编译为标准ISO C++20,Cpp2构建了一个干净的环境,消除了遗留的语法缺陷,强制执行确定性初始化,并用安全默认项取代了原始拥有指针和指针算术。Sutter展示了统一声明、自动边界检查契约、通过inspect实现的模式匹配以及标准化参数模式等特性,如何在保留零开销执行和无缝C++互操作性的同时,大幅削减偶然复杂性并减少内存CVE漏洞。 Herb Sutter介绍了cppfront,这是一款实验性的pre-alpha阶段编译器,可在不破坏向后兼容性的情况下,将无歧义的第二语法(Cpp2)直接转译为标准ISO C++20(Cpp1)。 (12:35) 该项目旨在验证Bjarne Stroustrup长期以来的猜想,即规模仅为C++ 10%的内部语言能够实现50倍的安全性(消除高达98%的CVE类别)和10倍的简单性。 (15:00) 受白宫行政命令和商务部宣布C与C++内存不安全警告的推动,Cpp2在默认情况下静态强制执行边界、生命周期和初始化安全。 (40:00) Herb Sutter和Joe Bialek对Windows安全漏洞进行的一项内部审计显示,所检查的边界越界违规行为100%本可以通过带边界检查的span来预防。 (55:10) Cpp2引入了统一声明(`name: type = value`)、六种显式参数模式、编译器强制的使用前确定性初始化,并消除了原始拥有指针和指针算术。 (17:30)

Reading summary

Herb Sutter presents an experimental front-end compiler, cppfront, designed to explore whether C++ can become substantially simpler and safer by introducing an alternate, zero-overhead syntax without breaking backward compatibility. Emphasizing that he speaks as an individual researcher rather than for Microsoft or the ISO C++ committee, Sutter aims to validate cleaner defaults and upstream viable design solutions into future ISO C++ standards. The Zero-Overhead Backward Compatibility Model (4:45) C++ development is traditionally constrained by the requirement of absolute backward source compatibility, which prevents the language committee from cleaning up historical ambiguities, legacy keywords, and unsafe defaults. To bypass this impasse, Sutter proposes extending C++'s core "zero-overhead" philosophy to source compatibility itself: keeping legacy compatibility fully available, but paying its complexity cost only when legacy syntax is invoked. Drawing an analogy to Bjarne Stroustrup's original Cfront compiler—which converted early C++ into standard C—Sutter introduced cppfront, an open-source, handwritten compiler that parses an experimental "Syntax 2" (Cpp2) and lowers it into human-readable ISO C++20 ("Syntax 1"). Unified Syntax and Toolable Grammar (17:30) To lower the language's concept count and test Stroustrup's conjecture that a language 10% the size of C++ is struggling to get out, Cpp2 replaces fragmented declarations with a single, context-free, left-to-right grammar patterned as `name: type = value`. This unified syntax standardizes type definitions, functions, variables, entry points, and lambdas, while enabling order-independent declarations. By omitting legacy preprocessor requirements, pure Cpp2 defaults directly to modern C++20 module semantics (`import std;`). The grammar also adopts Uniform Function Call Syntax (UFCS), allowing non-member functions to be invoked using member-access dot syntax (`f.fprintf(...)`). Sutter emphasizes that UFCS is not mere syntactic sugar; it provides contextual anchors that allow IDE autocomplete and IntelliSense tooling to discover valid operations on objects immediately, which prefix calls cannot do. Safety Defaults: Type, Bounds, and Lifetime Integrity (40:00) Addressing urgent regulatory and industry pressure surrounding memory vulnerabilities, Sutter targets the four recurring culprits identified in security studies: type, bounds, initialization, and lifetime defects. Cpp2 achieves type safety by replacing disparate casting and inspection mechanisms (`dynamic_cast`, `std::holds_alternative`, `std::any_cast`) with unified `is` and `as` operations, while explicitly rejecting unsafe legacy constructs like C-style unions in favor of `std::variant`. For bounds safety, Cpp2 makes the C++ Core Guidelines rule forbidding raw pointer arithmetic a compiler-enforced default, routing indexing through bounds-checked `std::span` or inserting contract assertions on standard subscripts (`words[3]`) without wrapping standard types. To eliminate null vulnerabilities, raw pointers in Cpp2 are non-null by default, and dynamic allocations bypass raw `new` and `delete` in favor of named arenas like `unique.new` and `shared.new` that yield smart pointers. Initialization Guarantees and Parameter Modes (74:35) In analyzing over 600 established C++ coding rules, Sutter observed that roughly one-quarter address parameter passing and initialization pitfalls. Cpp2 replaces these manual rules with definite first use and definite last use data-flow analysis, making it a compiler error to access a variable across any path before it has been initialized. Uninitialized declarations lower to `cpp2::deferred_init<T>`, deferring construction until an explicit assignment down an active branch occurs and eliminating redundant dummy writes. Parameter semantics are formalized into six explicit passing intents: `in` (the immutable default, which automatically picks value or reference based on type size and copy triviality), `copy`, `inout`, `move`, `forward`, and `out`. The `out` parameter acts as a delegated constructor, allowing callee functions to directly construct caller storage without dead stack initializations. Sutter acknowledges that cppfront remains an incomplete research experiment whose statistical default is failure. However, he maintains that proving these simplifications in working code offers an adoption ramp that avoids ecosystem fragmentation, providing a direct pipeline to test features, gather performance data on runtime checks, and upstream validated models into standard ISO C++.

精炼阅读摘要

Herb Sutter提出了名为“Cpp2”的实验性第二语法及源码转换编译器“cppfront”,主张在保留与标准C++无缝互操作的前提下,通过消除向后源码兼容性的包袱,从语言层面将C++的复杂度降低并彻底加固类型、边界、生命周期与初始化安全性。 零开销向后兼容与“柔道策略” (4:45) 面对全球七百万C++开发者面临的语言演进困境,Sutter提出了被称为“柔道策略”的核心构想:将C++标志性的“零开销抽象”原则拓展至源码级向后兼容性本身,即开发者始终保留对全部旧特性的访问能力,但“唯有使用时才需付出兼容代价”。为打破因维护数十年遗留代码而无法大刀阔斧重构语法的僵局,他借鉴四十年前Bjarne Stroustrup开发Cfront(将C++转译为C)的模式,构建了实验性转译器cppfront,将干净且无上下文歧义的“Cpp2”(语法2)编译为标准ISO C++20(语法1)。在完全由Cpp2编写的代码块内,设计者不必承担任何既往代码的向后兼容限制,从而能够移除陈旧历史瑕疵并重塑默认行为。 概念精简与统一从左至右声明 (17:30) Sutter旨在验证Stroustrup长期以来的推想,即“在庞大的C++内部潜藏着一个体积仅为其10%但更强大的语言”。为削减记忆特殊边界情况和交互规则的认知负担,Cpp2通过Niklaus Wirth推崇的从左至右自然书写模式,将所有实体声明统一归纳为“名称: 类型 = 取值”的单一语法体系。无论是类型定义(`Shape: type = {...}`)、函数、变量还是构造推导,均遵循完全相同的语法结构;常规函数、单表达式函数与Lambda表达式亦实现了语法归一。这种严谨的设计保证了上下文无关的解析能力与无顺序依赖的声明排布,彻底消除了“最令人头痛的语法解析”(Most Vexing Parse)以及传统的头文件声明先后顺序限制。 构建四大安全基石与静态约束 (38:00) 响应全球针对内存安全愈发严厉的监管环境与安全指令,Sutter明确将类型安全、边界安全、生命周期安全与初始化安全作为语言演化的首要目标。在类型安全方面,Cpp2使用`is`和`as`统摄既有C++中碎片化的类型查询与类型转换操作,在底层降级为类型特征判断或`if constexpr`分支,并直接在语法层面禁止使用不安全的裸`union`而强制推荐`std::variant`。在边界安全维度,语言全面禁止裸指针算术与指针下标注标,迫使开发者使用带范围检查的机制;同时转译器能在对容器执行下标操作(`words[3]`)时,自动生成基于`.size()`的边界检查断言,且可通过编译器选项注入精准的源码报错定位,而无需对标准库容器施加繁重的二次包装。 确切使用分析与确定性生命周期 在初始化与生命周期保障上,Sutter根据对经典编程规范整理出的600余条规则分析,指出有近四分之一的繁复规约仅用于弥补参数传递与初始化的陷阱。Cpp2引入“确切首次使用”(Definite First Use)与“确切末次使用”(Definite Last Use)的数据流分析,在静态层面保证变量在读取前必须初始化,同时禁止裸空指针(`nullptr`)与隐式未初始化行为。未显式赋予初值的变量被降级为`cpp2::deferred_init<T>`以预留对齐空间,避免低效的虚假默认构造与重叠写入。此外,Cpp2确立了六种明确的参数传递意图(包括`in`、`copy`、`inout`、`move`、`forward`与`out`),配合原生支持的多命名返回值,从根本上消除了传值与传常量引用的纠结,并使命名返回值优化(NRVO)成为语法自然结果。 工具友好性与平滑迁移路径 (35:00) 为了打破传统C++教学深陷C风格旧习惯的恶性循环,纯Cpp2模式直接禁用预处理器宏与`#include`,默认采纳C++20模块机制(`import std;`),促使开发者从第一天起就实践现代惯用法。在工具链层面,Cpp2推行通用函数调用语法(UFCS),允许形如`f.fprintf(...)`的对象前置调用,极大提升了IntelliSense等代码补全工具的上下文感知能力。Sutter强调,该项目并不寻求分裂社区,即使实验最终失败,开发者只要通过`-clean-cpp1`编译开关即可剥离全部转译指令,将代码降级还原为格式优美、可读性高的纯ISO标准C++,完全保留既有工程投资。 Sutter强调Cpp2仍是一个hilariously incomplete(极度不完备)的个人实验,并非微软或ISO委员会的既定标准。尽管部分安全断言机制与延迟初始化在现阶段可能带来少量的运行时开销,但该实验的核心价值在于为C++提供一条受控的演进路径,通过概念验证反哺ISO标准委员会,逐步验证并提炼能够真正让C++代码库兼具十倍简明与内生安全的可行机制。

Detailed notes — zh

## Background 该视频由 CppCon 官方频道于 2022 年 11 月 12 日在 YouTube 发布,主讲人为 ISO C++ 标准委员会主席 Herb Sutter。页面简介指出,自 2015 年以来 Herb 一直致力于推动 C++ 变得更简单、更安全且更易工具化,本场演讲探讨了制约 C++ 演进速率的根本症结,并公开展示了他旨在推动 C++ 长期演进的实验性探索。 ## TL;DR Herb Sutter 直面业界对 C/C++ 内存安全风险的严厉问责,提出了一个根本性命题:能否让 C++ 在保持 100% 互操作性与零开销抽象的前提下实现“简单 10 倍、安全 10 倍”。为此,他借鉴当年 Cfront 的演进哲学,推出了开源实验性转译器 cppfront 以及被称为 Cpp2 的第二语法。Cpp2 通过无上下文歧义的从左至右声明形式(name: type = value),将 Modules 导入、[[nodiscard]] 和统一函数调用语法(UFCS)设为默认,同时在生命周期、初始化、类型与边界安全四大核心维度构筑静态防线,彻底剔除裸指针算术、裸 new/delete 以及空指针隐患。这一方案旨在摆脱 100% 源码向后兼容性带来的历史包袱,彻底革新 C++ 的工程与教学体验。 ## Atmosphere & style 整场演讲充满诚恳的自省与大胆破局的工程师激情,既有面对美国政府安全禁令与传统语言包袱时的严肃紧迫,又洋溢着对 C++ 底层精髓的深沉热爱。现场穿插了终端卡死、忘记保存等生动滑稽的小插曲,以及 Matt Godbolt 现场接入 Compiler Explorer 带来的惊喜高潮,气氛热烈而默契。Herb 循循善诱地剥离语言历史琐碎,引得台下技术同行频频会心大笑并报以雷鸣般的掌声,让整场分享散发出一种既务实审慎又令人心潮澎湃的语言复兴愿景。 ## Bulletin - [10:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=600s) Herb Sutter 发布实验性转译编译器 cppfront,提出统一自左向右书写的“Syntax 2 (Cpp2)”,旨在不破坏与现有 C++ 生态 100% 互操作的前提下,实现简洁性与安全性的 10 倍跃升。 - [40:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2400s) 面对美国政府要求“在可能情况下弃用 C/C++ 等内存不安全语言”的严峻外部危机,Cpp2 将防御核心聚焦于四大高危漏洞维度:类型安全、边界安全、生命周期安全与初始化安全。 - [52:16](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3136s) Cpp2 默认全面弃用裸指针算术运算与裸 delete,并将自由存储区分配默认收敛为 std::unique_ptr,同时为带 .size() 的原生标准容器提供无需封套的编译期下标越界安全检查。 - [01:14:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4440s) 通过引入“使用前确定初始化”静态检查与六种明确语义的参数传递模式(如 in、out、move),Cpp2 旨在彻底剔除 C++ 教学中 90% 涉及底层历史包袱的偶然复杂度。 ## Key takeaways - [03:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=180s) 100% 语法层面的源码向后兼容性成为阻碍 ISO C++ 委员会带来质变的枷锁,必须将零开销原则(不使用的特性就不为其买单)扩展至源码兼容性领域。 - [10:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=600s) 复刻 40 年前 Bjarne Stroustrup 借助 Cfront 将早期 C++ 转译为 C 的历史路径,通过 cppfront 将 Cpp2 源码转译为标准 C++20 代码,降低新语法原型的验证门槛。 - [15:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=900s) Syntax 2 统一采用从左至右、上下文无关且顺序无关的声明范式(name: type = value),并在独立翻译单元中默认开启 C++23 的 import std; 以终结头文件地狱。 - [22:38](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1358s) Cpp2 全面支持统一函数调用语法(UFCS),使自由函数具备成员函数的调用体验,大幅改善 IDE 自动补全等工具链友好度。 - [24:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1485s) 代码转译器具备高度保真设计,严格追踪缩进与原位注释,并提供退回纯净 C++1 代码的逃生舱,确保实验失败时不会产生阻抗失配或资产损失。 - [40:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2400s) 美国商务部与 NIST 在 2021 年公开点名 C/C++ 内存不安全并建议停用,迫使 C++ 社区必须直面 MITRE 榜单常年盘踞的高危漏洞顽疾。 - [46:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2760s) 类型查询与类型转换被收敛为统一的 is 与 as 关键字,支持通用的 inspect 模式匹配,从根本上淘汰易出错的裸 union 并推荐 std::variant。 - [52:16](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3136s) 全面禁止裸指针自增与算术操作,微软 Windows 历史漏洞审查证明,强制带边界检查的访问机制可阻止近乎全部边界越界漏洞。 - [01:00:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3600s) cppfront 编译器通过内部契约机制重写下标访问,对 vector 等原生标准库容器直接施加边界安全契约检查,并支持细粒度自定义错误回调。 - [01:07:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4050s) 彻底在 Cpp2 中废除可设为 null 的指针与 NULL 宏,引入“使用前必须初始化”的分支路径分析,杜绝未初始化读取问题。 - [01:20:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4800s) out 参数本质上扮演了安全的委托构造器,结合 deferred_init 机制,避免了原始缓冲区在填充前因形式安全而执行的无意义全零写入。 - [01:22:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4950s) 支持多具名返回值并在调用端开箱即用 C++17 结构化绑定,底层自动插入移动语义,完全消除了手写打包结构体造成的命名空间污染。 - [01:25:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5100s) 通过 in、copy、inout、move、forward 六大参数抽象消除传值与传常引用的选择困境,并禁止手动写出 return std::move 破坏返回值优化。 - [01:30:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5400s) 重塑 Lambda 捕获概念为统一的“值捕获”语法($),同时将其同构复用到合约后置条件与字符串插值中,践行语法统一哲学。 - [01:52:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6720s) 确立独立的“Cpp2”名词而非仅称为“第二语法”,意在打造一个倒逼高校与教育界摆脱“从 C 语言和过时语法起步教 C++”不良现状的变革支点。 ## Detailed walkthrough ### [00:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=0s) 实验的起点:为 700 万开发者革新 C++ Herb Sutter 开场便坦言今年的演讲题目颇具挑衅意味,但实际上这是一个他已经“零敲碎打地讲了七年”的议题。现场参会者齐聚 CppCon 的原因很简单:大家都热爱这门语言。Herb 表达了同样的共鸣,认为 C++ 是为他编写所需软件的最佳语言;然而,C++ 同时也面临着无数严峻挑战。当下整个软件行业涌现出许多针对 C++ 的演进甚至革命性实验,而本场演讲展示的正是其中之一。 在深入细节前,Herb 郑重声明了立场:这纯粹是他个人的探索性实验,不代表任何公司,也不代表 ISO C++ 标准委员会。正因为是实验,它的默认状态就是可能会失败。即便未来有所成果,他也打算一如既往地将其实验成果转化为 ISO C++ 标准提案。Herb 呼吁全球 700 万 C++ 开发者社区成员:在探讨语言未来时,大家应当积极推动语言边界向前迈进,全力押注 C++ 本身,而不要急于转向其他替代品;我们的目标应当是实现 10 倍(10x)级别的演进,让我们成为更好的 C++ 程序员,而不是改行去做其他语言的开发者。 目前业界已有许多优秀的实践与尝试:Rust 已经是一门成熟的生产级产品,而非实验;还有许多语言实验走的是“从零开始全新设计(greenfield language design)”的路线,定义新语法、新模块系统、新包管理器并构建新生态。这些探索非常有价值且值得借鉴,但 Herb 指出,他要探索的是频谱的另一端——对 C++ 自身进行刷新与现代化。 ```mermaid flowchart LR accTitle: 编程语言革新路线对比 accDescr: 对比从零构建新生态与在 C++ 内部革新保留兼容性两条不同路线 subgraph Greenfield["全新语言路线 (如 Rust 等实验)"] direction TB G1["从零设计语言与语法"] --> G2["全新模块与打包机制"] G2 --> G3["构建独立全新生态"] end subgraph InPlace["Herb Sutter 探索路线 (C++ 自身演进)"] direction TB C1["刷新 C++ 自身设计"] --> C2["现成最佳实践设为默认"] C2 --> C3["保留现有生态与源码完全兼容"] end ``` - **核心宗旨**:将业界已有的最佳实践、指导规范以及 C++ 现有模块机制设为默认行为。 - **生态保全**:完整保留现有的 C++ 生态系统、包管理器与无缝互操作性,同时实现语言实质上的飞跃。 --- ### [03:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=180s) 核心冲突:零开销原则与 100% 源码向后兼容的束缚 讨论演进必须建立在清晰的度量标准之上。Herb 在演进路线图中强调了量化指标:“对我而言,安全性提升 50 倍意味着漏洞、CVE 和相关缺陷减少 98%;简洁性提升 10 倍意味着我可以扔掉当今教学书籍中 90% 的繁复内容。”回顾 2018 年 CppCon 上展示过的 C++ 本质定义,C++ 最坚不可摧的核心是“零开销抽象”(开发人员永远清楚数据在哪里、开销耗时几何)以及“无摩擦互操作与向后兼容”。 然而在社区中,很多人存在一种畸形认知,仿佛“如果它不让人感到折磨痛苦,它就不是真正的 C++”。Herb 对此观点提出强烈质疑:特定的冗长语法、不安全的代码实现、安全漏洞,这些绝非 C++ 的核心特征。尽管 ISO 委员会每个三年发布周期都在努力改良,但这种提升往往只有约 10% 的幅度,根本无法带来 10x 级的质变。造成这一困局的最大根源在于——委员会始终严格受制于“100% 语法层面的源码向后兼容(syntax backward compatibility)”。 为了破解这一死局,Herb 提出了一个犹如柔道借力打力的战略构想:将 C++ 最推崇的零开销原则(Zero-Overhead Principle),同样应用在源码向后兼容性上: > “That means we always have backwards 100% SFINAE macros, give us all of it. Backward source compatibility available. But, we only pay for it if we use it, is that possible?” —— Herb Sutter 这一思路意味着,开发者随时可以使用包括 SFINAE、宏在内的所有历史遗留特性,但如果不去使用它们,就不应当承受由于历史包袱带来的语法代价与安全妥协。 --- ### [05:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=300s) 破局之道:第二语法与七年概念重构 Herb 邀请观众进入一个有真实编译器实现支撑的思维实验:假如我们为 C++ 引入一套自洽且完全表达等价的“第二语法(second syntax)”会怎样?由于这套全新语法的表达形式完全无二义性,且此时没有任何人写过哪怕一行旧代码,这就形成了一个没有任何历史包袱的“新代码空间”。这意味着设计者拥有完全自由的裁量权,在不必顾虑破坏旧代码的前提下削减复杂度、提高安全性并提升工具链友好度,从而为 C++ 构筑一个足以继续演进 30 年的全新底座。 这并非改换门庭,正如 C++11 推出时那样,即便引入了智能指针与从左至右的尾置返回类型语法(trailing return type),大家依然坚信这毫无疑问是 C++。Herb 透露,这项重构工作他已低调进行了整整七年: 1. **重构而非发明**:始于 2015 年,利用业余时间梳理 C++,寻找那些表意一致却语法迥异的分裂结构,将它们抽离合并为更精简、可组合的通用特性。 2. **降低“概念计数(concept count)”**:语言的复杂度不仅体现在关键字数量,更取决于不规则特例的数量。每多一种特例、每多一种同义语法、每多一次不同特性组合时触发的异常边缘行为,都会强迫学习者陷入痛苦的机械死记硬背(rote learning)。 3. **分步向 ISO 标准提案反哺验证**:纸上谈兵毫无用处,因此自 2016 年起,Herb 每年在 CppCon 的演讲以及几乎所有向 ISO 提交的论文,均取材自这套实验原型,只是被转化成了现有的 C++ 传统语法来呈现。 通过拆解为独立提案,可以承受社区最严苛的审视、暴露缺陷,并检验该问题以及解法是否真为委员会和开发者所需。Herb 举例说明了著名的“飞船运算符(Spaceship Operator, `<=>`)”:这是唯一一个他未做原型验证就直接提交的提案。当时 C++17 刚刚发布,委员会正讨论比较运算符重载,Herb 顺势将自己实验设计库里的方案拿了出来。结果由于缺乏原型测试,该提案引入了两个重大缺陷。最后,全靠委员会多位同仁协助才得以修正。Herb 邀请坐在观众席各处的贡献者们起立接受全场的掌声致谢,以此强调这门庞大语言演进过程中集体协作的极端重要性。 ### [10:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=600s) “Bjarne 会怎么做?”:重拾 Cfront 的成功路径 Herb Sutter 以三路比较运算符(Spaceship `<=>`)作为开篇切入点。这是他推动的“C++ 简化工程”中首个正式并入 ISO 标准的成果。尽管为了定义核心语言机制而在标准文本中增加了十几页的行文,但由于该特性在标准库中的广泛应用,标准库削减的页数远多于增加的核心语言页数,使整个 C++ 标准的体量实际上变小了。这向大家证明:**为语言做简化是切实可行的**。 然而,长期困扰语言演进的最大阻碍是“原型开发的成本”(the cost of prototyping)。在庞大且沉重的生产级 C++ 编译器(如 Clang 或 Visual Studio)中试验新语言特性极其耗时耗力。面对这种困局,Herb 问道:“Bjarne 会怎么做?”台下会心一笑,Herb 指着幻灯片上 Bjarne Stroustrup 的纸板人偶打趣道,回顾历史最美妙的地方在于,如果前辈成功了,你直接去维基百科查答案即可。 40 年前,Bjarne 解决这一问题的方法是构建 **Cfront**——一个将 C++(乃至更早期的 C with Classes)编译输出为 C 源码的编译器。在 C++ 诞生的前十年里,Cfront 就是整个生态的核心支撑。Bjarne 当时的核心竞争力在于其精准的“15 秒电梯演讲”: > “我们热爱 C 语言的强大、贴近硬件以及灵活性,但它缺乏抽象能力;我就是要解决这个问题。” 通过编译生成 C 代码,C++ 得以运行在任何具备 C 编译器的平台上,并且与现有的 C 优化器、链接器、调试器及工具链实现完全互操作。即使调试器里显示的重载函数名是经过混淆修饰(name mangling)的,它依然能稳定工作。Herb 直言:“Without C-Front, we wouldn't be here.”(没有 Cfront,我们今天根本不可能坐在这里。) Herb 提出的核心设想是:**我们能否复刻这一路径?** ```mermaid flowchart LR accTitle: Cfront 与 cppfront 的架构映射关系 accDescr: 对比历史上 Cfront 将早期 C++ 转换为 C,与如今 cppfront 将 cpp2 语法转换为标准 C++ 的对应关系。 subgraph History["历史模式 (Cfront)"] A["C with Classes / C++"] -->|"Cfront"| B["C 语言代码"] B -->|"C 编译器 & 工具链"| C["目标机器码"] end subgraph Modern["实验设想 (cppfront)"] D["Syntax 2 (cpp2)"] -->|"cppfront"| E["标准 C++20 代码 (cpp)"] E -->|"标准 C++ 编译器"| F["目标机器码"] end ``` Herb Sutter 正式推出了他的个人实验项目——**cppfront**(已开源在 GitHub 的 `hsutter/cppfront` 仓库)。它的定位是一个正在开发中的“Syntax 2 到 Syntax 1”的转译编译器: - **延续相同哲学**:拥抱并加倍押注 C++,但着力解决使其“更安全、更简单、更易于工具化(toolable)”的核心痛点,同时它本质上仍是 C++。 - **广泛兼容与无缝互操作**:只要有现成的 C++20 编译器,它就能在所有主流平台上构建和运行,并与当今的 C++ 生态实现 100% 的双向互操作。 - **局限与现状声明**:Herb 多次强调这纯属个人实验性质,目前的编译器实现“极度残缺(hilariously incomplete)”。当前重点仅在函数层面,尚未支持 class(计划在接下来的冬季加入),但仅通过重构函数语法就已经实现了大幅度的语言简化。其初衷是在整个行业急于转向其他新语言之前,探讨在 C++ 自身演进范畴内实现语言“返老还童”(rejuvenate)的可能性。 项目的极简工程理念在其构建方式上表现得淋漓尽致:主流编译器的构建命令均只有单单一行,完全没有使用 CMake、Bazel,也没有引入任何第三方库、Yacc 或 Bison 等语法生成工具,全部逻辑由 Herb 从空白编辑器开始手写,完全依托标准库编写手写解析器(manual parser)。在日常使用中,无论是纯 cpp2 文件还是混编文件,只需通过 `cppfront` 生成对应的 `.cpp` 文件,随后正常送入普通 C++ 编译器编译即可。 ### [15:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=900s) 概念完整性与 Syntax 2 的左向统一声明语法 Herb 详细阐述了驱动其最近七年所有标准提案的核心原则与设计界限。他引用了 Fred Brooks 在《人月神话》中的经典论断,指出系统设计中最重要的品质是**概念完整性(conceptual integrity)**——整个系统的各个部件必须和谐自洽,宛如出自一人之手。这与 Bjarne Stroustrup 几十年来的公开论断高度呼应: > “Bjarne has conjectured publicly for decades that within C++ there is a language 10% the size, struggling to get out.”(Bjarne 数十年来一直公开推测,在 C++ 内部潜藏着一个体量仅为其 10% 的语言,正挣扎着破茧而出。) Herb 设立了清晰的取舍边界(stakes in the ground): 1. **拒绝无实质价值的顺手修改(drive-by improvements)**:绝不容纳没有足够分量、无法解决实际问题、纯属个人审美偏好的变动;必须死磕“更安全、更简单、更易工具化”这三大支柱,并且所有成效都必须具备可量化的指标。 2. **坚守 C++ 核心价值**:恪守零开销抽象原则(zero overhead),保障开发者在需要时始终拥有对底层的绝对控制权。 3. **语法不是目的,语法只是“大门”**:Herb 特别告诫观众,千万不要将其曲解为“Herb 只是想让 C++ 看起来更好看”。语法(Syntax)只是为了建立一块隔离历史包袱的新代码气泡,是通往新领域的大门;唯有借助新语法,才能安全地引入真正核心的语义改进、修正危险的默认行为(fixing defaults)并移除特殊特例,而不会破坏几十年来已有代码的向后兼容性。 现代语言设计的基础门槛必须包含:上下文无关文法(context-free parsing)以及与声明顺序无关(order-independent)。同时遵循 Niklaus Wirth 很久前提出的观点:“声明语法应当由左至右,顺应天性。” 在现有 C++(Syntax 1)中,自 1980 年左右就存在的 `class Shape {... }` 或 2011 年引入的尾置返回类型函数 `auto f(int i) -> std::string {... }`,其大括号内的所有规则都背负着历史包袱,官方无法直接修改其默认规则,否则会立刻“break the world”(导致全球代码崩溃)。 为此,cppfront 提出了**统一由左至右书写的“Syntax 2”**,将所有声明统一为极简的 `name: type = value` 范式: | 实体类别 | Syntax 2 (cpp2) 语法表现 | 说明 | |:--- |:--- |:--- | | **类型声明** | `Shape: type = {... }` | 名称为 `Shape`,类型为 `type`,值为其类型体 | | **函数声明** | `f: (i: int) -> std::string = {... }` | 名称为 `f`,类型为接受 `int` 返回 `string` 的函数,值为函数体 | | **变量声明** | `vec: std::vector<std::string> = ( "a", "b");` | 保持标准库命名一致性,清晰规范 | | **类型推导** | `view: std::span = vec;` | 支持 CTAD(类模板参数推导),推导出匹配的 span 视图 | 在该语法的实际代码演示中,主函数写作 `main: () -> int = {... }`。Herb 展示了一段仅占单张幻灯片的完整程序代码:这段代码能够直接编译运行,并且**整整一段代码中没有包含任何一行 `#include` 或 `import` 语句**。Herb 兴奋地指出,在 Syntax 2 的独立翻译单元中,C++23 的 `import std;` 是默认内置开启的,开发者今后将再也不需要费力去记忆并手动包含那一长串的标准库头文件了。 ### [20:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1200s) 极简函数语法与默认机制:参数传递、无前向声明与 Lambda 统一 Herb Sutter 继续深入阐述 Cpp2(即语法 2)在设计之初就确立的几项核心构想。首先是构建速度与强单一定义规则(ODR),这些优势唯有现代 C++ 模块(Modules)才能彻底提供。面对部分开发者对“直接引入整个标准库(包括正则表达式、并行算法等)会导致编译变慢”的担忧,Herb 指出在当前编译器实现中,`import std;` 的编译速度实际上比 `#include <iostream>` 还要快;甚至引入整个标准库比编译一个传统的“Hello World”程序耗时更短。因此,他在实验中将其设为了默认行为,并信心十足地表示:“我的实验是:让我们把它设为默认值。嘿,这是一次实验。如果效果不好,我们可以调整它。但我对这一个选择相当有信心。”(“My experiment is Let's make it the default. Hey, it's an experiment. If it doesn't work out, we can change it. We can tweak this. But I'm pretty confident about this one.”) 在参数传递方面,Herb 沿用了两年前在 CppCon 上介绍的 P0708 提案设计,通过 `in`、`out` 等明确语义传递形参,在 Cpp2 中开发者完全不需要直接书写底层的引用符号(`&`)。此外,下划线 `_` 被用作通配符以实现隐式函数模板。如果细看代码,还会发现最后一个函数甚至没有花括号包裹。Herb 解释这源于他的另一项核心信条——“万物归于统一语法”(One syntax for everything)。只要设定好优雅的默认规则,不需要的部分就可以直接省略;这并非提供两种写法,而是带有可选项的唯一定义方式。对于单表达式或单语句函数,不仅不需要花括号,当其返回值时甚至不需要写 `return` 关键字。 这种语法的统一性给 Lambda 表达式带来了巨大简化。在 Cpp2 中不存在专门为 Lambda 单独发明的声明语法,Lambda 本质上就是在一个表达式作用域内书写一个普通函数并省略其名字,直接以冒号加圆括号(`:(`)开头即可。对于追求简短便捷的 Lambda 而言,这种能够省去模板样板代码、花括号和返回语句的统一设计显得尤为自然。更令人赞叹的是,示例代码中直接在声明和定义之前调用了 `decorate` 与 `printline`,全程没有任何前向声明(forward declaration)——这是一种彻底无视代码书写顺序(Order-independent)的现代语言与语法体验,引来了台下观众的热烈掌声。 ```mermaid flowchart TD accTitle: Cpp2 语法与模块化核心特性 accDescr: 展示 Cpp2 在模块引入、函数定义与统一语法上的关键革新 subgraph BuildModel["构建与模块优化"] A["默认 import std;"] --> B["强 ODR 与超快编译速度"] B --> C["超越传统 #include 的吞吐量"] end subgraph UnifiedSyntax["统一声明语法"] D["单一函数语法"] --> E["单表达式省略花括号与 return"] D --> F["省略函数名即可就地表达 Lambda"] G["P0708 参数语义 (in / out)"] --> D H["下划线 _ 自动隐式模板"] --> D end subgraph OrderIndependent["声明顺序解耦"] I["无需前向声明"] --> J["任意位置自由调用"] J --> K["编译器自动发射相对声明与定义"] end ``` ### [22:38](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1358s) 代码生成哲学:默认 `[[nodiscard]]` 与统一函数调用语法(UFCS) Herb 展示了一张 CppFront 实际运行生成代码的终端截图,并特意强调这绝非 PowerPoint 上的概念伪代码,而是听众回家后编译同个文件就能立刻复现的真实结果。观察生成的代码可以发现,所有函数默认都带上了 `[[nodiscard]]` 属性。Herb 询问台下有多少人希望 `[[nodiscard]]` 是现代 C++ 的默认项,看到全场踊跃举手后他笑着回应道,自己开发这款编译器的初衷正是为了让自己能以最舒适、最喜欢的方式写 C++:“当你是编译器的编写者时,你就有权做出选择。”(“When you write the compiler, you get to make choices.”)同时,由于 CppFront 只是将语法 2 转译为语法 1,诸如类型推导(CTAD)等现代特性依然天生可用,像 `view: std::span = vec;` 这样的写法能够完美生效。 随后 Herb 指出前页幻灯片中出现的一个细节——调用了 `thing.ssize()`。熟悉标准委员会历程的成员立刻心领神会。过去多年间围绕“有符号(signed)还是无符号(unsigned)容器大小”的争辩层出不穷,最终 Bjarne Stroustrup 既未能说服委员会将全部容器的 `.size()` 改为返回有符号类型,也没能成功将 `.ssize()` 作为成员函数加入每个容器,仅仅在 `std` 命名空间下争取到了非成员函数 `std::ssize()`。 然而在 Cpp2 中,`thing.ssize()` 能够直接生效,原因在于语言天然支持统一函数调用语法(UFCS,Universal Function Call Syntax)。如果存在成员函数则优先调用;若无,则自动回退查找非成员函数。Herb 回忆起八年前与 Bjarne 等人共同撰写 UFCS 提案的往事,原本他以为这只是一项微小的便利,但在编写数十个测试用例和示例时,他惊讶于自己多么迅速地彻底习惯了直接写 `.ssize()` 且再也不想回头。 - 默认生成的现代特性:自动为函数标注 `[[nodiscard]]`,减少逻辑遗漏。 - CTAD 天然兼容:转译至现有 C++ 机制,完全保留类模板实参推导能力。 - UFCS 消除语法割裂:容器可以直接以点号调用 `std::ssize(thing)`,抹平成员与自由函数的调用差异。 ### [24:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1485s) 逃生舱与代码美学:无阻抗失配与干净转译 Herb 谈到了一个至关重要的工程原则:这套新语法必须具备“逃生通道”(Escape Hatch)。任何前沿实验都存在失败的可能,甚至失败才是常态;因此 Herb 绝不希望自己在实验一旦受挫时损失既有的代码资产。他要求必须能随时把新语法直接转译回当前标准的 C++1 代码并潇洒离场:“能够写下这种新语法的代码,并在它行不通时不会损失我的投入,随时能够将其丢弃并带着当今语法的代码离开,这对我来说非常重要。”(“I want to be able to write code in this new syntax, and if it doesn't work out, not lose my investment. And always be able to just throw it away and walk away with my today's syntax code. That's important to me.”) 为了实现无视声明顺序的特性,CppFront 的机制非常精巧:它在解析到 Cpp2 函数的位置就地交错生成该函数的前向声明,确保现有的 Cpp1 代码能够按照传统的顺序依赖进行调用;随后在文件的合适位置集中发射 Cpp2 的完整函数定义,使其内部彼此可见。 为了打破“机器生成的代码必然极其丑陋”的固有偏见,Herb 展示了一个包含大量单字母变量的极限测试用例。在 Cpp2 中,“声明跟随使用”(declaration follows use)的设计使得解引用等大部分操作符都变为了后置(postfix),这在编写表达式时大幅消除了多余的括号。当转译器将后置解引用重写为前置加括号时,CppFront 依然精密追踪了所有的缩进与排版,甚至将原始代码中所有注释的精确坐标记录下来,原封不动地放回原处。使用 `--clean-cpp1` 开关时,转译器更会剥离调试指示宏,输出完全符合人类代码风格的整洁代码。Herb 犀利地总结了机器生成代码美感的本质规律:“生成代码的丑陋程度,与语言间的阻抗失配直接成正比。如果你的源码本质上仍是 C++,你就能让它变得优美。”(“The ugliness of generated code is directly proportional to the impedance mismatch. If your source is still C++, then you can make it beautiful.”) ```mermaid flowchart LR accTitle: CppFront 源码转换与保真架构 accDescr: 展现 Cpp2 源代码通过 CppFront 编译为优美且保持人类风格的 Cpp1 源码流程 subgraph SourceInput["输入端: 混合源码"] A["Cpp2 逻辑 (后置操作符/UFCS)"] B["原有 Cpp1 语法与头文件"] C["原始注释与缩进排版"] end subgraph Transpiler["CppFront 处理引擎"] D["位置感知的 AST 重写"] E["就地发射前向声明 (兼容 Cpp1)"] F["集中发射函数定义 (顺序解耦)"] end subgraph OutputTarget["输出端: 优雅 Cpp1 代码"] G["--clean-cpp1 洁净代码"] H["无阻抗失配的原生 C++ 结构"] I["完美保留的原位注释与格式"] end A --> D B --> E C --> D D --> F E --> G F --> H D --> I ``` ### [27:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1650s) 现场演示:三家编译器角逐与意外的终端挂起 讲解完理念后,Herb 准备进入实操环节,展示这个自研的 Pre-Alpha 实验性编译器原型。在开始前,他笑着向全场打趣,希望大家为他祈祷好运,若发生崩溃也请多多包涵,尽管他坚信程序不会轻易崩溃。 现场展示的第一个示例文件是一个混合语法文件:文件顶部依然保留着传统的 `#include <iostream>` 和 `#include <string>`,`main` 函数也是采用当今 C++ 开发者非常熟悉的现代写法(利用 `auto main()`),而主函数调用的两个辅助函数则完全使用 Cpp2 语法编写。由于 CppFront 生成的是标准的 C++,因此该流程可以在当前主流的三大编译器(Clang、GCC、MSVC)上无缝工作。 Herb 现场发起掌声投票,让听众在三家编译器中挑选一个来运行首次演示: 1. Clang:赢得了一阵热烈的掌声; 2. MSVC(Visual C++):掌声声量与 Clang 势均力敌; 3. GCC:获得了全场最响亮的一轮掌声,无可争议地胜出。 为了运行 GCC,Herb 切换至 Windows 内置的 WSL(Ubuntu)环境,并特意感谢了微软在 Windows 中提供了原生的 Linux 支持。然而就在他准备大显身手时,意外发生了:Ubuntu 终端完全不再响应键盘按键。Herb 尝试重启终端依然无法输入任何字符,台上顿时陷入尴尬而充满喜感的空气中。Herb 无奈地苦笑着惊呼:“哇,这种事以前可从来没发生过。哇,这可真是太不可思议了。好吧,看来我们只能转向我们的第二选择了。”(“Wow, this has never happened before. Wow, this is really incredible. Well, I guess we're going to go to our second choice.”) ### [30:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1800s) 混合语法编译与 cppfront 错误诊断体验 在现场演示中,Herb Sutter 运行了 `cppfront demo.cpp2`。第一次编译立刻报错,但这完全在他的预期之内。第一阶段编译器首先分析代码,确认当前文件是 CPP1 与 CPP2 的混合代码,并验证 CPP2 代码部分通过了全部安全性检查;随后报错来自底层的第二个编译器(如 MSVC、Clang 或 GCC),但错误信息却准确对应到了原始的 `.cpp2` 源文件位置。编译器提示 `strim` 不是 `std` 的成员,而如果是 Clang 或 GCC 还会贴心地提示“Did you mean string?”。现代编译器强大的错误诊断信息在经过转译后依然完美保留。Herb Sutter 修正了这个故意写错的拼写错误后重新编译,生成的可执行文件顺利运行。 ```mermaid flowchart LR accTitle: cppfront 混合模式双阶段编译流程 accDescr: 展示从 cpp2 源码经过 cppfront 转换生成合法的 cpp1 代码,再由标准 C++ 编译器生成二进制的流程。 subgraph Phase1["第一阶段: cppfront 转换"] A["demo.cpp2 (混合 CPP1 / CPP2)"] --> B["cppfront 语法解析与安全检查"] B --> C["代码发射器 (Code Emitter)"] end subgraph Phase2["第二阶段: C++ 编译器编译"] C --> D["生成的 C++ 代码"] D --> E["C++20 编译器 (MSVC / Clang / GCC)"] E --> F["可执行程序 (.exe)"] end ``` Herb Sutter 展示了 `cppfront` 生成的具体 C++ 代码结构:文件顶部包含所需的实用工具头文件(支持 `in` 参数等语义);紧接着按原序发射所有的 C++ 语法 1(CPP1)代码;每当解析到 CPP2 的函数定义时,工具会在上方按声明顺序提前发射前置声明(forward declaration),以便按顺序依赖的 CPP1 代码(如 `main` 函数)能够顺利识别 `decorate` 等新函数;最后在文件末尾发射带有完整花括号函数体的 CPP2 实现细节。这种机制使得开发者无需在同一个函数体内部混写两种语法,却可以在同一个源文件中无缝交替定义不同语法的函数。 - 工具在初次生成二进制时会触发系统的杀毒软件扫描,导致第一次运行略有停顿,第二次执行则恢复极速。 - 混合模式彻底解决了 CPP1 语法对声明顺序强依赖的问题,同时保留了现代编译器的代码诊断能力。 > “Notice this error is coming from the second compiler, but it's in terms of my original CPP2 source file.” —— Herb Sutter --- ### [32:15](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1935s) 纯净 CPP2 模式与安全的内存管理默认行为 随后 Herb Sutter 展示了纯净的 CPP2 源文件(使用 `-pure` 编译开关)。当他尝试对包含传统预处理指令的代码开启 `-pure` 模式时,编译器立刻给出了明确警告:纯 CPP2 模式完全禁用了预处理器,包括 `#include`,并明确要求使用现代模块导入语法(`import std;`)。Herb Sutter 指出,这一设计遵循了当下的最佳实践建议:“少用预处理器”,并默认采用社区已经向开发者推行的现代规范。在纯 CPP2 代码中,`main` 函数简化为新式语法(如 `main: () = {... }`),函数定义不再依赖出现顺序,且标准库默认完全可用。 在堆内存分配方面,Herb Sutter 演示了如何彻底淘汰容易出错的裸 `new`。在 CPP2 中,所有的自由存储区(free store)内存分配必须通过“命名的内存空间(named memory arenas)”完成。这些内存空间本质上是提供泛型 `new` 成员函数的普通对象。系统默认提供了两个命名空间:`unique.new` 与 `shared.new`。如果开发者直接书写全局的分配语法而不显式指定空间,系统默认采用的就是 `unique.new`,返回一个 `std::unique_ptr`。Herb Sutter 强调,`std::unique_ptr` 必须成为语言层面的默认指针,从而从根源上提供类型安全与内存安全的生命周期保障。 - 在纯 CPP2 模式下,预处理器指令被直接拒绝,引导代码库平滑过渡到 C++20 模块体系。 - 内存分配默认返回 `unique_ptr`,杜绝了裸指针悬挂与内存泄漏的常见隐患。 > “S is a unique pointer of string because unique pointer should be the default pointer.” —— Herb Sutter --- ### [35:05](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2105s) 统一函数调用语法(UFCS)与平滑迁移路径 Herb Sutter 接着展示了一段调用 C 标准库 `fopen` 和 `fprintf` 输出文本到文件的代码,并将爱犬 Lily(一只可爱的小型意大利灵缇犬)的名字写入文件。代码运行正常并成功创建了文件,但他随即指出左侧代码的一个关键特质:代码中直接把 `fprintf` 当作文件句柄对象的成员函数来调用。现场大多数听众起初甚至没有察觉到这一异样,因为这种书写方式极其自然。Herb Sutter 解释,统一函数调用语法(UFCS)绝非仅仅是语法糖或为了“耍酷”,其核心驱动力在于**开发工具链的友好性(Toolability)**。当开发者调用普通非成员函数时,IntelliSense 无法在空白行预测输入意图;而只要先输入作用域内的对象后键入点号(`.`),IDE 就能立即通过自动补全列出所有可用操作。 ```mermaid flowchart TD accTitle: 传统语法与 UFCS 在 IDE 自动补全中的对比 accDescr: 展示对象后点号触发自动提示与空白行非成员函数无法获得有效补全的机制差异。 subgraph Legacy["传统非成员函数调用"] L1["在空白行开始输入"] --> L2["缺乏类型上下文"] L2 --> L3["IntelliSense 补全候选过多且模糊"] end subgraph UFCS["统一函数调用语法 (UFCS)"] U1["输入已知对象 (object)"] --> U2["键入点操作符 (.)"] U2 --> U3["IntelliSense 准确提示适用于该对象的所有函数"] end ``` 这种设计遵循了“零开销原则(Don't pay for what you don't use)”,并将其扩展至源码向后兼容性领域。Herb Sutter 援引了编程语言演进史上的两个伟大范式:Bjarne Stroustrup 当年设计 C++ 时允许直接将 `.c` 重命名为 `.cpp`,并逐步增加类定义;Anders Hejlsberg 设计 TypeScript 时允许将 `.js` 重命名为 `.ts`,仅需为一行变量添加类型标注即可获益。CPP2 采取了相同的渐进式迁移阶梯:开发者只需将 `.cpp` 重命名为 `.cpp2`,即可完全兼容 SFINAE、宏等既有代码,同时逐行引入安全的新语法,从而彻底规避类似“Python 2 到 Python 3”那样破坏生态的断崖式割裂。 - UFCS 让自由函数具备成员函数的调用体验,大幅提升了现代编辑器代码补全与探索的效率。 - 渐进式兼容模型保证了对既有 C++ 库 100% 的互操作性,无需重写整个项目即可享受安全性增强。 > “It's about being more toolable, because if you call using the non-member function call syntax, IntelliSense can't help you.” —— Herb Sutter > “And we've avoided the Python 2.3 problem because it's a unique and distinct syntax.” —— Herb Sutter --- ### [37:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2265s) 安全愿景与核心攻坚方向:MITRE 榜单的启示 在确立纯 CPP2 模式的可行性后,Herb Sutter 表达了自己长久以来的教学与工程愿景。如果这一实验取得成功,他希望能彻底告别过去 25 年里不得不向学生传授的 90% 的底层晦涩细节。他渴望教授的是真正的“如何编程”,而不是如何去念诵那些应付语言陷阱的神秘咒语(incantations)。纯 CPP2 模式力求构建一个在结构上天然安全(Safe by construction)、比传统 C++ 简单 10 倍且更安全的开发环境,并通过模块导入保持与现有库生态的无缝互通。 ```mermaid flowchart LR accTitle: 核心安全攻坚领域与 MITRE 常见高危漏洞映射 accDescr: 展示类型、边界、生命周期与初始化安全分别对应的常见安全漏洞。 subgraph Safety["四大安全防线"] S1["边界安全 (Bounds)"] S2["生命周期安全 (Lifetime)"] S3["初始化安全 (Initialization)"] S4["类型安全 (Type)"] end subgraph Weaknesses["MITRE 常见高危软件漏洞"] W1["越界读/写 (Out of bounds R/W)"] W2["释放后使用 (Use after free)"] W3["空指针解引用 (Null pointer deref)"] W4["整数溢出 (Integer overflow)"] end S1 --- W1 S2 --- W2 S3 --- W3 S4 --- W4 ``` 谈及安全性,Herb Sutter 明确将焦点对准四大核心领域:**类型安全(Type safety)、边界安全(Bounds safety)、生命周期安全(Lifetime safety)以及初始化安全(Initialization safety)**。之所以优先攻克这四项而非并发安全等其他主题,是因为它们始终占据着现实软件安全威胁的核心。Herb Sutter 特别鸣谢了 Dave Abrahams 的提醒——根据 MITRE 发布的 2022 年最危险软件缺陷榜单,排名前列的依然是这些“常青树”漏洞:越界写入、越界读取、释放后使用(Use After Free)、空指针解引用以及整数溢出。这证明产业界最亟待解决的顽疾依然牢牢盘踞在这四个基础维度之中。 - 教学的核心应当是软件工程与设计思维,而非 C++ 历史包袱造成的语法仪式感。 - MITRE 榜单表明,绝大多数致命安全漏洞仍然集中在边界越界、悬挂指针与未初始化状态。 > “Because I want to teach people how to program, not how to encant a C++ detail.” —— Herb Sutter ### [40:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2400s) 软件安全面临的时代危机与美国政府对 C/C++ 的警告 在探讨安全漏洞时,很多人会提到整数溢出,但 Herb Sutter 明确指出,人们最在意的整数溢出本质上是索引溢出,其核心归根结底属于边界安全问题。在行业排名前 13 位的常见严重漏洞中,如果剔除掉诸如 SQL 注入或“去清洗你的输入数据”这种不属于编程语言直接控制范畴的缺陷,单看编程语言本身能够解决的问题,就会发现其中有 5 项正是语言层面的责任。而且无论年份如何推移、所占比例如何微调,这些问题年复一年始终如一:生命周期安全、初始化安全、类型安全以及边界安全。 ```mermaid flowchart TD accTitle: 编程语言可解决的核心安全问题分类 accDescr: 展示自语言层面可控制的四项关键内存与类型安全属性 subgraph CoreSafety["编程语言可防御的核心安全领域"] A["生命周期安全 (Lifetime)"] B["初始化安全 (Initialization)"] C["类型安全 (Type Safety)"] D["边界安全 (Bounds Safety)"] end Vuln["Top 13 常见严重漏洞"] -->|"剥离业务逻辑与数据清洗问题"| CoreSafety ``` 随后,Herb Sutter 关掉幻灯片,神情严肃地与现场观众深入交流行业面临的严峻外部现实。自 2021 年初以来,整个行业的大环境发生了剧烈变化。美国总统乔·拜登于 2021 年 5 月签署了关于国家网络安全的行政命令,起因是一连串勒索软件袭击事件、针对选举基础设施的破坏,以及黑客勒索攻击直接导致医院停摆和石油管道断运。这类恶劣事件已经突破了容忍底线。为此,微软内部的团队以及许多参会者所在的企业,在过去一年中不得不投入巨大精力应对供应链安全和编程语言安全。 该行政命令要求美国商务部和美国国家标准与技术研究院(NIST)在 60 天内拿出提升软件安全性的官方指导意见。2021 年 7 月,美国商务部正式发布了指导意见文件,并在其中直接点名批评。Herb Sutter 逐字援引了报告中的原话: > “Some programming languages, such as C and C++, are not memory safe.”(部分编程语言,例如 C 和 C++,并非内存安全的。) 仅仅两句话之后,报告再次给出毫不客气的建议:“Where possible, do not use memory unsafe languages.”(在可能的情况下,不要使用内存不安全的语言。)Herb Sutter 强调,这是有史以来第一次由国家政府机构向工业界广泛发布此类指导方针,这实际上就是白纸黑字地要求工业界“只要条件允许,就不要使用 C 和 C++”。更进一步的是,政府正在追加资助,全面推动使用其他现代化语言重写低层基础设施和核心项目代码。 Herb Sutter 警示现场所有心存侥幸的工程师:不能再抱持 90 年代或 2000 年代那种漫不经心的心态了。过去几十年,C++ 社区仅仅靠加入智能指针、增加少许静态分析规则“得过且过地溜了过来”,但现在的世界已经全天候处于网络攻击之下。哪怕某个程序只是运行在联网冰箱这样的嵌入式设备中,如果缺乏安全修补通道,反而会成为最危险的攻击跳板。当政府监管部门已经下达最后通牒要求转移到其他语言时,C++ 社区必须正面迎战安全问题。 --- ### [44:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2670s) Cpp2 的安全理念:默认安全构建与友好诊断 面对安全挑战,Cpp2(及其实验性编译器前端 cppfront)采取的核心策略是:默认静态强制保证安全,实现“结构化保证安全”(Safety by construction)。Herb Sutter 在此前演示中展示了这一点:当编写类似 `s:= new string` 的语法时,系统默认推导并返回 `std::unique_ptr`,在语言结构层面上直接阻断了内存泄漏的可能。对于静态分析无法完全覆盖的场景,则辅以动态安全检查,但始终严格恪守 C++ “按需付费”(pay-for-what-you-use)和可见性原则,绝不强加不必要的运行时开销。 在编译器诊断设计上,Herb Sutter 提出了一个明确的设计目标:cppfront 的报错信息应当直接充当教学工具,默认强制执行社区多年来提倡的最佳实践。当开发人员写出潜在不安全的代码时,编译器会直接指出正确的做法。例如当开发者试图使用裸 `union` 时,cppfront 会直接抛出错误提示: > “don't use union, use std variant instead.”(不要使用 union,请改用 std::variant。) 这种做法的核心是通过工具默认推行既有的成熟规范,教会开发者采用正规、安全的现代 C++ 写法,从而在维持地道 C++ 语义的同时彻底封堵安全漏洞。 --- ### [46:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2760s) 统一类型查询与类型转换:is 与 as 的实战威力 关于类型安全,C++ Core Guidelines(核心指导方针)中已经列出了一整套详尽的类型安全规则与 Profile。在上一年的 CppCon 大会上,Herb Sutter 曾专门分享过利用 `is` 和 `as` 进行模式匹配的方案。在 Cpp2 的语法设计中,所有的类型查询全部统一用关键字 `is` 表达,而所有的类型转换(Casting)则全部统一通过关键字 `as` 完成。这一设计直接消除了传统 C++ 中五花八门、语法割裂的转换关键字(如 `static_cast`、`dynamic_cast`、`reinterpret_cast` 等)。 这种语法统一对于泛型编程至关重要。Herb Sutter 回顾了他去年演讲中那张带有“恶龙”的幻灯片,并指出:各种相互割裂的转换语法天生就是与泛型编程不兼容的。如果没有统一的语法,根本无法编写出高度通用的类型安全代码。 ```mermaid flowchart LR accTitle: Cpp2 中统合的类型查询与类型转换语法 accDescr: 展示传统 C++ 多种类型转换方式在 Cpp2 中统一简化为 is 和 as subgraph Traditional["传统 C++ 割裂的语法"] T1["typeid / dynamic_cast / holds_alternative"] T2["static_cast / dynamic_cast / any_cast / std::get"] end subgraph Unified["Cpp2 统一现代语法"] U1["is(类型查询)"] U2["as(类型转换)"] end T1 -->|"统一收敛为"| U1 T2 -->|"统一收敛为"| U2 ``` 为了证明其实用性,Herb Sutter 现场展示了一段通用测试函数代码。该函数内部采用了一个基于 `inspect` 的表达式来检查传入的参数 `x`: ```cpp // 概念代码演示:Cpp2 中的通用 inspect 模式匹配 test_generic: (x) -> void = { // 依然可以使用 C++ typeid 打印类型名称 std::cout << typeid(x).name() << ": "; msg:= inspect x -> std::string { is int = std::to_string(x as int); is std::string = x as std::string; is _ = "not an int or a string"; }; std::cout << msg << "\n"; }; ``` 虽然完整的解构模式匹配尚未彻底完成,但基础模式匹配已经稳定可用。更关键的是,这个函数是完全泛型的,开发者只需编写一次,便可以将其无缝应用于以下各种不同类型的数据结构: - 字面量与内置基本类型(如传入 `3.14`,模式匹配命中通配符 `_`,正确打印 `"not an int or a string"`) - `std::variant<int, double>`(初值为 `double` 时打印非 int/string;重新赋值为 `int` 后正确识别为 int) - `std::any`(存储字符串时能通过 `as std::string` 正确解包打印) - `std::optional<int>`(在未设置值的空状态下被判为 `"not an int or a string"`;一旦填充数值则准确识别为 int) Herb Sutter 借此展示了 C++ 现存动态类型机制与全新简洁语法的完美融合:通过简单的 `is` 和 `as`,原本繁复、分散在各类容器和语言底层里的类型检查与转换,被收拢到了统一且类型安全的形式之下。 ### [50:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3000s) 类型安全的宏大生成代码与消灭危险联合体(Union) Herb Sutter 打算在现场展示 GCC 环境下的实际运行情况,但遭遇了技术故障:Ubuntu 虚拟环境的窗口突然自行关闭了。他等了一会儿期望它恢复,却发现窗口直接消失无踪。Herb 试着重新启动,无奈地笑道:“肯定是刚装了更新,Ubuntu 今天就是不想工作,太奇怪了。”他表示随后可能会专门录制并发布在 Ubuntu 上运行演示的视频供大家自行尝试。随后他切回命令行运行 `cppfront` 编译器,指定 `-p`(即 `--pure` 模式),强调纯粹模式下不依赖外部导入或包含,完全直接使用 C++ 标准库内置类型来进行所有的动态类型派发。 ```mermaid flowchart TD accTitle: Cpp2 模式匹配与编译生成对比 accDescr: 展示用户在 Cpp2 中编写简洁的 inspect 语句,最终由 CppFront 生成庞大复杂的 if constexpr 模板分发代码。 subgraph Cpp2["用户代码(Cpp2 语法)"] A["简洁的 inspect 表达式"] B["强类型模式匹配"] end subgraph CppFront["CppFront 编译器"] C["直接引入并立即调用 Lambda"] D["大量展开 if constexpr"] end subgraph Cpp1["生成的 C++1 目标代码"] E["cpp2::as 转型与类型安全分发"] F["极其庞大的模板与分支实现细节"] end Cpp2 --> CppFront CppFront --> Cpp1 ``` 在展示生成的 C++1 代码时,Herb 指出 `main` 函数在语法一(Syntax 1)下看起来基本相似,原本的转型 `as` 变成了 `cpp2::as`。然而,当展示泛型函数中的 `inspect` 语法实现时,编译器生成了一个被立即调用的 Lambda 表达式。Herb 调侃道,虽然表面看起来简单,但只要看一眼右侧极小的滚动条,就知道这里是目前 CppFront 产生代码量最庞大的地方。当他在编辑器中按下 `Alt-Z` 自动折行时,密密麻麻的 `if constexpr` 分支铺满了整个屏幕,引来全场惊呼与欢笑。Herb 强调,这就是抽象的价值所在:“不是说我想看这个,恰恰相反,我不想要看这个实现细节。这才是我想写的代码,剩下的就作为实现细节存在吧。谢谢大家,这才是我想生活在其中的世界。” 为了彻底展示类型安全的决心,Herb 顺带演示了定义裸联合体(union)的后果。当他在 Cpp2 中尝试编译一个 union 时,编译器毫不客气地报错拒绝,并给出清晰的建议: - 不要使用 `union`; - 请改用 `std::variant`。 Herb 总结道:“这就是 C++ 本该拥有的样子,我也希望它最终能变成这样。” --- ### [52:16](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3136s) 边界安全机制:全面禁止裸指针算术与位运算 紧接着,Herb 转入对第二项安全维度——边界安全(Bounds Safety)的探讨。他展示了《C++ 核心指南》(C++ Core Guidelines)中的边界安全 Profile,表示自己所做的只是把核心指南现成的教学准则直接落实为默认机制。核心指南明确规定:“指针应当只指向单个对象,绝不要进行指针算术操作。”Herb 反问道,为什么现有的 C++ 不能直接强制执行这个规则?答案是这会直接破坏全世界的代码,包括所有现存的 C 语言互操作代码。 而在 Cpp2 这一全新语法中,由于不存在遗留代码包袱,Herb 可以直接彻底拒绝任何形式的指针算术操作(包括下标运算 `ptr[i]`)。此外,Herb 在实现时敏锐地注意到,现存的 C++ 竟然允许对裸指针执行位运算(例如按位取反 `~` 或异或 `^`)。他调侃道:“你们知道竟然可以对一个裸指针做一元按位异或、按位取反吗?现在你们知道了,但可别说是我教的。有时候你一句话刚开了个头就不得不说完,但心里其实后悔莫及,我为此向大家道歉。” 随后 Herb 在现场演示了一段典型的指针迭代代码。代码中定义了一个包含多个字符串的 `std::vector<std::string>`,并分别获取了首尾元素的指针: ```cpp words: std::vector<std::string> =...; first: *std::string = words.front()&; last: *std::string = words.back()&; ``` Herb 提醒大家,在 Cpp2 中大多数一元操作符(如取地址 `&`)都是后缀形式。这段指针赋值本身是合法的,真正的安全隐患在于开发者写出类似旧时代的循环迭代逻辑:“‘我要用指针迭代,哈哈,因为我知道我能行。至少这套做法我已经用了 40 年了,Kernighan 和 Ritchie 用着挺好,那对我肯定也足够好了,所以我就写个 `first++`。’但我们心知肚明这极其不安全,大量的漏洞报告都源于这种代码。我知道十次里有九次你能写对,但哪怕百分之一的概率写错,代价也过于高昂。” Herb 直言这种不安全代码最可怕的地方:“因为一旦你搞错了,你的程序能正常编译,没有任何警告,甚至看起来运行得很正常。那不是我想生活的世界。” --- ### [55:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3300s) 默认强制规范、消灭 delete 与微软漏洞分析报告 当 Herb 尝试编译这段含有 `first++` 的代码时,CppFront 立即给出了清晰的错误提示:禁止对指针进行自增操作,请改用 `std::span` 或 `gsl::span`。Herb 指出,CppFront 的错误诊断始终贯彻一个核心模式:**默认强制执行业界现已推行的最佳实践,并在报错时明确告知开发者正确的替代方案**。对于推荐非标准库设施的情况,Herb 解释说,编译器错误信息中推荐非标准的设施仅有两处,即 `gsl::span` 和 `gsl::narrow`,而这纯粹是因为标准库目前尚未完全跟进: - `std::span` 与 `gsl::span` 的唯一差别在于,`gsl::span` 保证严格强制执行边界检查(Bounds Checking); - 当初 ISO C++ 委员会标准化 span 时,如果强制要求了边界检查,Core Guidelines 团队原本会彻底弃用 `gsl::span`;正因委员会没有强制要求,才保留了 `gsl::span`。 紧接着,Herb 取消了其他几种指针数学计算操作的注释,证明哪怕使用按位运算取反 `~ptr`,编译器也会明确拦截并建议使用 `std::bit_cast`。此外,Herb 还顺带展示了对裸指针执行 `delete` 的报错表现。在 Cpp2 中,`delete` 和具有所有权的裸指针直接被废除: - 明确禁止对指针调用 `delete`; - 必须按优先级使用现代资源管理方案:优先选择 `unique.new`,其次是 `shared.new`,最后是 `gc.new`(垃圾回收分配,将在演讲末尾介绍)。 ```mermaid flowchart LR accTitle: Cpp2 内存与边界安全替代演进路径 accDescr: 展示从危险的原生 C 风格操作转向安全现代 Cpp2 推荐特性的对应关系图。 subgraph Unsafe["淘汰的旧语法与危险操作"] U1["指针算术 (ptr++, ptr + n)"] U2["裸指针 delete"] U3["指针位运算 (~ptr)"] U4["裸联合体 (union)"] end subgraph Safe["现代 Cpp2 强推的安全机制"] S1["std::span / gsl::span"] S2["unique.new / shared.new"] S3["std::bit_cast"] S4["std::variant"] end U1 -->|"强制替换"| S1 U2 -->|"强制替换"| S2 U3 -->|"强制替换"| S3 U4 -->|"强制替换"| S4 ``` 随后,Herb 将代码重构为使用范围安全的写法:创建 `s:= std::span(words)`,利用类模板参数推导(CTAD)无需显式声明模板实参。在此处展示循环重构时,Herb 顺便提到 Cpp2 的语法清理成果:现在所有的循环结构(包括 `for`、`do`、`while`)都原生支持 `next` 步进子句。 在准备结束这部分演示时,Herb 分享了一段与微软安全专家 Joe Bialik 合作的内部真实经历。大约四年前,Herb 与 Joe 耗费时间深入审查了微软过去一整年中所有记录在案的、能够合法调阅源代码的安全漏洞报告,特别是 Windows 操作系统的核心源码。他们逐一剖析导致漏洞的根本原因,得出了一个惊人的结论:“在那组特定的真实数据集中(虽然不能代表所有情况),Joe 和我发现:如果我们当时普遍使用了带边界检查的 span,那么每一个边界安全漏洞在当时都会被当场捕获并阻止。” “因此,边界安全其实是这四种安全维度中最容易解决的一个:只要在 C++ 中使用带边界检查的 span,就能帮你搞定大约 99% 的边界安全问题。”但就在演讲前两周,当 Herb 与 Joe 再次核对细节时,Joe 提出了一个敏锐的现实观察:大约有三分之二的代码场景中,容器本身就在当前作用域内,开发者所做的仅仅是对它进行遍历。针对这种场景,使用 span 依然显得有些不够自然。 - 微软 Windows 源码安全漏洞审查证实,严格带边界检查的 span 能够消除当年数据集中几乎 100% 的边界越界漏洞。 - Cpp2 统一了循环控制结构,使 `do`、`while` 和 `for` 均可附带 `next` 执行语句。 - 诊断信息中推荐的替代项遵循最小惊讶原则:推荐现存标准设施,仅在标准库缺乏安全保障时才推荐 GSL。 ### [01:00:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3600s) 突破标准库限制:在编译器端为原生容器实现边界安全检查 Herb Sutter 谈到,为了满足静态分析工具的检查而凭空引入一个像 `std::span` 这样的临时变量,显得既突兀又别扭。如果原生容器自带的下标操作符(`operator[]`)本身就能直接进行边界检查,岂不是好得多?他坦言自己过去也觉得要求开发者写多余的变量“有点勉强(a little hokey)”,尽管安全性的价值无可辩驳。大约两三周前,在考量了 cppfront 的提交历史后,Herb Sutter 产生了一个念头:能不能在不包装、不封装(no thunking, no wrapping, no marshalling)、不破坏标准库无缝互操作的前提下,直接给原始容器加上边界检查?“但接着我想,我手里有一个编译器。我想我知道怎么做到这一点了。(But then I thought, I have a compiler. I think I know how to do this.)” 在现场演示中,Herb 写了一段使用 `std::vector<std::string>` 遍历单词的代码,并故意插入了一条打印语句访问“第三个词”,下标写成了 `words[3]`。全场听众起初有些发愣,随后陆续举手识破了 bug——对于一个只有 3 个元素的容器,`[3]` 访问的其实是第 4 个元素。在默认编译下运行,虽然产生了越界访问,但程序并未崩溃而是侥幸执行过去了。 Herb 接着演示了 cppfront 的命令行开关 `-s`(Subscript checks),启用下标边界安全契约检查;重新编译运行后,程序立刻拦截了越界行为,精准报告了“检测到越界访问尝试(Bound safety violation, out-of-bounds access attempt detected)”。 ```mermaid flowchart TD accTitle: 下标边界检查转换流程 accDescr: 展示 cppfront 编译器如何将普通下标操作符重写为带断言检查的 C++ 代码 subgraph S1["Cpp2 源代码"] A["下标访问: words[3]"] end subgraph S2["cppfront 编译期分析"] B{"下标为整型<br/>且容器拥有.size()?"} end subgraph S3["生成的 Cpp1 代码"] C["cpp2::assert_in_bounds(words, 3)"] D["内部调用: bounds.expects(...)"] E["触发违规处理或安全访问"] end A --> B B -->|"是"| C C --> D D --> E ``` - **源码定位选项**:搭配 `-s` 还可以开启 `-a`(add source info,Herb 开玩笑说连起来像 PSA/公共宣传广告)。在 Visual C++ 支持下,运行报错能够直接打印出错的文件与行号:`demo.cpp2, Line 5 in function main, bound safety violation`。 - **底层转换原理**:cppfront 会检测所有针对具有 `.size()` 成员函数的对象进行整型下标访问的操作,并自动将其包装为 `cpp2::assert_in_bounds` 调用,对 `std::vector`、`std::array`、`std::string`、`std::span` 通用,完全无需修改或包装标准库容器。 --- ### [01:05:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=3900s) 契约分组系统与自定义错误处理钩子 Herb Sutter 进一步拆解了 `assert_in_bounds` 的实现机理。它在内部调用了契约接口 `bounds.expects(...)`。这套契约系统借鉴了 C++ 委员会 SG21 的设计思路,但在实现上具有独特的工程考量:每一个契约类别都是一个具名的对象实例。Herb 在库中预置了数个契约分组,例如处理类型错误的 `type.expects`、负责越界的 `bounds.expects` 以及处理空指针的 `null.expects`。 Herb 解释这一设计的根源在于现实工程需求。在与微软同事 Joe Bialik 沟通以及收集来自 Windows 等产品团队的反馈时,核心诉求之一就是**差异化开关策略**:像内存越界这样的严重安全隐患,在生产环境中是无论如何都绝不能关闭检查的;而用于常规业务逻辑的通用契约,团队则希望在发布模式下剥离以换取性能。“这就是为什么你需要粒度控制(granularity)。”Herb 采用的方案是将每个契约类别做成独立命名的变量对象,并在每个对象上提供类似 `std::set_terminate` 的自定义钩子函数 `set_handler`。 - **自定义错误拦截演示**:Herb 在现场编写了一个 `call_my_framework` 函数,通过一行代码 `cpp2::bounds.set_handler(&call_my_framework)` 挂载到越界契约组中。程序再次越界时,不再直接调用默认的崩溃终止(terminate/assert),而是转入自定义的回调函数中执行用户逻辑。 - **现阶段成果总结**:所有带 `.size()` 的标准类型(如 `vector`、`string`、`array`)均可在完全零封装的前提下实现自动边界检查,且违规动作可通过命名契约组自由定制拦截行为。 --- ### [01:07:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4050s) 生命期安全与摒弃空指针的语言实验 在完成了边界安全的介绍后,Herb 转向了 C++ 另一大痛点:生命期安全(Lifetime Safety)。虽然 C++ Core Guidelines 中包含生命期静态分析 Profile,但 cppfront 尚未全量实现它。Herb 此时在 cppfront 中做了一个激进的“实验中的实验(experiment within an experiment)”——探索在 C++ 语言中是否真的必须保留空指针(null pointers)。 “如果我们根本不提供空指针会怎样?或者至少,在 Cpp2 中没有任何你可以设置为空的指针?(What if there weren't any? Or at least no pointers you could set to null in CPP2)”Herb 承认如果在当下的 Cpp1 中强推这一点会“把整个世界搞崩(break the world)”,但空指针的技术缺陷显而易见:它是指针能取到的最危险、最糟糕的值,讽刺的是它却往往是指针的默认状态。Herb 借此引用了 Kate Gregory 的经典演讲标题《当你什么都不说时你说了什么》(What you say when you say nothing at all):如果一个局部指针未显式初始化,开发者实际上得到的是无法安全解引用的野值或空值。 在现场编码验证中,Herb 尝试在 Cpp2 代码中声明一个指向 `std::string` 的指针并将其赋予 `nullptr` 或整型 `0`。cppfront 编译器直接拒绝编译并报错:“指针不能被初始化为 null 或 int(Pointer cannot be initialized to null or int)”,展示了从语言源头切除空指针隐患的激进约束。 ### [01:10:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4200s) 杜绝空指针与未初始化变量的使用 Herb Sutter 现场演示了如何在代码中尝试规避编译器的空指针检查。如果程序员试图把指针初始化为 `nullptr`,编译器会直接拦截;而如果自作聪明使用 C++ 程序员的老套路——写成 `0`,甚至尝试传入 `42`,编译器同样能够识别并报错,因为编译器实现者早就对这些花招了如指掌。更有甚者,如果在 Cpp2 代码里写了宏 `NULL`,编译器会抛出一个截然不同的错误:“You can't even say null anywhere in CPP2. That's not because it's pointer initialization. That's because it's a bad word.” Sutter 半开玩笑地表示,这就好比说脏话一样,在 Cpp2 中根本就不允许使用这个词。 随后,Sutter 转向了生命周期安全与初始化安全的核心议题。在 Cpp2 中,声明一个未初始化的局部变量本身是被允许的,例如写下 `p: *std::string;` 而不赋予初值。但这绝不意味着不安全,因为 Cpp2 保证了“使用前必须初始化”(Initialization Before Use)。编译器会先分配对应的内存空间,然后静静等待后续的构造调用。 ```mermaid flowchart TD accTitle: Cpp2 编译期初始化安全检查 accDescr: 展示未初始化变量在被使用前如何通过路径分支检查以确保安全 subgraph Declaration ["变量声明"] A["局部变量声明: p: *std::string;"] --> B["分配内存空间 (等待显式构造)"] end subgraph BranchAnalysis ["静态分支路径分析"] B --> C{"所有分支均完成初始化?"} C -->|"是 (例如 if 与 else 均赋值)"| D["通过安全检查: 允许后续解引用/读取"] C -->|"否 (仅在 if 中赋值)"| E["编译报错: 违反初始化安全保证"] end ``` 现场演示中,当代码试图在未初始化的情况下直接解引用 `p` 时,`cppfront` 输出了极其精准的错误信息:“Local variable p is used before it was initialized. Line 14: program violates initialization safety guarantee.” 紧接着,Sutter 尝试给代码加上一个随机条件 `if (std::rand() % 2)` 来初始化 `p`,但不写 `else` 分支。编译器立刻指出:该变量在某些执行路径上未被初始化就被使用了。Sutter 邀请全场对此类清晰的报错信息鼓掌,并幽默地指出,掌声不是为了满足个人虚荣心,而是为了让正在观看演讲视频的各大 C++ 编译器实现者听到用户的迫切需求。前排的 Dietmar 随即打趣起哄,Sutter 笑着回应正是英雄所见略同。只有当补齐 `else` 分支、使得所有执行路径都完全覆盖了对 `p` 的赋值后,编译器才予以放行。 ### [01:14:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4440s) 偶然复杂度与“确定首次/末次使用”模型 这一现象顺理成章地引出了更为根本的“初始化安全”(Initialization Safety)。Sutter 回顾了两年前关于参数传递和初始化的演讲,重申了 Fred Brooks 在《人月神话》中对“本质复杂度”(Essential Complexity)与“偶然复杂度”(Accidental Complexity)的经典划分: - **本质复杂度**:例如在面向对象中表达一个函数是虚函数或多态函数,这种语义是核心需求,不可或缺; - **偶然复杂度**:在现行 C++ 语法中表达虚函数时那些冗长、晦涩的仪式性语法(Ceremony),这完全可以被改进甚至剔除。 Sutter 透露,他曾花费数月时间整理了一份超过 600 行的庞大 Excel 表格,地毯式梳理了 Scott Meyers 系列专著、Andrei Alexandrescu 与自己的合著、High Integrity C++ 以及 Abseil 等权威代码规范中的每一条细则与段落建议。在区分本质复杂度与偶然复杂度时,Sutter 设定了一个极其严苛的试金石标准:“That was my litmus test for I called it accidental and improvable or essential and improvable only if I had an answer, not because I was being a couch critic, an armchair quarterback.” 只有当他确实拿出了具体的工程解决方案时,他才会将某项规则归类为“可改进的偶然复杂度”。 统计显示,在所有这些规范中,**初始化安全**与**参数传递**占据了近四分之一的篇幅。每本 C++ 教程都在不厌其烦地重复这些内容,正说明现行语言在此处的繁琐。为了解决该问题,Sutter 重新阐释了“确定首次使用”(Definite First Use)与“确定末次使用”(Definite Last Use)的概念: - **确定首次使用**:若在进入函数的任何执行路径上,某行代码必然是第一次接触该变量,则该点即为确定首次使用; - **确定末次使用**:若控制流经过某处后,后续代码在任何情况下都不会再触碰该变量,该点即为确定末次使用; - **分支与循环特征**:C# 与 Ada 都有类似的控制流路径分析机制。在计算时,分支语句必须保证覆盖所有分支;而循环体则不能作为确定首次使用的位置,因为循环体可能根本不会执行(类似于只写了 `if` 而没有 `else`)。 ### [01:17:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4620s) 递归分支规则与生成的下层实现代码 随后,Sutter 展示了将初始化安全应用于常规类型(如 `std::string`)的实例。代码中声明了未赋值的局部变量 `x`,并在其后尝试读取它。此时现场发生了一个程序员熟悉的滑稽插曲:Sutter 编译后发现编译器没有如期报错,定睛一看才发现自己刚才改完代码忘记按保存键(Ctrl+S),引来台下一片会心的笑声。 保存文件后重新编译,编译器立刻给出了与此前指针示例完全一致的严谨报错:变量 `x` 在初始化之前被使用,违反了初始化安全保证。Sutter 指出,即便面对层层嵌套的 `if` 结构,检查逻辑依然高度自洽: > “You either initialize on both branches or neither branch. It's a delightfully recursive rule once you think about it.” 在测试跨分支运行阶段,由于 Microsoft 编译器的模块导入(Module Import)目前存在无法正确识别 `<time.h>` / `time` 的已知问题,Sutter 无法像原计划那样利用系统时间奇偶秒交替展示双分支的运行,于是退回使用 `std::rand() % 2` 驱动分支选择。 最后,Sutter 将 Cpp2 源代码与生成的 C++1 目标代码并排展示,揭示其底层运作机制: - 对于未赋初值的局部变量,Cpp2 编译器会生成一个专属的包装容器 `cpp2::deferred_init<T>`。 - 该类型会精准分配满足目标类型大小与对齐要求的裸内存空间,但暂不调用任何构造函数。 - 当后续代码执行赋值初始化(`=`)时,编译器将其直接转换为在既有内存上的原位构造调用(`construct`)。 - 同样,当变量通过函数调用进行初始化时,Cpp2 支持 `out` 参数语义(在底层体现为 `cpp2::out<T>`),使得一个未经初始化的对象可以安全地作为传出参数送入函数完成构建。 ### [01:20:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4800s) 组合式初始化安全与 Out 参数的真正威力 在处理未初始化变量时,Cpp2 能够在静态编译期获知变量是被完整值初始化,还是处于该类型的延迟初始化(deferred initialization)状态。在函数体内,变量的首次赋值会被编译为就地构造;若变量此前已被构造,则后续赋值会退化为普通的赋值操作。Herb Sutter 强调,这种机制为 C++ 带来了前所未有的“可组合的初始化安全”(composable initialization safety)。在日常开发中,程序员经常在栈上分配一块原始缓冲区(Buffer)或 `std::array<std::byte>`,随后立即调用 `fill_from_database`、`memset` 或 `memcpy` 进行填充;以往为了安全,人们往往被迫先写一行垃圾值进行全零初始化(zero initialize),但这会产生根本没有实际意义的无效写入(dead writes),而编译器在消除这些无效写入方面的优化表现极差。 ```mermaid flowchart TD accTitle: Cpp2 延迟初始化与 Out 参数数据流 accDescr: 展示未初始化变量通过 out 参数安全传递并在函数内就地构造的过程 subgraph Caller["调用方上下文"] A["声明未初始化缓冲区: buf"] --> B["调用 fill_data(out buf)"] B --> C["buf 获得完整意义的值并安全使用"] end subgraph Callee["被调用方 fill_data(out b)"] D["静态检查:保证所有分支必须赋值"] --> E["首次赋值: 调用底层 construct"] E --> F["返回给调用方"] end B -.->|"传递 deferred init 状态"| D F -.->|"保证已安全构造"| C ``` 借助统一的规则,Cpp2 让指针、字符串、整型、大型缓冲区以及宽泛的 POD 类型享有完全一致的初始化保障,并且这一机制能够无缝跨越参数边界组合传递。Herb Sutter 特别谈到社区对参数传递模式的误解,并为 `out` 参数正名: > “When I've talked to people about parameter passing, it is very common for people to say, ‘Oh, I like all of what you're doing here, except I question the value of out parameters.’ And they're actually tied for the most important of all six of them. in and out are the most important.” (“当我与人们讨论参数传递时,非常普遍的反馈是:‘哦,你这里做的所有东西我都喜欢,但我怀疑 out 参数到底有什么价值。’但事实上,在所有六种模式中,它们并列最为重要——in 和 out 是最重要的。”) Herb 进一步指出,`out` 参数之所以关键,是因为它赋予了程序“组合式初始化”的能力,强制要求变量必须用具备实际业务意义的值进行初始化,从而根除了“先随便赋个假值,以后想起来再真正初始化”的陋习,因为这种假值初始化往往会欺骗并遮蔽静态检查与 Sanitizer 工具的检测。从概念上看,任何接受 `out` 参数的函数本质上都变成了一个安全的委托构造函数,将对象的构造动作以静态保证、动态执行的方式委派出去,从而彻底泛化了初始化的概念。 - 避免无效死写:消除栈缓冲区为了形式安全而进行的冗余零初始化,减轻编译器优化负担。 - 静态委托构造:接受 `out` 参数的函数在语义上充当构造器角色,编译期强制检查所有执行路径必须完成构造。 - 工具链友好:不再有虚假的哑值填充,使未初始化检测和 Sanitizer 工具发挥真正作用。 --- ### [01:22:30](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=4950s) 多具名返回值与 Cpp1/Cpp2 丝滑互操作 为了展示组合式初始化的另一项优雅应用,Herb Sutter 现场演示了 Cpp1 与 Cpp2 混合代码中的多具名返回值(Multiple Named Return Values)。在 Cpp2 中,函数签名可以直接声明返回多个带名字的类型,例如 `f: () -> (i: int, s: std::string)`。在被问及有多少人曾长年期盼 C++ 能原生支持该功能时,台下观众纷纷举手。随后 Herb 现场通过预处理器生成底层 C++ 代码,向大家解构背后的编译器实现技巧。 生成的 C++ 代码会在底层自动合成一个返回结构体,为了不污染用户的命名空间,Herb 戏称自己作为语言实现者享有使用双下划线的特权,因此结构体名称带有 `__` 修饰前缀。在传统的 C++ 中,如果开发者手动声明此类返回结构体,无法将其直接内联在函数声明里,必须提前在函数外部定义并打上分号,这会造成命名空间污染。而在 Cpp2 的实现中: > “I just generate it for you, and I don't litter your namespace because I double underbar it. And then, but here's the point I'm getting to. All I then do is create two local variables of exactly the same type you just saw. They're just ordinary local variables with those names.” (“我直接为你生成它,并且由于我使用了双下划线,我根本不会污染你的命名空间。不过,这正是我要讲的重点:随后我要做的,仅仅是创建两个与你所见类型完全一致的局部变量。它们就是带有这些名称的普通局部变量。”) 这两个局部变量严格遵循相同的初始化规则:在第一次赋值时就地构造,并在函数返回路径上被自动标记为 `std::move`。函数结尾只需写一句 `return;`,无需再重复罗列变量名,编译器就会将其打包并移动返回。更妙的是,当 Cpp1 代码调用该 Cpp2 函数时,现代 C++ 的结构化绑定(Structured Bindings)可以开箱即用,写成 `auto [i, s] = f();` 即可完成解包。这彻底消除了以往返回匿名 `std::tuple` 导致的类型可读性差、索引容易弄错的问题。 - 消除命名空间污染:生成的打包返回结构体采用内部隐藏命名,免去手动定义结构体的样板代码。 - 自动生命周期转移:在返回点由转译器自动插入 `std::move`,开发者只写 `return;` 即可保证无拷贝开销。 - 零成本跨语言版本互操作:Cpp1 的 `main` 函数直接通过 C++17 结构化绑定接收 Cpp2 函数的具名多返回值。 --- ### [01:25:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5100s) Cpp2 的六种参数传递抽象与生命周期自动化 在演示完 `out` 参数之后,Herb Sutter 逐一剖析了 Cpp2 体系中另外五种明确表达调用意图的参数传递方式:默认的 `in`、`copy`、`inout`、`move` 以及 `forward`。这六种模式抹平了底层传值还是传引用的琐碎心智负担,由编译器根据类型大小与调用语境在底层自动选择最优机制。 ```mermaid flowchart LR accTitle: Cpp2 参数传递抽象模式 accDescr: 展现 Cpp2 中六种参数传递模式及其在生成的现代 C++ 底层中的映射机制 subgraph Params["Cpp2 参数抽象"] P1["in (默认)"] P2["copy"] P3["inout"] P4["move"] P5["forward"] end subgraph Lowering["转译至 C++ 底层"] L1["cpp2::in<T><br>(<=2 指针宽传值, 否则 const&)"] L2["按值传递 + 确定性最后使用自动 move"] L3["常规非 const 引用 (&)"] L4["右值语义 + 调用端强制显式 move 声明"] L5["auto&& + requires 约束具名转发"] end P1 --> L1 P2 --> L2 P3 --> L3 P4 --> L4 P5 --> L5 ``` 每种参数模式的具体实现和规则如下: 1. `in`(默认):映射为 `cpp2::in<T>`,底层自动赋予 `const` 约束。对于大小不超过两个指针且可平凡复制(Trivially Copyable)的轻量类型自动采用值传递,其余类型自动降级为 `const&`。开发者再也不用纠结到底该写 `T` 还是 `const T&`。 2. `copy`:明确声明需要一份副本。在函数体内,转译器会追踪变量生命周期,并在变量的“最后一次确定使用”(definite last use)处自动插入 `std::move`;若最后一次使用涉及取地址操作(`&`)或自增自减(`++`/`--`),转译器会智能识别并抑制移动,防止编译报错。 3. `inout`:直接等价于非 const 引用传递,但声明了强烈的就地修改意图。 4. `move`:表示所有权转移。在函数体内同样会在最后一次确定使用时隐式移动;在调用端,则强制要求开发者写出 `move(arg)` 显式表明让渡所有权。在 Cpp2 中,标准库的 `std::move` 语法被直接禁用。Herb 借此感叹道: > “You say move only in these places on the left, which means I never again, I hope, have to teach. Why does std move not move? Well, it does now. Why shouldn't I say return std move? Well, no. You can't, so I don't have to teach that. That's the kind of world I want to live in through my C++ code.” (“你只需在左侧这些特定位置写 move,这意味着我希望自己以后再也不用去教学:‘为什么 std::move 并不发生移动?’好吧,现在它真的移动了。‘为什么我不应该写 return std::move?’好吧,在 Cpp2 里你根本写不了,所以我再也不必费口舌去教这些了。这才是我想通过 C++ 代码身处的世界。”) 5. `forward`:在 C++20 的支持下,它被转译为一个带有 `requires` 概念约束的 `auto&&` 通用引用参数,约束推导类型必须与目标类型一致(如 `std::string`)。这种方式打破了以往完美转发仅能应用于模板参数的限制,实现了对具体具名类型的完美转发,并在最后一次使用处自动实施 `std::forward`。 在剖析完参数机制的结尾,Herb 引出了统一的函数声明语法:无论是具名函数还是作为表达式实参的匿名 Lambda,在 Cpp2 中都统一采用前置名称(或留空)加冒号的同构声明法则。 - 参数类型自动化:`in` 参数根除关于传值还是传常引用的长期无谓争论,交给编译器依据 ABI 最优规则选择。 - 静态消除经典陷阱:禁止手动调用 `std::move`,杜绝了多余的 `return std::move(x)` 破坏命名返回值优化(NRVO)的问题。 - 具名类型完美转发:借助 C++20 Concepts `requires` 语句,使普通非模板具名类型同样享有透明且安全的完美转发能力。 ### [01:30:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5400s) Lambda 捕获的重塑与统一语法哲学 在讨论完 `std::ranges::for_each` 时,Herb Sutter 指出大家今天称之为 Lambda 的东西,在他看来其实就是一个“函数表达式”(function expression)。在底层,它遵循完全相同的文法规则,走的是语法分析器里的同一个函数。甚至范围 for 循环(range-for loop)的循环体本质上也是一个函数,因为它是语言中唯一一个具有“以不断变化的参数重复调用某段代码”语义的地方。Herb 调侃道,听他讲了一个半小时“所有东西的语法都是一样的”,台下一定有人在想:“那捕获(capture)怎么办?Herb 是不是把事情过度简化了,毕竟你总归需要 Lambda 捕获吧?”Herb 坦承确实需要捕获,但并不意味着需要一套完全不同的函数声明语法来实现它。 ```mermaid flowchart TD accTitle: Cpp2 中统一的捕获机制应用 accDescr: 展示 Cpp2 如何将相同的捕获语法应用于 Lambda 表达式、合约后置条件以及字符串插值三种场景。 subgraph CoreSyntax["统一捕获机制 $ 语法"] CAP["值捕获语法:$"] end subgraph UseCases["三大应用场景"] L["Lambda 表达式<函数表达式内部>"] P["后置条件 Postconditions<函数入口捕获,出口求值>"] S["字符串插值 String Interpolation"] end CAP --> L CAP --> P CAP --> S ``` Herb 将这个灵感的来源归功于 Epic Games 的 Tim Sweeney。几年前 Herb 坐在 Tim 的办公室讨论语言设计时,Tim 提出:对于 Lambda,为什么不干脆总是在函数体内部捕获?而且永远按值捕获(capture by value)。如果开发者想要指针,显式获取地址并捕获该指针即可。尤其是在 Cpp2 已经具备指针安全和初始化安全的前提下,这种设计变得极为合理。Herb 采纳了这一想法:捕获只允许在表达式函数内部使用,但其语法骨架与普通代码完全相同。 紧接着,Herb 引导观众思考:在现有或拟议中的 C++ 中,还有哪些场景类似捕获——即“捕获某物并在稍后执行”?台下有人提到了协程(coroutines),而 Herb 重点展示的是合约中的后置条件(postconditions)。在 Cpp2 中,后置条件写在函数顶部(概念上是进入函数时),但在函数正常返回退出(非异常退出)时才执行。如果一个 `push_back` 操作的后置条件是“新集合大小等于旧集合大小加 1”,开发者只需在后置条件中直接捕获旧的大小值(如 `collection.size$`),待退出时求值校验。更妙的是,字符串插值(string interpolation)也复用了完全相同的捕获语法。Herb 强调,变量、类型和函数都以同一种方式声明,捕获机制也是唯一的,这样程序员才能摆脱语法泥潭,专注于业务逻辑: > “I have not said the word monad once.” —— Herb Sutter Herb 强调,整场演讲没有引入其他语言中晦涩难懂的外来概念(比如 Monad),讨论的全部是 C++ 程序员日常熟悉、深入骨髓的思维模型与术语。 - 统一的捕获语义:默认按值捕获,若需引用语义则显式取地址捕获指针。 - 捕获语法在 Cpp2 中打通了三处使用场景:表达式函数(Lambda)、合约后置条件(postconditions)以及字符串插值(string interpolation)。 - 统一声明原则消除心智负担:统一的实体声明模型让变量、类型、函数拥有对称的形式。 --- ### [01:32:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5565s) Cpp2 演进路线图与中远期社区协作计划 Herb 坦言 Cpp2 仍是一个正在进行的实验,还有大量工作亟待完成。他按实现难度从低到高梳理了接下来的开发路线图:首先是显式参数列表(explicit parameter lists);其次是基于 D708 提案将构造函数与赋值操作符通过 `out` 参数进行大一统的类机制;再往后是基于解析树(parse tree)的代码反射生成与元类(metaclasses),以便规范地进行代码生成而非简陋的文本替换;最艰难的挑战则是轻量级异常(lightweight exceptions),需要介入堆栈展开(unwinding)逻辑并实现确定性耗时(deterministic time)的抛出与捕获,目前他正在与编译器实现团队深入探讨。 针对希望参与 Cpp2 实验的开发者,Herb 列举了若干具体的协作项目: 1. **编辑器语法高亮**:VS Code 的 C++ 语法文件极其复杂,Herb 向微软官方高亮语法作者请教如何增加关键字和正则匹配时,对方直言这极其困难并推荐去论坛求助,因此急需语法文件专家支持。 2. **统一函数调用语法(UFCS)下的自动补全**:探索在输入 `obj.` 时如何通过智能补全自动索引并呈现非成员函数。 3. **Compiler Explorer (Godbolt) 支持**:让用户在网页端体验 Cpp2,并能自由指定下层编译为 Cpp1 代码时调用的后端编译器。 4. **垃圾回收内存 Arena (GCPP)**:Herb 重申 Cpp2 绝不会把所有东西变成垃圾回收,但在 Arena 内存分配模型中,应当包含一个零开销原则的、可选的(opt-in)GC 内存泡(bubble)。Herb 六年前在 CppCon 展示过原型,但缺乏拓展性,期待真正的 GC 专家协助设计。 5. **现代终端绘图库 (Cpp2 draw)**:一个支持键盘和文本交互的基础图形 API,类似于 21 世纪的现代版 `curses` 或 `<conio.h>`,最好是 header-only 实现。 6. **逆向源码迁移工具 (front-cpp)**:长期来看,利用主流编译器的 Pretty-Printer 将现有的 Cpp1 代码转换为 Cpp2,自动识别陈旧代码中的设计模式并直接改写为 Cpp2 的第一类语言特性。 Herb 充满激情地向全场发出呼吁: > “What if we could do C++ feels like a new language again, like we did in C++11? It feels like it, but it is still C++.” —— Herb Sutter 他强调 Cpp2 的目标绝不是取代或对抗 ISO C++,而是拥抱 C++20/23/26、默认推行 `import std;`,以源码编译回现存 C++ 的方式保持务实与诚实,并将实践检验出来的优秀特性反哺给 ISO C++ 委员会。 --- ### [01:38:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=5880s) 抛弃历史包袱:Cpp2 消除的复杂性清单 在演讲进入尾声的总结对比中,Herb 逐项展示了如果 Cpp2 取得成功,未来 C++ 程序员将永远不必再学习、讲解或纠结的传统语言包袱。在对比表格中,原本在 Cpp1 侧充斥着大量繁文缛节的项目,在 Cpp2 一侧直接变为了空白: | Cpp1 的传统历史包袱 | Cpp2 的解决方案与消除效果 | |:--- |:--- | | **预处理器与头文件**(宏、`#include` 选头文件、声明顺序依赖) | 彻底消除。模块化默认导入(`import std;`),消除声明依赖,无需预处理器。 | | **混乱的声明语法**(令人困惑的解析 Most Vexing Parse、顺时针/从内向外解析法、非成员 vs 成员 vs Lambda 语法分歧) | 彻底消除。统一声明语法(`name: type = value`),语法解析零歧义。 | | **const 放置争议**(East const vs. West const) | 统一放置规则,终结无休止的编码风格争论。 | | **参数传递心智负担**(值传递 vs. `const&` 传递抉择) | 引入语义化的参数修饰符(`in`, `copy`, `out`, `inout`),由编译器决定底层传递技术。 | Herb 看着那些变为空白的列表感叹,没有了宏与头文件的依赖排布、没有了 Most Vexing Parse、消除了由内而外的倒装声明形式、解决了传值还是传引用的选择困难,C++ 开发者终于能从语言防御性编程的陷阱中抽身,让这门语言重获新生。 ### [01:40:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6000s) 繁重特性的减法与 Bjarne Stroustrup 的 10 倍假说 Herb Sutter 逐一清点了现代 C++ 中那些充斥着规则特例、给教学与心智带来巨大负担的概念。他谈到引用(references)时直言,引用最初被引入语言仅仅是为了参数传递,但后来却蔓延至整个类型系统,迫使开发者在各种不相关的场景中不断为其填坑修补。此外还有许多令人头疼的教学难点:`std::move` 并不真正发生“移动”、为何容易被滥用、为何不能在函数返回时写 `return std::move(...)`、为何在模板参数类型下的 `T&&` 根本不是右值引用而是转发引用(forwarding reference)、以及如何拼写并实现完美转发(perfect forwarding)。在返回值优化方面,cpp2 采用具名返回值语法后,具名返回值优化(NRVO)便水到渠成,不再需要向初学者费力解释如何触发 NRVO,亦无需在破坏命名空间的独立结构体与匿名元组(tuple)的缺陷之间做权衡。 ```mermaid flowchart TD accTitle: Cpp2 简化理念架构对比 accDescr: 对比传统 C++ 繁杂的特例规则与 Cpp2 自动消除复杂度的设计路径。 subgraph Legacy["传统 C++ 教学与心智负担"] L1["引用的泛滥与全类型系统渗透"] L2["std::move / 完美转发特例"] L3["手写 NRVO / new / delete / 裸指针与空指针"] end subgraph Cpp2["Cpp2 试验性进化方向"] C1["仅按需表达意图(如参数传递语义)"] C2["自动 NRVO(具名返回值)"] C3["消除所有权裸指针与空指针解引用隐患"] end L1 -.->|"简化与约束"| C1 L2 -.->|"内建机制替代样板"| C2 L3 -.->|"默认安全防范"| C3 ``` 谈及底层内存管理,Herb 发出感叹:“`new`、天哪,`new`、`delete`、拥有所有权的裸指针、内存泄漏;0 既是整型又是指针;还有 `NULL`,别让我提它了,空指针解引用与指针算术越界……”他向全场观众提问:有多少人觉得刚才列出的这些繁琐知识点似曾相识?台下纷纷举手。随后他追问道,是否有人愿意参与一场实验,在保留 C++ 全部性能与表达力的同时,彻底丢弃这些右侧的繁复规则?全场观众几乎全员举手赞同。Herb 指出,Bjarne Stroustrup 曾提出过一个著名假说:C++ 有潜力做到比现在“简单 10 倍、安全 10 倍”。Herb 郑重表示: > “My thesis is that Bjarne is right, that he was right all along, and I'm giving it a go to see if I can prove it by actually implementing it, because I love C++.” - 教学难点清单:`std::forward` 拼写与非模板类型转发、前缀后缀运算符的额外括号、五花八门的类型查询、占据整整两个段落的各种类型转换语法、以及杂乱的 lambda 捕获语法。 - 核心愿景:探索 5 年或 10 年后的 C++,能否让开发者在编写代码时拥有更高的确定性、更低的教学与学习成本、极高的简洁性,并享有与其他现代语言平起平坐的工具链支持。 --- ### [01:43:08](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6188s) 现场突发喜讯与零开销抽象原则的探讨 在进入问答环节前,现场发生了一个小插曲。一位排队提问的观众走上前表示自己没有技术问题,只想宣布一个突发消息:就在几分钟前,Matt Godbolt 已经将 cppfront 原生支持添加到了 Compiler Explorer(Godbolt)中。全场随即响起了掌声与笑声,Herb Sutter 兴奋地直接向 Matt 喊话致谢:“Thank you, Matt. You're my hero.”并补充说 Matt 多年来一直都是他心中的英雄。 随后,现场首位提问者探讨了“零开销抽象”(zero-overhead abstraction)在 cpp2 中的权衡。提问者指出,C++ 一直以零开销著称,但 cpp2 引入的一些抽象机制(例如延迟初始化 deferred initialization)可能会引入隐藏的布尔状态标志,从而在整型等基本类型上带来隐式开销,询问 Herb 如何在这些设计与零开销理念之间取得平衡。Herb 明确指出,零开销原则的真实含义是“你不用的东西,就不需要为此付出代价”(you don't pay for what you don't use),而非“任何情况下的绝对零成本”: > “90% of the time you should just give an initial value when you declare the variable. That's zero overhead.” Herb 解释道,在绝大多数情况下,变量在声明时就赋了初始值,此时开销完全为零;只有当开发者主动选择在不同控制流分支中延迟调用不同构造函数或工厂函数时,系统才可能需要引入额外的布尔标记来追踪生命周期,但这是开发者使用该特性时自愿承担的成本。紧接着该观众追加了关于联合体(`union`)的问题:cpp2 提倡用 `std::variant` 替代 `union`,这也会引入标记开销,对于追求绝对极限制约的开发者该怎么办?Herb 回应,现代 C++ 规范本就推荐使用 `std::variant`,同时 cpp2 原生支持与 cpp1 代码完全混编并保持源码级向后兼容;如果确实需要传统联合体,开发者完全可以直接在同一个源文件中书写 cpp1 的 `union` 语法。 --- ### [01:46:17](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6377s) 纯净模式、预处理器终结与约束原则 另一位现场观众提问:既然 Herb 的核心目标是让写出糟糕代码变得困难并改善默认行为,为什么编译器没有将 `-p`(纯净 cpp2 模式,禁止包含头文件、宏或混合 cpp1)设为默认选项,反而要求用户手动传入开关?Herb 回应称,目前默认允许混编是为了平滑渐进迁移;如果彻底禁止传统代码,开发者将寸步难行。他顺势将话题扩展到安全检查开关的设计上:下标越界检查与空指针解引用检查目前也设计成了由开关显式启用(opt-in)。尽管依据现代内存架构与分支预测器的工作机理,这些检查理论上极其廉价,但为了稳妥起见,在尚未完全测明其开销前先保留开关,随着实验深入再做演进。 随后,来自线上的观众 Alexei 提问:现阶段很多底层操作仍依赖预处理器宏(如 `__LINE__`、`__FILE__`、`__func__` 用于日志或断言),即使 C++23 提供了 `std::source_location`,有时仍难以完全脱离宏,cpp2 将如何应对?Herb 回答,消除预处理器宏是一场持久战,Modules 与 `constexpr` 已经迈出了决定性的大步,目前仅剩下类似 `#line` 指令这样的少数尾巴(cppfront 本身也利用 `#line` 将生成代码的诊断信息映射回原始源码)。他强调,彻底摆脱宏依赖的下一个决定性里程碑将是反射与代码生成(Reflection and Generation)。 最后一位观众抛出了关于统一规范的问题:为什么不借此机会在语言中强制推行标准文件扩展名、标准代码格式化或统一变量命名规范?Herb 表达了他对“约束”哲学的看法: > “Constraints are liberating because they focus ingenuity in the directions that matter.” 但他指出,自己尚未被说服命名规范属于“真正重要的方向”。Herb 确立了一个刚性标准:任何语法上的强制改动,都必须能拿出硬数据证明其能够减少 X% 的安全漏洞(CVE),或者从编码规范指南与《Effective C++》中永久剔除 X% 的条款。除非有人能切实证明统一命名规范能够帮教学和工程削减 5% 的心智负担,否则他不会将其纳入语言的核心改造中。 ### [01:50:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6600s) 工具生态的困境与 Cppfront 解析器的探索 在被问及现有代码格式化工具为何常常需要依赖启发式规则(例如通过命名方式来推断变量是否应该为 `const`,从而常常误判)时,Herb Sutter 指出,工具生态系统之所以不得不严重依赖这些启发式手段,根源在于编写一个健壮、标准的 C++ 解析器实在太难了。C++ 复杂的语法、上下文相关性以及历史遗留包袱,使得构建通用的解析工具举步维艰。 为了从底层改变这一现状,Herb Sutter 在自己的实验项目 Cppfront 仓库中迈出了关键一步。他目前已经编写了一个独立的、上下文无关(context-free)、顺序无关(order-independent)的解析器雏形。尽管它目前还只是部分完成,但他的长远目标是让它逐步发展出足以匹敌现有全部 C++ 语法的表达能力,并作为一个可复用的模块开放给社区。一旦生态中拥有了这样清晰、健壮的解析器,各种静态分析和格式化工具就再也不需要痛苦地去猜代码意图、依赖脆弱的启发式规则了。 ```mermaid flowchart TD accTitle: 解析器与工具生态演进对比 accDescr: 对比传统 C++ 解析器依赖启发式规则与 Cpp2 上下文无关解析器带来的标准化工具生态 subgraph Legacy["现有 C++ 工具生态困境"] A["传统 C++ 语法极度复杂"] --> B["难以构建标准独立解析器"] B --> C["格式化/分析工具依赖启发式规则"] C --> D["频繁误判(如根据命名瞎猜 const)"] end subgraph Cpp2["Cppfront 路线图"] E["独立、上下文无关解析器 (Cpp2)"] --> F["顺序无关、结构清晰的语法树"] F --> G["通用可复用组件"] G --> H["精确的代码工具与重构支持"] end ``` ### [01:50:50](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6650s) 扭转教学沉疴:为何必须摆脱“从 C 开始教 C++” 借着工具的话题,Herb Sutter 顺势切入了一个困扰社区多年的痛点:编程教学方式。他坦言,他和 Bjarne Stroustrup 以及许多语言设计者最深恶痛绝的一件事,就是大学和各类机构在讲授 C++ 时,总是顽固地“从 C 语言开始教起”。Herb 直截了当地指出其核心原因:“That's an artifact of the fact that backwards source compatibility is always mandatory. They do it because they can, because that code works.” 正因为 C++ 必须严格保持向后源码兼容性,那些几十年前的古老语法今天依然能够编译运行,导致高校在 C++ 课堂一开始就大讲特讲 `fprintf`。Herb Sutter 强调,`fprintf` 本身并没有罪,但它绝不是现代 C++。Bjarne Stroustrup 为了扭转这种不良倾向倾注了无数心血,他撰写了《A Tour of C++》等多部权威教材,苦口婆心地劝说教育者:“拜托,请用我写的教材吧,我专门为你们写的!”然而教育界却充耳不闻,依旧以一句“我们习惯这么教了”应付了事,继续兜售 C 语言或 C++98 中那些早已被淘汰的过时产物。 - 高校计算机课程依旧在 C++ 入门课上教 `fprintf`、裸数组和原始指针。 - Bjarne Stroustrup 专门编写教材展示如何以“真正的现代 C++”开展教学,但收效甚微。 - 强制性的向后源码兼容性在保护旧代码的同时,也成了阻碍现代教学模式普及的温床。 ### [01:52:00](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6720s) “Cpp2”的命名意图:倒逼教学与生态跃迁的杠杆 正因如此,Herb Sutter 解释了他为什么不把这门新语法仅仅称为“Syntax 2”,而是坚持将其命名为一个独立的名词——“CPP2”。赋予它一个全新的名字,本质上是为教育和生态提供一个强大的支点与抓手。Herb 坦诚地表示这个项目大概率可能会失败,但一旦它成功了,它就能彻底终结这种教学乱象:“It also gives you a noun where nobody will be able to ever teach a CPP2 course and start with fprintf because that's not in pure CPP2.” 在纯粹的 Cpp2 语法规范中,根本没有原生的 `fprintf`;即便能够通过兼容方式以成员函数风格调用,那些来自 C++98 的糟粕也彻底无法立足。开发者和学生将再也无法使用宏定义的 `NULL`,不能用整数 `0` 混充指针,更无法使用默认就会引发内存泄漏、返回裸指针拥有权的裸 `new`(naked new)。Herb 总结道,如果我们继续沿着现有的老路走,那么即便再过很多年,C++ 的教学与学习体验也绝不可能实现 10 倍的飞跃。为新语法确立独立的名词,正是为了创造一个倒逼行业必须采用安全、现代编程范式的关键杠杆。 演讲的最后,Herb Sutter 感谢所有现场和线上的参会者来到 CppCon 现场聆听这场报告。在热烈的掌声中,他祝愿大家会议愉快、返程一路平安,并相约明年在 CppCon 再次相聚。 - 纯粹的 Cpp2 语境下被彻底摒弃的遗留隐患: - 宏定义 `NULL`。 - 用字面量 `0` 兼代指针或整数的模糊用法。 - 产生拥有权裸指针并极易导致内存泄漏的裸 `new`(naked new)。 - 脱离类型安全检查的 C 风格 I/O 函数(如 `fprintf`)。 - Herb 对生态变革的尖锐反思:“On the current path, we would have done it by now. What other lever can we use? Perhaps this could be one.” ## Notable quotes > [01:46:17](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6377s) Constraints are liberating because they focus ingenuity in the directions that matter. — Herb Sutter > [24:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=1485s) The ugliness of generated code is directly proportional to the impedance mismatch. If your source is still C++, then you can make it beautiful. — Herb Sutter > [37:45](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=2265s) Because I want to teach people how to program, not how to encant a C++ detail. — Herb Sutter > [01:43:08](https://www.youtube.com/watch?v=ELeZAKCN4tY&t=6188s) 90% of the time you should just give an initial value when you declare the variable. That's zero overhead. — Herb Sutter ## Mentioned Herb Sutter, Bjarne Stroustrup, Fred Brooks, Niklaus Wirth, Anders Hejlsberg, Joe Bialik, Tim Sweeney, Matt Godbolt, Scott Meyers, Andrei Alexandrescu, Kate Gregory, Joe Biden, CppCon, ISO C++ 委员会, Standard C++ Foundation, Cfront, cppfront, Rust, TypeScript, Python, Ada, C#, Clang, GCC, MSVC, Visual Studio, Visual C++, Ubuntu, Windows, WSL, Compiler Explorer, VS Code, MITRE, NIST, 美国商务部, Epic Games, Microsoft, GSL, 人月神话, A Tour of C++, Effective C++, High Integrity C++, Abseil ## Video description https://cppcon.org/ https://github.com/CppCon/CppCon2022 --- Can C++ be 10x Simpler & Safer? (Simplifying C++ #9 of N) - Herb Sutter - CppCon 2022 Since CppCon 2015, all of Herb’s talks have been about ways to evolve C++ to make it simpler, safer, and more toolable. Every release of ISO C++ has already been making regular incremental “10%” improvements in these areas. But what are the fundamental factors that limit our rate of improvement, and what would it take to make greater progress? Like every year, Herb’s talk will explore selected current pain points and describe experimental ideas to address them that might someday contribute toward C++’s long-term evolution. --- Herb Sutter Herb is an author, designer of several Standard C++ features, and chair of the ISO C++ committee and the Standard C++ Foundation. His current interest is simplifying C++. --- Work at Hudson River Trading (HRT): https://tinyurl.com/safxfctf --- Videos Filmed & Edited by Bash Films: http://www.BashFilms.com YouTube Channel Managed by Digital Medium Ltd https://events.digital-medium.co.uk #cppcon #programming #coding [Source](https://www.youtube.com/watch?v=ELeZAKCN4tY)

Source quotes

[106:17] Herb Sutter: Constraints are liberating because they focus ingenuity in the directions that matter. [24:45] Herb Sutter: The ugliness of generated code is directly proportional to the impedance mismatch. If your source is still C++, then you can make it beautiful. [37:45] Herb Sutter: Because I want to teach people how to program, not how to encant a C++ detail. [103:08] Herb Sutter: 90% of the time you should just give an initial value when you declare the variable. That's zero overhead.

Source quotes

[0:00] Herb Sutter: To me, safer, 50x safer means 98% fewer CVEs, vulnerability exploits, and bugs in those categories. 10x simpler means I can get rid of 90% of the paper of the books I teach today. [40:00] Herb Sutter: Call me paranoid, but when a government department starts telling you to do work and move to other languages, we should start to listen. [25:00] Herb Sutter: The ugliness of generated code is directly proportional to the impedance mismatch. If your source is still C++, then you can make it beautiful. [110:47] Herb Sutter: One of the things that really bugs Bjarne and me and others is people teaching C++ starting with C. [80:00] Herb Sutter: Any function that has an out parameter can be a constructor.

Original transcript

[0:00] I have a bit of a provocative title this year, But this is actually a talk that I've been giving for seven years in pieces. I'll explain what I mean by that. We are all here at CppCon and at conferences like it because we love C++. Maybe you feel as I do, C++ is the best language in the world for me to write the software I need and want to have. Thank you. But C++ also has lots of challenges. And so we're in a phase where we are seeing the industry doing... lots of major C++ evolution and sometimes revolution experiments. This talk is one of those. So I want to be clear up front, and I'll try to repeat it often. This is my personal experiment. I am not speaking for any company. I'm not speaking for the Standards Committee. it's my noodling around that I'm sharing, because I hope it'll start a conversation in a direction to make C++ better. But if anything comes of this, well, first of all, it's an experiment, so it can fail. That's the default for experiments. And if anything does come of it, I intend to continue bringing those as ISO C plus proposals, and you'll see I've already been doing that. Yeah. I would like to encourage us as a community of 7 million C++ developers around the world, Uh... As we consider... C++'s future to push the boundaries to bring C++ itself forward and double down on C++ not be quick to switch to something else. Thank you. to aim for major C++ evolution, 10x major, directed toward things that will make us better C++ programmers, not programmers of something else. And there are lots of good experiments going on and products. Rust is a real product, not in production, not an experiment. And there are other experiments in it. on that side of the range. that do a greenfield language design, which is always fun to do, with new module systems, new packaging systems, building up their own new ecosystem. And those are valuable. And we can learn from them. But today I'm talking about the other end of the spectrum. I am interested in refreshing C++ itself. making the guidance we already have, the modules we already have, the default. . keeping our ecosystem, our package managers, our full compatibility but making C++ better. So here's a roadmap for what we're going to talk about. I'm going to talk about the motivation, the history that I've been working on this project for seven years now. So this is a bit of a coming out party. But you'll see I've already been talking about it for seven years. And we'll talk especially about safety. and about simplicity examples. And I'm always going to talk about metrics. So when you see on the right hand side of this title card, every time it comes up as the roadmap, I'm interested in measurable progress. To me, safer, 50x safer means 98% fewer CVEs, vulnerability exploits, and bugs in those categories. 10x simpler means I can get rid of 90% of the paper. of the books I teach today. That's what I made me for. What could go wrong? So what is C++? This is a simplified version of a slide I showed here. in 2018 at CppCon, C++ is fundamentally about zero overhead abstraction, I always know where things are, how long they take, and friction-free interop, backward compatibility. That is core. But the rest of this stuff, I mean, some people seem to think If it doesn't hurt a little. It's not really C++. Thank you. Wow. I challenge that. And we have been challenging that because specific syntax. We've been changing and adding syntax with every three-year release of the standard. Unsafe code, security exploits, those aren't core to the C++ experience. And we have been making those things better, but kind of like by 10% better-ish. every standard cycle. not a game-changing 10x better. And the major reason for that, one major reason, is that we have strictly hewn to 100% syntax. backward compatibility. Thank you. Thank you. What if we could have our cake and eat it too? And here is the strategy, the potential if it works judo move. You know our zero overhead principle we love so much? What if we applied [5:00] at to backward source compatibility. That means we always have backwards 100% Sfine macros, give us all of it. Backward source compatibility available. But, We only pay for it If we use it, is that possible? Thank you. So I'm going to ask you to indulge me for the next hour and a half. in the thought experiment, backed by an implementation. in the works. out. What could we do if we had a second syntax for C++. One of the things that would give us, if it was a complete self-contained alternate syntax, had all the expressiveness, but spelled a different way than today, and it was unambiguous. That's a bubble of new code. You know what you have when you have a bubble of new code that doesn't exist today? Because nobody's written any code in the syntax yet today. You know what that means? You have exactly no backwards source compatibility constraints. You can do anything you want. that makes sense, that you feel a sensible person. without worrying about breaking code, to reduce complexity, increase safety, improve toolability, and if it works out, to have a platform to build on to continue evolution, for another 30 years. Remember when C++11 came out, there was no question it was still C++. But some of it looked a little foreign and fresh, like, oh, smart pointers. Oh, the left to right trailing return type declaration syntax. Sure, we added stuff, but it was still C++. It felt like a new language. What if we could do that again? for the whole language. That is what I've been working on primarily for the last seven years. Yeah. So in 2015, I spent a year in copious free time as a lower priority than all the other work. to say, could we refactor C++? I didn't start with a blank sheet and invent a language. That's fun, but that's not what I was interested in. I started with C++. What are the common pieces that may be spelled differently but could be unified? What are the pieces that we could refactor out so we have a smaller number of simpler, composable, general features that still let us express all the things we want to express? We're not dumbing it down. But the concept count is way lower. Because remember, concept count is the number of things you have to teach. And it's not just the number of keywords in a language, it's also the number of exceptions. Anytime you have an irregularity, anytime you have two ways to say the same thing, anytime you have, oh, but when these two features work together, something different happens, there's another thing to teach and another thing to teach. And you know what you get with that linear list? rote learning. Thank you. Rote learning is hard. That's why we want to reduce concept count. Thank you. But anything can look good on paper. So what I've been doing since 2016 is I've been taking one particular piece that could be disentangled, could be presented as a standalone proposal for ISO C++. And for the last six years, every conference talk I've given here and nearly every standards proposal paper I've written, has come from the work I'm going to show you today Just In today's syntax, And the reason is to flesh each one out because you get that next level of scrutiny so you find bugs and you can fix them. And also validate, is this even a problem that the committee and you, the community, want to solve? Do you think it's important? And even if that's true, Will you accept this solution direction, which is a separate question? This is all part of validating that there could be a there there. And so all of the proposals and talks and papers down at the bottom have come from this work. I want to call out one in particular, Spaceship, because of all of these, that's the only one I presented today. and gave a paper without a prototype. And the reason is because I never actually had a talk about it here. I mentioned it for a few minutes in another talk. because it was part of this design I had been doing, but it was a target of opportunity where after C++17 shipped, suddenly the committee continued talking about comparisons and it was clear that something was going to happen. So I thought, well, I have a design in my back pocket. Let me propose it. but it didn't have a prototype. And because of that, it had two major bugs. which were fixed thanks to these people. So I want to acknowledge them. Some of them are here in the room. If your picture is on the screen, would you please stand up and wave both your arms so people can see you? Some of you are way at the back, some of you are at the front. And give them a round of applause please. Thank you so much. And of course others, many others help too. It's a large committee. - Okay. [10:00] This is the first piece of of my simplification work that is part of the standard, and I think that's the right path to go. And I brag on Spaceship a little because it's the first feature we've ever added to C++, where, yes, you added a dozen pages for the core language wording, but it made the standard smaller because we applied it in the library, and it reduced more than that many pages. in the standard library. So I take that as some data that yes, simplifying can work. But now let's talk about the problem. I already talked about the problem of prototyping. The biggest thing that's been holding me back is it is expensive to prototype things in a production C++ compiler. Some of these have been my own little library, some have been done in Clang, some have been done in Visual Studio. But man, it's expensive to do. So then I thought, As we all should. Wait for it. What would Bjarna do? But not this Bjarna. Not this Bjarna. The other Bjarne in this picture Thank you. That yarner. It's a cardboard cutout, but the picture works, so I'm going to go with it. What would that Bjarne do? And the really nice thing about asking a question of what would somebody-- 40 years ago do is you could look it up on Wikipedia if they succeeded and that's why we're all here. What Bjarne did is he built Seafront. It was a C++ compiler that emitted C code. Yeah. And if you weren't around in the '80s, as many of us weren't in computing, you may forget that for the first decade of C++'s life, this was the C++ compiler. and C with classes before it was even called C++. And Bjarne nailed three things. He nailed his value proposition. He had a 15-second elevator speech. Thank you. We love C, its powerful machine near flexible. but it needs abstraction. I'm going to solve that problem. Thank you. That's a succinct elevator pitch. And by compiling 2C, Okay. It meant it was available wherever a C compiler was available, and you had full interoperability with C code, with C optimizers, linkers, debuggers, tools. Yeah, in the debugger you might see a mangled name for an overloaded function, but the debugger worked. This was powerful for C++'s early adoption. Without C-Front, we wouldn't be here. Amen. So my experiment is... Could we do that again? And what I'm about to present is just my own experiment, my own personal code. It is live on GitHub under hstutter. You can find it at CPP Front. It is a syntax two to syntax one compiler in progress. Yeah. But I have similar goals to be ARMS. I love C++. Let's embrace C++ and double down on it, but address that It needs to be safer, simpler, and more toolable. Those are the problems we want to solve, but we still want it to be C++. And it works wherever a C++20 compiler is available. You'll see that in live demos today. And have full interoperability with today's C++. It should be C++, just nicer. Caveats, again, my personal experiment. Have I said that enough times now? This is a partial compiler, hilariously incomplete. Over the winter, I hope to add classes. I mean, it's hilariously incomplete. Turns out you can get a lot of simplification just from functions. But my hope here is to start a conversation. Before we rush off to a different language, what could be possible within C++'s own evolution to rejuvenate C++. Yeah. So here are the complete build instructions for CPP Front. one line for each major compiler. The builds and runs today are all major compilers in C++20 mode. When you write your own code in the syntax to CPP2, I call it for short, which can be either a file that's all cpp2 syntax or that's interleaved with today's and syntax and the second one. Thank you. then just run it through cpp front, and then that will produce a cpp file, which you then just run through your compiler. So the top half of this slide is the complete build instructions. There is no Bazel, there's no CMake, there's no third-party library dependencies, there's no Yak or Bison, because I started with an empty editor and the standard library. I never looked back so far. There are no there's no third party code. It's a custom written manual parser. [15:00] And all the code is custom written by me. Thank you. Let me just talk a little bit about an important principle and stakes in the ground. If you've been noticing those papers that I've been writing for the last seven years, Every single one of them in section one has this information on this slide and the next one. and how it applies to that paper. Okay. and please steal and reuse. Fred Brooks nailed it when he said the most important quality in a systems design is conceptual integrity, that it should hang together well, as if the product of one mind. even if it's designed by a small team. So my goal is to refactor C++ into fewer composable general features, and that requires being consistent, orthogonal, general. And here are some examples of that. And if that sounds familiar, it's because it's exactly what Bjarne said that you saw in the walk-in slides as you were walking in the room. Bjarne has conjectured publicly for decades that within C++ there is a language 10% the size, struggling to get out. The simplification would come from generalization, and my goal is to see if I can prove him right. Thank you. So here are some stakes in the ground, some constraints. I am not interested in drive-by improvements. There are a lot of things I think I could improve about C++ that I tried and threw out because they didn't carry their weight. They didn't solve an existing problem. They were just a personal opinion. And I only want things that make C++ safer, simpler, and more toolable, and laser focus on those things. And to be measurable, to be able to quantify things how much improvement we get in each of those. And of course, stay C++. our values, zero overhead, and we can always take control. But the syntax is the thing that's most visible. Please do not make the mistake. Please, and I know it's going to happen anyway. Please don't make the mistake of saying, oh, yeah, Herb gave a talk and he says he wants to make C++ prettier. That's not the point. That would not be interesting. It's fixing the semantics, changing defaults, removing special cases. That's the meat of it. The syntax is just to get us a bubble of new code. It's the gate into the... new area. And yes, if you're going to build a gate, it should look good too. You should make it with good boards and give it good paint. But it's just the gate. But let's talk about syntax, the gate, and then we'll talk semantics, which is really the big payoff. Yeah. Any language these days should be context free parsing and order independent. You'll see examples of that. That's just table stakes for language design these days, I think. Also, as one language designer I know, "declarations should be left to right," he said, this is quoting somebody else, "as nature intended." Yes, so as with many things, Nicholas Wirtz was right. And also I put a stake in the ground that declaration and use syntax and expressions should be complementary. I'll write more about that later. in a later blog post or article. So here are two examples of today's code. ClassShape, that code, that-- ClassShape has been around since around 1980. AutoF int i arrow string has been around since 2011. And everything in those braces we can't touch with breaking changes. We cannot change defaults. we would break the world. So I'm proposing a left to right syntax. So syntax to every declaration is name, colon, type equals its value. shape is a type equal to this body. F is a type. function that takes an int and returns a string equal to this body. and variables the same. So here's an example. main is a function that takes nothing and returns an int, and it's equal to this body. Vec is a std vector std string, notice the familiar terms, equal to this list of values, and so forth. And yes, we get c tag. Notice view is a std span, not a std span of string, equals vec, because this is still C++, right? So let's make this a whole program. So one thing I want to emphasize, I'm showing this one slide program. This is a whole program that will compile and run, and there's not a single include or import. Thank you. The reason is because in Syntax2, if you build a Syntax2-only translation unit source file, C++23 import std semicolon is default. You will never need to include and remember what header to include again. This is a module [20:00] was first design. It is designed for fast builds, for strong ODR that modules only give you. And if you're worried about the build speed of bringing in the whole standard library, regex, parallelism, everything, Yeah. Import STD on the current implementation is faster than include Iostream. It's faster to compile, to bring in the import, the whole standard library, than to compile Hello World. My experiment is Let's make it the default. Hey, it's an experiment. If it doesn't work out, we can change it. We can tweak this. But I'm pretty confident about this one. You'll notice the parameters are passed the way I already told you about two years ago here at CppCon, about with my paper 708 parameter passing. So you'll see in out there. That's how parameters are passed. There are no references. in CPP2, for example. Thank you. Underscore is the wildcard, so those are implicit templates. If you had eagle eyes, you notice that last function doesn't have squigglies around it. The reason it doesn't have braces is because One of the tenets I've embraced is there's one syntax for everything. But let's pick good defaults where, well, if you don't need certain parts, you just omit them. That's not two ways to say the same thing. That's one way to say the same thing with defaults. optional parts. If you don't need them, you don't use them. And for single expression or single statement functions, You don't need braces or the return statement if it's returning a value. . And that's important because remember I said I have only one declaration syntax? I don't have a separate declaration syntax for lambdas. A Lambda function is exactly it's the same parse function. It is exactly writing a function at expression scope And just omit the name. It starts with colon paren. one syntax for functions. And so for lambda's, it's really important that you can omit those because you want to write short lambda's, Conveniently. And by the way, did you notice that we called decorate and printline before they were declared and defined? There's no forward declarations. This is an order independent syntax and language. Thank you. Thank you. Here is the code that CppFront generates. And I want to emphasize, this is not PowerPoint. This is a screenshot. I'm showing the screenshot because I could do this as a demo, but demos take way longer. And I'll still do some demos, but I'm trying to save time. But try that program today when you get home, you'll get that generated output. This is a screenshot. And so let's see what's generated. So did you notice no discard is the default? How many of you wish no distard was a default? Yes. Okay, good. Well, so do I. And remember, I'm writing this compiler, first of all, for me, for the code to be able to write C++ in a more convenient way that I like. And I think that's the right choice. So that's why. When you write the compiler, you get to make choices. Again, we have normal CTAD because I simply emit, I turn into syntax one code, what you wrote a syntax two. So stdspan view, that said, view colon stdspan equals vec. CTAD works just fine. How many of you noticed that I called thing.size on the previous slide? Anybody notice that? A few people. I'll bet they're all Standards Committee members who were in all of the signed, unsigned, discussions for years and So where those all ended, to cut a long story short, is we did not, Bjarne did not convince the committee to change all the dot size functions, for example, of all the containers to return signed. And he did not convince them to put a dot s size on every container. Container. he did get them to have a non-member S-size function in namespace STD. but because thing.sise paren paren... is a universal function call syntax in CPP2 it calls it just fine. It'll use a member if there is one, otherwise it will fall back. And I thought that was a small convenience. I've always been a fan of UFCS. Biarda and I co-authored the papers about it, you know, eight years ago. And others have written very good papers about it as well. But I was surprised as I was writing my dozens of examples and tests, I was surprised how quickly I just went to .SI and never looked back. Like, it's just so nice. . And notice this does not, I hope, look like generated code. it is really important to me that if this experiment doesn't work because it might not Experiments fail all the time, and that's the default. I want to be able to write code in this new syntax, and if it doesn't work out, not lose my investment. And always be able to just throw it away [25:00] and walk away with my today's syntax code. That's important to me. And if we scroll up, here's how you get order independence. I generate the forward declarations for everything. In the place where you write a CPP2 function, I emit its forward declaration, intermixed with all the CPP1 code in the right relative places so it can call it as order-dependently as CPP1 code is. And then I emit all the definitions for CPP2. and they're order independent. They can all see each other. Just to emphasize the readability, here's another example, which is a straw man example, A, B, C, D, E, F, G, H, I, J, K, L, little variable names. But to show you that I, to me, it's really important. This is one of my test cases to make sure that I kept everything where it was when I emitted code, because it's really important to me that the generated code is not some glop, that it is beautiful in my personal style that I used in my CPP2 file. And so you'll notice a couple of things. I might have mentioned the declaration follows use One of the consequences of that is most operators including dereference are postfix It actually eliminates a lot of parens when you using them And so I rewrite those You notice on the right-hand side, for instance, the dcol and ecol, I do the plus plus and then put parens in the right places for star f as I rewrite the expression on the left, which changes the locations of things, and the universal function call syntax also changes the locations of things. But I stick close to what the original indentation was, including that I keep track of the locations of all comments and put them right back where I found them. That's really important to me. And if you run this with the dash clean CPP1 switch, because when the day comes that I want to drop it and just walk away with my CPP1 code, I'll run it with dash clean CPP1, and that's what I'll have that emits the hash line directives and things like that. Because I want to never lose my investment. I want to have my today's C++ code all the time. That also keeps me honest. It helps me ensure that I'm sticking to C++. So everyone knows that generated code is ugly right Those are people who seen foreign languages generated to C The worst examples are when you have foreign concepts you're trying to shoehorn into a language that doesn't understand them. The ugliness of generated code is directly proportional to the impedance mismatch. If your source is still C++, then you can make it beautiful. So that's a litmus test for me. So would you like to see some live demos of a pre-alpha compiler? You just asked to see live demos of a homebrew pre-alpha experimental compiler. Wish me luck. And be kind for any crashes. I don't think it'll crash, but we'll see. So our first example. is on the left hand side, notice this is a mixed file because I have include iostream, include string, and I have a main line which is today syntax. This is normal C++ today. Yes you can write auto main today But I have these other two functions that I writing So I can show this in any compiler any of the three major ones So let me just by applause hear which one you want to see Clang, GCC or MSVC. How many would like to see me start a demo with Clang? Okay, how many for Microsoft Visual C++? That's about a tie. How many for GCC? Okay. It's GCC, which means I have to switch to my Ubuntu. Thank you, Windows, for having actual Linux. And so we'll go... So we'll go... For some reason, it is not taking keystrokes. Okay, let me restart Ubuntu. Wow, this has never happened before. Wow, this is really incredible. Well, I guess we're going to go to our second choice. [30:00] which is Microsoft. Honest, I've been doing this for months and months, and this has never happened. Okay, there must've been an update. Okay, let's see. Okay, good. This is taking keystrokes. So let's do cppfront demo.cpp2. And the first thing we get is an error because it's a live demo, but don't worry, this is an error I expected. So the first thing you go through the first compiler and it says, OK, great. It's mixed CPP1, CPP2. The CDB two parts pass safety checks. OK. But then I get this error. What is it? So in demo CPP2, notice this error is coming from the second compiler, but it's in terms of my original CPP2 source file. Thank you. String is not a member of STD. And if you were seeing this in Clang or GCC, it would say, "Did you mean String?" So all the cool error messages we get today still work. And the reason is, if you look up here, I deliberately, mistakenly, wrote strim, which is not a very useful type. So let's put back the G. Okay. And now, Thank you. Let's run. And now it works. Thank you. There's always a virus scan the first time you do it in EXE. Oh my, this is really incredible. There, second time was faster. See, there you go. So let's see what was generated. Thank you. So starting at the top, here's our header with the utility functions that I need, including in parameters and things like that. Then I emit all the C++ syntax one code in order, and wherever I see a CPP2 declaration, definition rather, I emit the forward declaration in that order. That way, main can see decorate a name because CPP1 is order dependent. That's fine. But then I emit the definitions here at the end. including that I'll put braces around the decorate function body, things like that. Good. So that's the first example, just to show how how interop works. You can interleave. You can't write CPP2 code in the middle of a CPP1 body or vice versa, but you can interleave them in the same source file. have one function in one syntax, one function in another. So now let's see about a pure CPP2 file. Now, if I try to build this with Here, let me show you some of the switches. If I tried to build this with the pure CPP2 switch now, that wouldn't work. Why not? Well, let's just see in First thing it says is, no, no, no, CPP2, you don't get no preprocessor, you don't need no preprocessor. Yes, that includes include. Use import instead. Do you like that? Thank you. But let me just highlight a pattern you're going to see a lot. Thank you. It tells you today's advice by default. Use the pre-crossed or less. And The thing it says to do instead is the thing we already teach people to do instead. In this case, as modules are coming online, import STD. So let's actually make this code I'll see from here too. So here we go. We'll just replace this with a full CV2 file, then we'll build that. and now -pure auto work, And there we go. Yes. I So this time, all I did was change main to the new syntax. I just changed auto main to main colon. and I added an equals. That's the only difference. Plus, I moved it up here just because I could. and I dropped includes and imports just because I could. because it's just order independent, and I always have the standard library. as it should be. Okay. Thank you. One last thing about this first demo. is here I'm doing the normal good thing of putting a local variable on the stack. So I'm going to do something that's in bad form, but just to show you how the freestore works. You shouldn't normally put a local variable on the freestore, but just so you see how it works. S is a new string, new stood string. Thank you. All allocation in CPP2 is done using named memory arenas, which are just variables, just objects that have a function called new. a template function called new. And I give you two of those by default. They're called unique.new, guess what it calls? and shared.new, guess what it calls? And I didn't say either one here, and so what's the default if you don't say the memory arena at all? What's global new? unique new. So S is a unique pointer of string because unique pointer should be the [35:00] - Fault pointer. And this is totally type of memory safe code. And that's how I want it to be. Now, let me show you one thing about this, because we've been talking about the C++ standard library. But wouldn't it be nice if we gave the C standard library some love as well. that Okay. Take a look at this code here. I've got a C++ string and I'm using a C f open and f print f to print the string using Cster, right? That should just work, right? So, Let's build that. Thank you. And Yes, that will... That will print to my file, which is my dog, Lily. So now if we do dir, there should be a file called dog. There it is. It's... Yes, that's the timestamp we have now. Dog? There is Lily. That's one of our dogs. She's really cute. She's an Italian greyhound. Did you notice anything odd about the left-hand side code? It's amazing how many people I show this to who don't notice I'm calling fprintf as a member function. How many of you did not notice that until I mentioned it? Quite a few hands because it's just natural, right? Now, I'm going to ask you, is that just just being cute? No. It's about being more toolable, because if you call using the non-member function call syntax, IntelliSense can't help you. If you start with an object that you have in scope, dot, now IntelliSense can help you with autocomplete and tell you all the things you can do with that object. If you're just starting a new blank line, IntelliSense can't do a thing for you. And that's important for toolability. So UFCS is more than just convenient. It's about toolability. So for those of you who want to see these demos, I've captured some screenshots here of everything that we just saw, and you can take a look at those in the slide handouts. But the key here is, Don't pay for what you don't use. being applied to backward source compatibility. You saw examples where I had mixed C++ syntax one and syntax two. I included Iostream and that was groovy. It worked fine. Include string. Use main with today's syntax. And it can call the new functions. It's all just fine. That is really important for adoptability. Remember, this is what Bjarne had. Bjarne and TypeScript are two excellent examples of this. Bjarne said, "Take any .c file you have." Well, he also messed with the C language while he was at it, and improved it. "But take any .c file you have, rename it to .cpp, and it will work. And that's largely still true today. Thank you. And you can start writing one line of C++ code, one class, and start seeing benefit. Oh, this little bit is now encapsulated. that's a great adoption ramp. TypeScripted the same thing, rename any .js to .ts, And it works, and you can write one line of TypeScript to add type information and start seeing benefit. That's what you can do here. You can rename any .cpp to .cpp2, write one line of syntax2, and see what you think. In this mode, you have 100% perfect source compatibility with Sfinet, macros, all of it. and we've avoided the Python 2.3 problem. because it's a unique and distinct syntax. Thank you. But there's another way you can use it, which is a source file that's only CPP2. Then you get to program, I hope, if the experiment succeeds, I hope that I get to program in a C++ that's 10x simpler and safer and more toolable, that's safe by construction, that still has seamless interop with all of today's C++ libraries through module import, not the preprocessor. Thank you. And I can stop teaching 90% of what I personally have been teaching people for the last quarter century of my life. because I want to teach people how to program. not how to encant a C++ detail. Thank you. So let's talk about safety. Thank you. And I'm going to hit type bounds lifetime and initialization safety. I'm also interested in other safeties like concurrency safety, but why these four? Thank you. because they're still playing the hits. If you look at MITRE's 2022 most dangerous software weaknesses, and thank you for Dave Abrahams who reminded me to look at this. Thank you, Dave. out of bounds write, out of bounds read, use after free, null pointer deref. And just when you thought that there was something other than type bounds lifetime initialization safety, integer overflow, that's [40:00] for an No, it's not. Because the integers you most care about overflowing are indexes, and that's bound safety. So of these top 13 things, once you look at the things that a programming language can do something about, you remove things like SQL injection or go sanitize your data, duh. What can a programming language do? Five of these top 13 things are things that a programming language could do, and they're the same things year after year in different proportions. But it's lifetime, excuse me, initialization, type, and bound safety. Yeah. And, Let's just turn off slides for a second and just talk, because A lot has happened here. in the past year, especially since early 2021. And then a few years before that, how many of you, especially if your companies are US-based, how many of you, have personally or had your work impacted, you've had to do work or something about your work has changed because of last year's executive order on cybersecurity in the United States. How many of you? OK, quite a few hands. So for those of you who don't live in the States or who haven't been affected yet, On May 8th of last year, President Joe Biden issued an executive order on cybersecurity. The reason he did it, the reason that cybersecurity started getting political issues. executive attention is because you remember all those ransomware attacks, all those cyber attacks on election infrastructure, on ransomware shutting down hospitals, oil pipelines, It has become intolerable. Thank you. And so a lot of us, a lot of folks on my team at Microsoft, maybe on many of yours, and those people who raised their hands, have spent the last year on supply chain security, have spent the last year on programming language security. And part of what has come out of that executive order is it told the Department of Commerce and NIST You got 60 days. Give us your recommendations for making software secure. How many of you are aware that that happened last year? Department of Commerce. Let me tell you what they wrote. So 60 days later, in July of 2021, Okay. The Department of Commerce of the United States, so like not some little office, the Department of Commerce issued guidance that in response to this executive order, And this is a direct quote. that said, Some programming languages, such as C and C++, are not memory safe. Some programming languages, but we're going to call out those two. That was a direct quote. "Subprogramming languages such as C and C++ are not memory safe." two sentences later, Direct quote again. Where possible, do not use memory unsafe languages. All right. This is the first time a government has actually issued guidance to industry broadly. This was a year ago. That was the barest paraphrase away from where possible, don't use C and C++. And this year they're doubling down on it by funding projects to rewrite existing infrastructure and low-level projects. in other languages. I'm going to go home. So when we talk about things like this, the reason I took a few minutes to mention this is because some of us are kind of jaded. We've been seeing these vulnerabilities since the 90s, since the 2000s. Thank you. And we've been skating by. Let's let's you know, we added some smart pointers. We added some static analysis rules, but we've been skating by. But this is not the 1990s anymore. This is not the odds anymore. This is a world where your software is under attack. My software is under attack, especially in the last three or four years. And if it's not under attack right now, as this talk is happening, it'll be under attack tomorrow. And if you think that it doesn't affect you because you're an embedded system that runs in a refrigerator, oh my goodness, I'm scared. because those are the ones I'm most concerned about, those internet-connected appliances that don't get serviced. and where the vulnerabilities don't get patched. Call me paranoid, but when a government department starts telling you to do work and move to other languages, we should start to listen. So in that context, here's why I care about safety. Thank you. What do we do about safety now in C++? So in CPP2, as always, static enforcement by default, safety by [45:00] instruction. You saw that with the example of s colon equals new string. Unique pointer by default, you can't leak it. Thank you. and then sometimes dynamic enforcement where you need to, but we want to make it visible and pay for what you use. That's very important to us as C++ developers. And as you've already seen in some of the error messages I've given, I want CPP2s, CPPFronts error messages to be of the form, "Oh, we're going to enforce that rule that we already teach by default. when you violate it will tell you to do the thing we already teach you to do Instead, like, don't use union, use std variant instead. That's an exact message from CppFract. And then if we need to close any gaps, we'll do that. But mostly enforce them by default, teach, enforce our rules we already have by default, teach people to do instead what we already teach them to do. And that's how we make sure that we're saying C++. That's my goal and my hope if this experiment can succeed. So let's talk about type safety. Yeah. If you look in the C++ Core Guidelines, here is the full type safety profile or set of rules. Thank you. Thank you. Thank you. He also tagged most of them with "as." So last year here at CppCon, I gave a talk on pattern matching using is and as, which is mostly about is and as. And basically, in CPP2, All casts are done through "as", exactly as I showed last year. And it turns out that covers most of these rules. I implement the other ones as well, except the one about classes, because I don't have classes yet. So here's that slide I showed you last year. The takeaway for type queries, instead of all these spellings, just say "is." For type casts instead of these divergent spellings, Just say as. Thank you. And let's see it working. Thank you. So for type safety, Here is First example I want to show you And... Let's put that here. So what I have is I have a generic function. It's a test function, but it's generic, so I'm emphasizing that. and it will print out the type ID, because I still have C++ type ID, if I want it, to print out the name of the type that I was actually given. And then I have an inspect expression No, I have not implemented full pattern matching. I haven't done decomposition and stuff. I've Just barely started writing inspect impressions. But this works today. So I'm going to inspect X, the thing I was given, and I'm going to the value of the inspect expression will be a string. So if X is int, then the value of the inspect expression will be std to string x as int. If it's a string, I'll print X as std string. Otherwise, If it is wildcard, I'll say not an int or a string. So is it an int? Is it a string? Is it something else? But notice this is a generic function. I wrote it once. And I can call it with variant any optional with with uh, literals, variables of built-in types, And it works. And I really emphasize, go back to last year's talk video. There's a there's a whole couple of slides with a big dragon saying why. Divergent syntaxes are non-generic. I couldn't write this function if I couldn't spell all casts and all type queries as, as, and is. That's so important for generic code. So let's see what we've got here. We've got a variant of int double. And again, these are just the standard library variants that we have. in our standard library today. And I'll set it to be a double. initially. Then I have an "any." Remember "any"? and it's going to be xzzy as std string. So it's going to store a std string. And then I'll have an optional int, which is currently Initially, doesn't have a value. So when I print test generic 3.14, that should say not an enter a string, right? V contains a double value, so that should say not an interest string. A should say... the string, right? X, Y, Z, Y. And then for O, it should say not an interest string because it's an optional int, but it's currently very optional. It's currently empty. There's nothing there. But then if I go and assign ints to each of these, then all of these should say true, true, true, true, int, int, int, int. Make sense? This is dynamic typing we already have in C++, [50:00] but I hope nicer. So let's try this out. and Ubuntu seems to have closed its own window. I was waiting to see if it would finally come back, and it's actually just gone away. Let me just try it one more time because I really, really do want to show a GCC. if I can. Must have gotten an update. Ubuntu just does not want to work today. Very strange. Well, maybe I'll post a video with the Ubuntu demos, and you can try them yourself. . So let's go CPP front and Oh, that's pure, so I better go dash pure because I don't want to import anything or include anything. cdb2-p And There we go. So I'm doing all my dynamic typing using the existing types I have for that in the standard library. This is just using the standard library types directly. So notice the code I'm generating here. Here is the main function that's just It looks almost the same, it's just in syntax one, that's doing all those same things. Notice the as becomes a CPP2 colon, as... but it looks very much the same. And here's that generic function. And you'll notice the inspect statement is a lambda that I introduce. I emit a lambda that I invoke right away. And you say, "Oh, that looks simple. Look at the scroll bar down here." This is the place where I generate the most code yet in CppFront. You probably want to see it, don't you? Okay, here's what happens when you Alt-Z line wrap. If constexpr for the win, let's just look at the actually, but you know what, it's not that I want to look at this instead. I want to look at this. That's the code I want to write and let the rest be an implementation detail. Thank you. That's the world I want to live in. By the way, while we're talking about type safety, let me show you that thing that I mentioned. Like, let's say we wanted to find a union. What happens if we try to compile that now? Don't union. Go variant. And that's the way that C++ should be. And I hope that it can be. And that's-- That's my experiment for type safety. Here are the screenshots. And now let's talk about bound safety. Bound safety, here is the bound safety profile again. I'm just taking what's in the C++ Core Guidelines. and implementing it. making it the default. And I haven't done the fourth one yet, which would require me to actually check and have knowledge of the standard library functions that are being called. But I think I can do that one too, but I haven't done it yet. Thank you. So let's say we want pointers to point to a single object. That's what the core guidelines teach. Don't do pointer arithmetic. Why can't we just enforce that today? it would break the world. including it would break all the C code that we have. So what I do instead is I, in syntax to code, which doesn't exist today, so I don't have to worry about breaking anyone. I just reject pointer arithmetic, including a subscript. And while I'm at it, I just thought, those bitwise operators compile. Why? And so I eliminated those two. Did you know you could do a bitwise XOR on a unary XOR on a compliment on a raw pointer? Now you know you didn't hear it from me. Maybe, you know, it's one of those things where as soon as you start the sentence, now you're committed and you have to finish it. But you wish you hadn't started saying it, so I apologize for that. Thank you. Thank you. So let's look at bounds safety. So for bounds safety, here is a demo that we can start with. All right. Here is our, this is all CMP2 again, just because I'm lazy, and I don't want to include or import anything. And so I have words is a vector of strings with some strings in it. Now notice what I'm going to try to do. First equal is a pointer to std string That's set to words.front address. Last is a pointer to soot string set to words.ax address. Remember, most operators are suffix. Unary operators are suffix in C2B2. And those will compile Where you get in trouble is where you do this old style loop where you decide, "I'm going to use pointer iteration, ha ha ha, because I know I can. At least it's always worked for me for 40 years. And it was good enough for Kernaghan and Richie, it's good enough for me, so I'll do first plus plus." But of course, we know that's unsafe. We get a lot of reported vulnerabilities from code like this. And I know that nine times out of ten you get it right. But one time out of 10 is way too much. One time out of 100 is way too much to give. [55:00] wrong. Because if you get it wrong, your program compiles no warnings and appears to run. That's not the world I want to live in. And so Let's try running... Ah, so the first thing CPP Front says, error. No, no, please don't plus plus a pointer. Use stdspan or gslspan instead. So notice again the pattern. Enforce by default the rule we already teach. I'm not inventing anything here. This is in the core guidelines today. Thank you. and tell people what to do instead, which is the thing we already teach them to do today. That's in the core guidelines today as well. In fact, GSL spam is from the core guidelines. And the only reason, by the way, that I mentioned a non-standard thing there, there's two non-standard things that my error messages will recommend. One is GSL span and the other one is GSL narrow. And the only reason I recommend those non-standard things is because they're not yet standard. So we have std_span, and the only difference between GSL_span and std_span is that GSL_span is guaranteed to do balance checking. That's the only difference. Once the committee standardized it, we changed GSL spend to snap to exactly that interface, But because-- If the committee had mandated bounds checking, we would have gotten rid of GSL span. But it didn't, and so we kept GSL spam, but the only delta is that it's bounds checked always. Okay. So what else could we do? Well, just to show you that you get error messages on these other ones as well, let me uncomment them. because there's different ways to do math on pointers. And yes, I don't like any of them, including that for tilde, Pointer, bitwise manipulation is illegal. You stood BitCast. if you must. You know, there's a way to spell it in your code. It's already in the standard library. Do that. While we're at it, this doesn't really fit under bounds safety, but while we're talking about pointers, this is just a convenient place to say, "Oh, why don't we just go delete?" delete something. And if you try to do that, I got to get rid of, I don't like these other error messages, so ignore them. That's just because I don't bail out of errors fast enough. That's on my to-do list. The main error is delete and owning raw pointers are just They don't exist in C52. You just can't do that. Use unique.new, shared.new, or GC.new instead in that order. I'll mention gc.new at the end. That's not implemented here. Thank you. All right, so what should we do instead? Well, the thing that we teach people to do is use stdspan. That's what the error message just said. So let's do that. So S is a std.span that we-- We don't have to say of string because CTAD. S is a std span on words. And while I is less than S dot SI, see how it grows on you? Next, I++. By the way, when I refactored the loops, All of them can have an X. So I just that's part of the refactoring. So I refactored that. So for do while each can have a next. Phrase. That's what that is. Then I just go through and I print and decorate each one and this Thank you. We'll be fine. So there we go. Now, I was going to stop the demo here, but when I talked to Joe Bialik, who I think is a speaker here also was a speaker here also this week. one of our security experts who loves GSL span, is deploying it across Windows. One of the things he mentioned to me is, yeah, so about four years ago, Joe and I sat down, we spent like a day, something like that. He spent more time, I only spent about a day. And we actually went through the last year's worth, I think it was a full year. of bounds, safety, security, vulnerability reports, where we had the source code. that we could look at legally, especially our Windows source code. And we looked through it and looked at what are the root causes for each of them. And in that particular data set, it's not always going to turn out like this, but in that particular data set, Joe and I found every single one would have been caught if you'd just been using a bounds checked span. So that's not always going to be the case, but bound safety is the easiest of these four safeties. Like just use span. gets you like 99% of the way to full bound safety in C++. the span is bounds checked. But as I was checking with him a couple of weeks ago preparing for this talk, Just to validate that, double-check, he said, "Yeah, yeah, that's true, but Okay. You know, it's really a little hokey because about two thirds of the time, like here, You have a container in scope and all you do is [60:00] You is you create this kind of artificial looking extra variable span just to iterate over it, just to satisfy the static analyzer. Wouldn't it be nice if the container itself, its subscript were bounds checked? And I had to admit, I had kind of thought that too. It is a little hokey that we teach people you have to have this extra variable just to get the safety, but you know, the safety is worth it. But that made me think. And so it's as a result of that, that I, I think a couple of weeks ago, you can check the commit log on CPP Front. I forget when it was. I think it was like two, three weeks ago. I thought, well, What if we actually did the balance checking on the original containers, but I can't change the original containers. I want to use the standard library. So when I say seamless interop, no thunking, no wrapping, no marshalling, just direct calls, that's C++. So I'm certainly not going to do anything crazy like wrap the standard library or wrapstid vector. But then I thought, I have a compiler. I think I know how to do this. So let me show you If I say change, just put in, let me just put in a print and decorate the third word, right? How many of you see the bug? Thank you. Okay, more hands are going up. Good. You scared me at first. Of course, I kind of took you by surprise. Yeah, I know. We still all do this. This is not the third word. It's the fourth word. Out of three. Not so good. So what happens if I compile this? I know the reaction I'm going to get from the first one. Wait for it. I Oh, CPP2 passes safety checks. That's not so safe as you were saying, Herb. Demo. Thank you. falls over-- OK, good. I get my command line box back. That's good. That did not do what I wanted it to do, right? What that actually did was access out of bounds, but I happened to get away with it and not crash that particular time. But if you were noticing earlier when I showed you the CPP front command line options, One of them was subscript checks. Enable subscript bound safety contract checks. What would happen if I compiled this... Same thing, but also with dash s. All right. Once the virus scanner decides the executable is okay, Ah, that's better. And see, it's not always that slow. Bound safety violation, out-of-bounds access attempt detected. But if you scroll up here, one other thing in this group of switches is add source info. I happen to know that that's supported in Visual C++. I don't think GCC has that yet. So I think I fake it on GCC or it just doesn't work in GCC, but it does nothing in GCC. But if I add PSA, like public service announcement, that's a total accident. Thank you. And now I run it. demo.cpp2. Line five in function main, bound safety violation. Notice that's in my CPU source. I just used a vector of string no wrapping, and it told me exactly where my out of bounds access was for vector subscript. And I put this under a switch so I could opt into it. But for my code, I'm going to turn that on all the time. And if you're wondering how that works, just look over here. in the generated code, what it does is it wraps in a CPP2 assert in bounds Any subscript operation, so here, like subscript word sub three, where the argument is integral type, And the thing being subscripted has a dot size function. This will work on vector, array, string, span, and do balance checking for you. by checking the size automatically. with just a switch. I kind of like that. By the way, one of the things you'll notice here is if you looked inside the implementation of assert in bounds, What it does is it calls a function called capital B bounds dot expects Boolean expression, and then there's a message too, optionally. Uh, The way that that works is I've also implemented... Contracts. contract implementation, similar in line to what SG21 is doing in CPP2, just because I wanted to be able to write in contract [65:00] It's pre and post conditions and assertions And I'll be happy to make that follow whatever SG21 does. But the way I've built it and the way that I've proposed also for the GSL expects and insurers, is that Every contract group is a named object. So there's one type contract group, which has an expects function on it. Yeah. And then you make an object of that type, and I give you, I think, four or five of them. There's one called type. So type.expects, bounds.expects, null.expects, And so what those let you do is is do different handling for different categories of errors. There's a reason I did this. So again, talking with Joe Bialik. One of the reasons is as we talk to different customers, including Windows, but also including others, One of the requirements is that certain errors like bound errors have to be checked all the time. Like there is no turning those off. But contracts can be used for other things. They can be used for business logic, and they don't want those contracts turned on all the time. So you have to have some granularity. So the way I just chose to do it. was to have a single contract group type, and you just make a variable for every contract group. That's how you give things a name in C++. So if you want to, and each one also has, a set handler just like set terminate handler that you can hook to get the messages yourself. So let's do that. Let's say that you have your own framework for handling contract failures already, because many of us do. and I'm going to just say, let's say you have a So here's this function. It takes a message and I'm just going to print to the console, but this could do whatever you do to call your framework. Right. This is the hook function and you can make it do whatever you want. All I need to do is to install the handler is in one place in Maine, is go CPP two bounds, that's that variable, that contract group, Set handler, call my framework, its address. And then when you do that, Thank you. Thank you. Thank you. Thank you. Thank you. Thank you. Once the virus scanner decides demoexe is okay, there you go. See, we'll do it again just to show that it's faster the second time. There you go. So now you've hooked that error. So now what have I done? What have I accomplished? I have bounds checking for all standard types that have a dot size-- vector, string, array, all the containers that are indexable with an integral subscript. can now be bounce checked automatically without wrapping them, without changing them. And you can hook that to do what you want with that bounce check violation if you don't like the default, where the default is terminate, ascertain and complain to the console. that So that's a quick overview of the bound safety work that I've been doing, And here are some screenshots so you can see them in the handouts that go through all the things that we just talked about. just so you have them for your notes. And there's that handler we just talked about. Now let's talk about lifetime safety. Uh, We have a lifetime safety profile in the core guidelines. It is a static analysis. I have not implemented that. in CPP front. But I've implemented just a little bit, and the little bit is the part about null pointers. Wow. My experiment within an experiment here is, Do we really need null pointers? What if there weren't any? Or at least no pointers you could set to null in CPP2 because you can still get a null pointer from CPP1, so we can still deal with that. Well, we can't do that today because it would break the world. In fact, no... Null is the worst value for a pointer, and it's the default value. Life is just so hard sometimes. And that always makes me think of Kate Gregory's wonderful talk, "What you say when you say nothing at all." When you say nothing at all on the initialization of a pointer, Thank you. if it's not a local pointer, when it's uninitialized, which is even worse. you get a null pointer, which you can't actually dereference. And that doesn't seem kind of nice. So here's what I'm trying instead. Thank you. For... Let's say that we have the same words, vector string example, and I'm going to try to set make p a pointer to sit string that's a null, pointer. Thank you. Thank you. So let's see what happens. Thank you. Error. Pointer cannot be initialized to null or int. [70:00] You mean you can't? You won't let me initialize it to null pointer, I know how to sneak that one past you. I'll do zero because I'm a C++ programmer and I know this trick. Yeah, the compiler knows this trick too, because I know it. And 42 doesn't work either. You can try that. Oh, you know what else doesn't work either? I know some of you are thinking. Come on, admit it. Some of you were thinking that. That one gives a different error message. You can't even say null anywhere in CPP2. That's not because it's pointer initialization. That's because it's a bad word. Like you just we don't talk like that here. . That's how I feel anyway. I may be opinionated. So what should we do instead? Well, This gets into initialization safety too. So it's about lifetime safety and about initialization safety. But let's start with lifetime and pointers. So here's the same example, except I'm not initializing. Notice this is an uninitialized variable. Now that might sound scary, but it's okay, because CPP2 guarantees initialization before use. I let you make any local variable uninitialized. In fact, that's the default. If you say P is a pointer to soot string, Semicolon. there's no equals value, it's like, okay, I'll just allocate space for it, and I'll wait till you give me a constructor. So if we don't initialize it, let's first see what that looks like. Thank you. Local variable P is used before it was initialized. Line 1425, program violates initialization safety guarantee. Okay, so line 14. Yep, there's the initial, we try to dereference it, which is I use. an important use before it's initialized. Okay. So let's initialize it. Let's initialize it. If std rand mod 2, that's like flip a coin, We'll initialize it. How many are happy with that initialization? Pretty thorough, check it in, ship it? Now CPP Front agrees with you, but now look what it said. How many of you wish your compiler gave you that kind of error message today for uninitialized variables? Can I hear an applaud if that's a compiler error message you would like to get today? The reason I asked for applause was not ego, but so that I can see your hands, is so that the compiler implementers watching this video can hear you. Oh, is that what you said too, Dietmar? I stole his line and I didn't even know it. Okay, great minds think alike. I can hear the heckling from the front row. . So let's actually go put back that else branch. And now, Sometimes we'll initialize it to the first word, sometimes we'll initialize it to the last word. And so, Now if we Compile that. Now it says, "Okay, pass the safety checks." And on this particular run, it'll print, The first... Yeah, the first word. Thank you. Cool beans. This is a good segue. And here again are your screenshots of what we just saw. This is a good segue to initialization safety because you'll notice the error I gave in most cases besides the equals null pointer was not about, hey, your pointer is null. It was much more general error. It was you are using a variable that has not been initialized. Okay. And two years ago, I gave a talk that talked about parameter passing and initialization, because they really go together. I made a big deal, rightly, I think, of Fred Brooks' distinction between essential and accidental complexity. Bye. You've got to be able to say a function is virtual somehow, polymorphic somehow. That's an essential thing to say. The cumbersome way we sometimes have to spell that in C++ is is not essential. It's just the ceremony we use in one particular design And that means it's something that we can improve or even remove. So you might remember I went through the C++ literature shown on the screen and I actually, this took me many months. I have a big Excel spreadsheet with over 600 rows where I painstakingly cataloged the rules, not just each item, but each particular piece of advice. It might be in a paragraph, it might be a subheading. In Scott's books, in Andre's and Mai, High Integrity C++, Abseil, a few others, [75:00] . and painstakingly catalogued which ones were essential and accidental. So I told you that part two years ago. What I'll now tell you is, and I also cataloged which ones... I did this by saying which ones would be fixed by CPP2. That was my litmus test for I called it accidental and improvable or essential and improvable only if I had an answer, not because I was being a couch critic, an armchair quarterback. I said it was improvable only if I thought I had a way to do it. And two of the biggest categories were initialization safety and parameter passing. That was of that guidance literature corpus, it was nearly a quarter of all the rules that it taught, often because they're repeated. It turns out every book teaches parameter passing. We just have to say so much about it. Thank you. So I also introduced the concepts of definite first and last use, which you're about to see. So remember, if I have a variable x, And you look at this function, look, there's the first use of X in line one. That's a definite first use because if On any path through this function, That's going to be the first use of X. Thank you. you have two definite last uses of x, because if control flows through either one, it will never touch x again later in the function. So this is actually very simple to reason about. C# has this. I think Ada has something like this. All you do is look at the branch paths. Oops. Loops don't count. If you have a loop, you better have initialized because loops are in some ways like an if because you might not enter them. Right. So, And y has no definite first use, but it has a definite last use. If you get to the end of the function... No exceptions thrown, so a normal return, that is the definite last use of why. So that's just a refresher. That's pretty easy to think about, but we're now going to use those concepts. and I think these ones. Oh, yeah, here we go. For quite a... use some of our familiar functions here. So here I have a function that has an x that is not initialized, and then I try to use it. Oh, did I hit save? I can't tell you how many times I-- why didn't the compiler check that? Oh, I have to save the file. I'm sure nobody here has ever done that before. Okay, so this is the same thing. It's not a pointer this time. It's a string. Local variable. X is used before it was initialized. Violates initialization safety guarantee. So great. Let's uncomment. our code, And these could be nested ifs, and it's the same rule recursively. You either initialize on both branches or neither branch. It's a delightfully recursive rule once you think about it. It's like, oh, yeah, that's... That's the initialization safety rule. So Save that and Now let's run this. "Oh yeah, I have this problem with Microsoft. For some reason, module import of the current modules doesn't find time. Okay. I just discovered that a day or two ago. So that's okay. I'll just change this this is where I really want to show GCC. Okay, I'll just change this to, our good old rand mod2. The nice thing about showing it with time is that I can then rerun the program, and every second the answer slips back and forth, so you can see both branches being exercised. but I can't use time with modules today. So sorry about that. So now let's try this one more time. Thank you. Now we're fine. And so when we run this, okay, it took the one branch. Which branch did it take? It took the branch that was the else in this case, so it filled it from plug. Now, let's actually take a look at what's going on here. And you can watch in the generated code as well to see let me line it up so you can see them side by side Oh, side by side would be like this. Here we go. I don't know. So for a local variable that doesn't have an initializer, I create a CPP2 deferred init of that type. That just allocates storage, it's aligned, etc. correctly, and then it waits for a constructor. And then when you initialize it with equals, I turn that into a construct call. All right. When you initialize it with a function call, which we sometimes need to do, you can fill it with an out parameter. Notice that there's an out parameter on the function. The semantics of an out parameter which is implemented as a CPT2 out of, in this case, a string, is it can take an initial [80:00] or an uninitialized variable. So CPP2 out knows which it's constructed with. Is it constructed with... a full value or with the deferred init of that type And then in the body, When you assign it will call construct, which if the variable is already constructed, it will just assign to it. I want to just point out how cool this is for composable initialization safety, because how often do you put a buffer on the sack or a std array of bytes? And then you call a function, fill from database, memset, memcopy. And you don't want to have to put dead writes into zero initialize a buffer with data that's only going to be overwritten immediately anyway. That's inefficient. Compilers are really bad at getting rid of those dead writes. This way we have one set of initialization rules for every type, for pointers, for strings, for ints, for big buffers, big pods, It's all the same rules and they're composable throughout parameters. And I'll just say, "Please take some time in over the coming weeks, months, as you think about this in the back of your mind. When I've talked to people about parameter passing, it is very common for people to say, "Oh, I like all of what you're doing here, except I question the value of out parameters." And they're actually tied for the most important of all six of them. in and out are the most important. And here's one of the reasons why, is because they give you composable initialization. They give you initialization with program meaningful values guaranteed. no dummy initialization, then I'll initialize it if I remember, which hides this from sanitizers, which hides the lack of initialization from sanitizers. Plus, if you think about what this out parameter is doing, Any function that has an out parameter can be a constructor. You're delegating construction in a safe way, because it's guaranteed statically that it's going to happen dynamically. to this function. So fill is in fact is conceptually a constructor, we have generalized the concept of initialization. to be composable. And I think that's a really cool and valuable thing. And... And you know one other thing that it lets you do that's much more cleanly? And let's go back to mixed. CPP1, CPP2 code. So I apologize, there are some includes here. You will have to look here. Let's scroll down so you don't have to look at them. Thank you. What I'm going to do here is have a function. Here is F is a function that takes no parameters and returns what? Shout it out. Thank you. All right. It returns an int named "i" and a string named "s". How many of you have wished for years you could have this in C++? Bunch of hands. Okay. Okay. So what you do, this follows the same initialization rule. So let's actually generate the code, and you'll see it. Oh, not -p. Ha ha ha. We're using the preprocessor. Sorry. There we go. Great. So if you look at the generated code, I'm just going to go slightly smaller here. If you look at the generated code, What we're doing is we are generating a return struct for the return type. So this is a normal C++ struct. with decorated with an under under, I'm going to count myself as an implementer, so I get to use under under, which means that it is not littering your namespace. One of the problems with doing this yourself is you're littering your namespace because you can't actually define the return struct in the function declaration. You have to define it right before with a semicolon in between, and that litter your namespace. I just generate it for you, and I don't litter your namespace because I double underbar it. And then, but here's the point I'm getting to. All I then do is create two local variables of exactly the same type you just saw. They're just ordinary local variables. with those names. They follow all the rules that they construct on first assignment. and you get all the initialization guarantees that you must initialize before use on any path, including the use where you return and you automatically move from those values. because I generate the student moves so you don't have to, all you do is you say, OK, I return two things. I is an int and S is a string. Oh, look, I construct I here, I construct S here or here, Then I do this other thing, which is not a constructor. That's OK, too. I just use it. And if I forget to construct, it will tell me before I do this. And then I just say, "Return," and I don't need anything [85:00] or the semicolon because I already know the names I'm returning. They're in scope, I and S. And furthermore, if you look at the structured bindings here, Oh, let me actually run the demo so you can see this. Thank you. There. This is why this is mixed, because this is a CPP1. This is a today's main that uses today's structured bindings, and it just works. So I've got a function of multiple return values in CPP2. I call it from a CPP1 function. It just works. And if I wanted to, I could even use the names. if I wanted to. because the names are there. This is so much better than returning an anonymous tuple. or that you have to remember to write the structure yourself and not litter your namespace too badly. Thank you. So that's it for additionalization safety. Thank you. So here are the slide shots, the screen shots that we have from what we saw. Now let's talk briefly about more parameter passing, because You've already seen out parameters, so let's just look at the other five briefly. Here is a function that exercises all of them in, which is the default, but I wrote it out anyway. It could be whitespace, just so you can see it. copy in out. Move forward. And then when I generate this code, Here is what gets generated. Thank you. So let's look at these things one at a time. Thank you. An in-parameter becomes a CPP2::in of that type. This automatically makes it const. So there's no more, oh, if I pass t versus t const, that's the same, except when it's not, none of that. There's also none of this, do I pass it by value or by reference to const because CPP2 in for trivial copyable types that are smaller than that are two pointers or smaller. it'll pass by value, otherwise it'll pass by const reference. So that's just automated. You never need to think about that again in CPP2. at least I don't, and I wrote this for myself, - Okay. Copy just takes a copy, as you can see at the top, but... Then I automatically move notice from definite last use if it's legal. So you'll notice that in the generated code, the first definite last use of B automatically got a move. The second one did not because I'm taking its address. If I wrote std move there, it wouldn't compile in the C++ compiler. because that's illegal. Same for plus plus minus minus and a few others. So I'm smart enough to do that because I learned that because I first emitted move and then it wouldn't compile. So then I went back and fixed it. In out is the easiest one. It's just as if passed by reference, but I'm declaring my intent. Move. Thank you. MOVE also implicitly moves from last use, And at the call site, notice at the bottom, you opt in by saying move. from that argument to call the move parameter. It generates the move. But you never need to say std.move. In fact, it's illegal to say std.move in CPP2. You say move only in these places on the left, which means I never again, I hope, have to teach. Why does std move not move? Well, it does now. Why shouldn't I say return std move? Well, no. You can't, so I don't have to teach that. That's the kind of world I want to live in through my C++ code. Forward is interesting. Notice that forward becomes an auto-ref-ref parameter. Thank you, C++20. This would have been harder to write. this compiler emitting C++17. Could still do it, but it would be harder. It's an auto ref ref parameter with a requires clause, That's C++ concepts. that says it's the same type as string. So the deduce type is string. You can do this today, but almost nobody does. This is how you forward a concrete type. Normally, perfect forwarding only works for template parameter types. You can do that too. But you can also forward concrete types easily. And then we automatically forward from definite last use, as you can see at the bottom. Thank you. It's one last thing. You might have noticed those symbols at the bottom. And... I've talked about consistency in functions. There's one way to declare functions in CPP2. This is my experiment anyway. A name function just has a name in front of the colon. The exact same function can be written at expression scope as an argument [90:00] to std ranges for each. We call it a Lambda today, but I think of it as just a function expression. It's just still the same grammar. It goes through the same function in my parser. And a range for loop body is also a function, because that's the one place in the language with the semantics of... that we invoke some code repeatedly with some arguments that change. So what a range for loop does. The syntax is the same. Amen. And if you've been hearing me all hour and a half long saying how the syntaxes are all the same, how many of you have wondered, "Yeah, but what about capture?" Come on, it's okay. A few. So you think, oh, Herb's oversimplifying because you need lambda capture, right? Right. Thank you. Yeah, you do. But you don't necessarily need a different function declaration syntax to do it. Thank you. And I have to credit Tim Sweeney with this. I was sitting with Tim Sweeney in his office a few years ago talking about this. And he said, well, for Lambda, why don't you just always capture in the body? And by the way, always capture by value. If you want a pointer, take the address and capture that. And especially now that I have pointer safety, and initialization safety. That made a lot of sense to me. So thank you, Tim. I took that one and ran with it. So I appreciate it. So this is how Lambda capture looks. And it's only allowed inside expression functions, but it's the same grammar. It's just restricted outside expressions. If I want s's value, I capture it there, and then later when the lambda is invoked, I use the value. Can you think of any other place in current or current? currently proposed C++ that is like capture, But it's kind of like this, where you capture something and then execute it later. I've heard you. Coroutines, that's a good one. Anything else? There's a few The one I was thinking of is post conditions. So this code works in CPP2 today, the code at top. A post condition, you often want to look at the initial value. So I thought, well, the post condition, you write it at the top of the function, which is conceptually on enter, right? And you execute it when you leave the function, not through an exception. on return at the end or a return in the middle. Yeah. So what I'll do is that's capture. If I want to say for a pushback, pushback one value, that the post condition is that the new collection size is the old collection size plus one. Well, I'll just capture the old collection size here. I'll say collection size capture. And then when the post condition is evaluated at the end, collection size equals that captured value plus one. Works great. Yeah. And if you're familiar with other languages, there's one other place, Thank you. which is string interpolation. All three of them, this code works today in CPP2. They all use the same syntax. And I think it's really important that there's one way to capture, there is one way to declare functions, there's one way to declare anything, because variables, types, and functions are all declared the same way. And then we can get on with writing the business logic that we want, that we get paid to do. . So we've talked about all these things, and I want to make an observation here at the end. Think about the words and the ideas that we've been using for the last hour and a half. I have not said the word monad once. Ha! All of the words and the ideas that we have been using Thank you. are not weird foreign terms from some other language, not weird foreign concepts from some other language. They are things we are deeply familiar with. They're how we talk. They're how we think. Thank you. And I want that to stay that way just nicer. I want all those same familiar things. Just nicer. I still have lots of work to do. So as I continue with this, next I actually have to write explicit parameter lists. This is an order of difficulty, by the way. I still got to do classes, including in my paper D708. You'll notice I proposed unifying Constructors and Assignment throughout parameters. This is a huge unification. Maybe over Christmas, I'll try implementing that and make sure it works out. But I think I'm most of the way there because I've already implemented out parameters. I Reflection generation and metaclasses using the parse tree, because I think that's the right level where you want to generate new things, as if generating source code, but not just text. You're doing it in a disciplined way. and lightweight exceptions, That one's going to be, I'm going to see how far I can get. And I'm talking to some of the implementers about how I can implement that. That might be the hardest one. There might be something I [95:00] Piggyback on, because you need to be involved in the unwinding logic. And then can we get the throw and the catch? to be deterministic time. I'm optimistic, but that one will be the most work. But this is what's on my roadmap to implement next. So this is an experiment in progress. If you want to help, maybe in the medium term, here are some project ideas. And maybe you have other ones. If you do, please email me. Editor support. I took a look at doing syntax highlighting. oh that grammar is so hard that's too hard for me I asked the-- I actually emailed the author of one of VS codes most popular, like the default C++ grammar and said, "How do I just highlight a couple more keywords and this regular expression?" And he emailed me back saying, go to these discussion forums. It's really hard. This is the guy who implemented the one Microsoft is now using. for C++ and visual code. So if you're a guru at that, you're smarter than I am about that, please email me to help. If you're interested in implementing, hey, we have universal function call syntax, what would autocomplete look like to find a non-member function when you say my object dot to find a non-member, please send me mail. If you're interested in putting this on Godbolt, maybe being able to pick which compiler that you use for the second compiler, If you are a garbage collection expert, So six years ago at this conference, I showed, and I still have on my GitHub, GCPP, a prototype garbage collected arena. In C++2, CPP2, everything is garbage-- not everything is garbage. Like, that would be terrible. All memory allocation is done in arenas, One of them should be a "don't pay if you don't use it" but opt in and have it be available garbage collected bubble of memory arena. I wrote a prototype implementation, but I am not a garbage collection expert, so it don't scale. but it's a proof of concept. If someone who knows what they're doing would like to collaborate, please send me mail. And I've done some work too, so I'm not just asking for a pony. Thank you. Cpp2 draw, if you are interested in a basic drawing API with keyboard and text, basically a 21st century curses or con.io.h And especially if you can think of a way to make it header only, Send me mail. And longer term, if you're interested in taking An existing major C++ compiler is PrettyPrinter, and emitting CPP2 code. So I call that front CPP. seemed like a reasonable name. Send me mail. Because one of the things that could do is, in today's code, detect patterns and idioms that in CPP2 can be expressed directly and translate them. So that's a source migration tool. That's a longer-term project idea. Thank you. But let me close with this. What if we could do C++ feels like a new language again, like we did in C++11? It feels like it, but it is still C++. Thank you. but this time do it broadly across the whole language. This is an experiment that can fail. If somebody can find a better way to skin this cat, please do. I want to start a conversation to see if we can try it out. to see if we could get there. The idea would be to support, not compete with C++ evolution, embrace C++20, 23, 26, Embrace the modules we have make import STD the default. Think of it I do as a directed evolution of C++ itself and compiling to today's C++ keeps us honest. And then as we learn things, we can bring parts or the whole thing to ISO C++ because they own the language. That is what C++ is. Thank you. So here are the major things that I talked about today. Just to put it in perspective, let's look at what are the things that I hope, and if this succeeds, I hope I never have to teach or think about anymore. Thank you. Some things just go away, so there's nothing in the CPP2 column. There's no preprocessor, so I don't have to teach include, which standard header to include, ordering dependencies for declarations, those just go away. Thank you. My one declaration syntax gets rid of the vexing parse, East versus West const, but you can still wear the bracelets. The inside out declarations, all the different member function versus non-member versus lambda syntaxes we have, etc. I The inCopy, in, out, and out get rid of, do I pass X or const ref X? If I pass a value or value const parameter, [100:00] Why are those really the same thing except when they're not? What about references? And I would like to just get rid of references throughout the language because they were only designed for parameter passing. That's why we already introduced them in the language. But they're put through the whole type system. So we constantly have to answer questions about them. That makes the language more complex, even when we're not using references in many of those other contexts. Move, std move, why it doesn't move, why it's overused, why not to return std move, why ref ref isn't actually an R value reference if the type is a template parameter type, and then forward, student forwards. How to spell perfect forwarding. That is not something I would teach a regular C++ class. Why a forwarding reference is only for template types, how to forward a non-template type. Named return values. Did you notice that you automatically, when you get the named return values I showed, NRVO just falls out. because you're always returning that same name. So you automate NRVO as well. But I don't want to teach how to enable NRVO, how to return... the downsides of an anonymous tuple versus a struct that litters your namespace, Thank you. New, oh, my goodness, new, delete, owning raw pointers, memory leaks. Zero is an int, except when it's a pointer. Null, don't get me started. Null dereference and pointer arithmetic out of bounds. Extra parentheses for postfix and prefix operators. all the different type queries, all the different type conversions, which gets two paragraphs on its own, and then all the capture syntaxes. Now, I have a question for you. As I've been saying those things, by show of hands, how many of you are generally familiar? Yeah, like at least half of the things I just said sound kind of familiar. Okay, how many of you would like to see an experiment where we can write C++ with all of the performance, with all of the expressiveness, but without writing the things on the right hand side. Is that something you would be interested in? Yes. Okay, pretty much all the hands have gone up. Me too. Thank you. Bjarne Strustrup conjectured famously that C++ could be 10x simpler and safer. My thesis is the Bjarne. is right, that he was right all along, And I'm giving it a go to see if I can prove it by actually implementing it, because I love C++. It's the best language in the world right now for me to write the code that I need and want to write. but I would like to write it just nicer. and I would like to see if this experiment succeeds or one like it that make it inspired from this to do it even better, Can we get to a C++ where five years from now, or 10 years from now, we can be writing C++ with a lot more certainty, with a lot less to teach and learn, with a lot more simplicity, with the same kind of tools other languages enjoy? Do you think that's an experiment worth doing? Is that something you would like to see happen? We have time for like one or two questions if you want to line up at the mics, and we might have one online. We'll just keep right at time, but we'd like to take one or two. Hello, Herb. Maybe I'll take one here, one here, and then online. I apologize. Yes. I don't really have a question, but I just wanted to let everyone in the room know. that as of a few moments ago, Matt added support for CPP front to Compiler Explorer. "Thank you, Matt. You're my hero." And to be clear, You've been my hero for years. Thank you for Compiler Explorer. Okay, that doesn't count as a question. One more over here. Thank you, Matt. Hey, thank you so much. I just wanted to ask, not because it so matters to me, but because C++ is the zero-over abstraction language to me. But some of the abstractions that CPP2 introduces is like the deferred initialization, which has probably a Boolean, which introduces override even to types like integer. So I wonder how do you pretend to tackle all this kind of versus the concept of zero-overhead abstraction. Yes. So the zero-overhead principle isn't zero cost, so you don't pay for what you don't use. So absolutely. First of all, my answer to all such questions is, it's an experiment. Experiments can fail. So we're going to learn by trying. We should try. My second answer in this particular case is, [105:00] You don't pay for it unless you use it. 90% of the time you should just give an initial value when you declare the variable. That's zero overhead. If you choose to defer initialization, maybe because you want to call a different constructor on different branches, or on this branch you want to call a constructor, and on this one you have to call a function that will do the work, Thank you. then you might get the extra Boolean, but you only pay for what you use. That's the principle. And it's an experiment, so we'll see how well it works. But a good observation. Yeah, thanks for the answer. Can I follow up on this? If it's quick, and then we'll take one more. Okay, so one of the things you also added is basically avoiding union and using std variant. This also kind of introduces overhead. So I'm just looking for parts where Like people who want to take control and want the zero over it, they can't because of these rules enforcement. Yeah, so that's a reasonable question. So today we already teach people don't use union, use stu variant instead. So we're already teaching that. I didn't make that up here. If you really want a union, We have full backward source compatibility always available. You pay for it only if you use it. You can write union. still in the CPP2 file. Because we still have CPP one syntax. It's all still there. Okay, thank you. So it seems clear that a big part of your goal here is to make it harder to write bad code. make the default behavior better. So one thing I noticed while you were demonstrating is Why is dash P not default behavior for your compiler? So why is dash P not the default behavior for the compiler? It stands for dash pure CPP2. because I want to allow mixed code. Because dash P means you can only write in CQP2. You can't use include, you can't use macros or anything like that. So why not make mixed code the one you have to put a flag for? Yeah, we could do that too. But let me generalize your question a bit. The reason some of those others are like, for instance, subscript checking and null deref checking, those are two switches. The reason they're opt-in is because I conjecture they're probably pretty cheap, for various memory architecture reasons. And the way branch predictors work, But I don't know they might have non negligible overhead. So I put them under a switch for now to make them opt in but we'll learn as we go. I hope to learn a lot. Do we have any online? Yes. This is a question from Alexei. What is your idea for things that still require the preprocessor now? Specifically, I mean line, file, function. I'd love to have a logging statement or an exception check without having to use a macro. C++23 has Sted Sword's location, but you'd still need a macro in order to capture the current line. Yes. So in general, we're currently working our way through trying to figure out how to get rid of the uses of the preprocessor. Modules are a huge step in that direction. Constexpr is another huge step in that direction. There are a few things left, such as poundline, which I use in CppFront, right, to emit the code, to line up the diagnostics correctly with the original source file. So the goal is to work through that tail and to have replacements for all of the things that currently require macros. One of the things I hope that will get us out The next big step there is by having reflection and generation, because that gets rid of a lot of the remaining use cases I know of where I resort to macros today. Good question. Okay, one more and then we'll close. I think you'll like this one. So there's a lot of teaching that is difficult because of the things we can't put in the books. Oh, it's like I paid you to ask this question. It's not just things we would like to take out. So why not have standard file extensions, standard formatting, standard variable naming conventions, and things like that? Okay. I am a big fan of constraints. Constraints are liberating And to use the frequent quote, constraints are liberated because they focus ingenuity in the directions that matter. Right. I'm not convinced those matter. Some of them may, but naming conventions, one of the things that I mentioned on the slides that I'm taking as a stake in the ground, there are many improvements I think I could make. but I can't justify by saying it would remove X percent of CVEs. it would remove X percent of items in a coding standard book or in an effective C++ book. If you can convince me that a naming convention for variables would get rid of 5% of the things that we currently teach people, Now you've got my attention, but I would need to be able to prove that and measure it. But they do add a lot of [110:00] to the tooling ecosystem, right? Because we have to have tools that... like all these different formatters, and they can't just deduce that, well, you named it this way, so it should probably be const, and you named it wrong, and things like that, right? Okay. So, Let me answer that and then springboard to a second topic. One of the reasons tools need those heuristics is because it's so hard to write a C++ parser. In the CppFront repo, I have written so far only a partial, but a standalone context-free order independent parser that I hope to grow to have the same expressiveness as the full C++ syntax we have today. and a partial people can reuse. Once you have that, you have to lean on heuristics less. So that's one thing to say. But you reminded me of one last thing, so I'll close on this thought. So thank you for that. One of the things that really bugs Bjarne and me and others is people teaching C++ starting with C. Thank you. But that's an artifact of the fact that backwards source compatibility is always mandatory. They do it because they can. because that code works. That's why so many colleges and universities still teach fprintf early in a C++ course, which isn't bad, but it's not C++. And Bjarne wrote a tour of C++ to try to get around this. And Bjarne and others have been trying so hard. Bjarne has written textbooks. Please use my textbook, he says. I wrote it for you. And they say, what? Well, we're used to doing this. And they saw because he wrote this textbook, so here's how you can teach programming using C++, real C++, not starting with C, or starting with C++98, or the older things that have been superseded. One of the advantages of having a name for a syntax, so one of the reasons I say not just syntax2, but I call it CPP2, because if it succeeds, and it will probably fail, but if it succeeds, It also gives you a noun where nobody will be able to ever teach a CPP2 course and start with fprintf because that's not in pure CPP2. Thank you. And if they do, they'll use it with member functions index because they can call it compatibly. But they can't use the old style C++98 things. They can't use null macro. They can't use zero, meaning a pointer or an integer. They can't do many of those things. They can't use naked new where you leak things by default. with a raw pointer and owning raw pointer. So just by having a name, my hope is that in the future, when people teach courses using C++, that we will have a lever where we can make them. use modern C++ because we've said what it is and we've given it a name. That's only if it succeeds. But that's the idea. Because otherwise, I don't see any hope. If we would have... brought C++ courses significantly 10x forward. which is part of making teaching and learning 10x better. On the current path, we would have done it by now. What other lever can we use? Perhaps this could be one. Thank you. So thank you for coming to CPPCon. Thank you for coming to this talk. I hope you enjoyed the whole conference. We'll be right back here again next year. And I hope you have safe travels home wherever those travels take you. Thank you again for coming.