Modern C++ Design Patterns (C++23 and Beyond)
Modern C++ Design Patterns (C++23 and Beyond)
The classical Gang-of-Four catalog assumed a language where polymorphism meant virtual functions, error handling meant exceptions or status codes, and abstraction always cost an indirection. C++23 and C++26 dismantle most of those assumptions: explicit object parameters retire CRTP, std::expected retires the exception/error-code dilemma, std::generator retires hand-rolled iterators, and senders/receivers retire ad-hoc thread pools. The unifying philosophy is value semantics, compile-time composition, and zero runtime cost — patterns now express themselves through the type system rather than through inheritance hierarchies.
The ten patterns
The source distills ten patterns that displace older idioms. Each one is a small page on its own; the table below is the map.
| Pattern | Replaces | Key feature | Standard |
|---|---|---|---|
| deducing-this | CRTP | this auto&& explicit object parameter |
C++23 |
| std-expected | exceptions / error codes | monadic and_then / transform / or_else |
C++23 |
| std-generator | hand-rolled iterators | co_yield coroutines composing with ranges |
C++23 |
| std-mdspan | matrix classes, raw pointer math | non-owning multidim view + layout policies | C++23 |
| if-consteval | std::is_constant_evaluated() hacks |
compile-time/runtime strategy in one body | C++23 |
| std-move-only-function | inheritance for callable storage | move-only type-erased polymorphism | C++23 |
| cpp26-static-reflection | macros, codegen, protobuf-for-trivia | ^T, splicers, template for |
C++26 |
| senders-receivers | thread pools, callback chains | std::execution structured concurrency |
C++26 |
| overloaded-visit-pattern | virtual dispatch (closed sets) | std::visit + variadic overload set |
C++17, refined |
| cpp26-contracts | assert() macros |
language-level pre / post clauses |
C++26 |
The three philosophical shifts
From inheritance to value semantics. deducing-this makes the base class non-template again; std-move-only-function stores any callable as a value; overloaded-visit-pattern dispatches on a std::variant with compile-time exhaustiveness instead of a virtual table. The hierarchy of Shape → Circle → Square that defined OOP-era C++ is now a closed std::variant<Circle, Square, ...>, and the compiler will catch a missing case the day you add Triangle.
From runtime to compile time. if-consteval lets a single function carry both implementations and pick at parse time. cpp26-static-reflection generates JSON serialization, ORM mappers, and RPC stubs directly from struct definitions — replacing the macro-and-codegen layer that defined every serialization library since the 2000s. cpp26-contracts hoists assert() into the language so the compiler, not the preprocessor, decides whether to check.
From "throw or return -1" to monadic flow. std-expected makes a fallible call a value, and .and_then(...) / .transform(...) / .or_else(...) compose a pipeline that short-circuits on the first error. This is C++'s direct adoption of railway-oriented-programming and the same Result<T, E> discipline that defines idiomatic Rust — see rust-error-handling for the Rust side of the convergence.
Why C++26 matters more than C++23
C++23 is mostly refinement: better iterators, better error type, better view over memory. C++26 is the larger leap. Static reflection finally gives C++ what every "modern" language (Java annotations, C# attributes, Rust derive macros) had for decades — the ability to ask the compiler about types and generate code from the answer. std::execution introduces structured concurrency with cancellation propagation, scheduler abstraction, and composable pipelines, displacing the patchwork of std::async, raw std::thread, executor-library forks, and callback-chained futures. Contracts add a Design-by-Contract syntax that the optimizer can see, so pre(size > 0) can simultaneously document, check in debug, and inform code generation in release.
Together they pull C++ much closer to the rest of the type-driven, structured-concurrency, derive-driven world that Rust, Swift, and modern Kotlin already inhabit.
The unspoken pattern: convergence with Rust
Read the patterns end-to-end and the convergence with Rust is unmistakable. std::expected<T, E> is Result<T, E>. std::generator<T> is a Rust generator block. std::move_only_function is Box<dyn FnOnce>. overloaded-visit-pattern is match on an enum. Contracts echo the typestate-and-newtype discipline of type-driven-development. The two languages are not merging, but they are converging on the same answers — value semantics, sum types, monadic error flow, structured concurrency, compile-time metaprogramming — because those answers are correct.
The C++ side carries one durable advantage: zero runtime overhead is the default rather than the option. The Rust side carries its own: the borrow checker enforces what C++ leaves to discipline. The patterns above are the C++ side of the bargain — they assume the discipline; C++26 contracts and reflection are tightening the assumption.
What this source doesn't cover
The source is a pattern catalog, not a survey. It deliberately skips ranges-and-views beyond a brief std::generator example, modules (still painful in 2026 despite ~3 years of shipping), the executor proposals around GPU dispatch, and the long-running Safe C++ / Profiles debate. Several of those will need their own pages as additional sources arrive — flagged in the open-questions section of the ingest log.
Linked from
- `if consteval`
- `std::expected`
- `std::generator`
- `std::mdspan`
- `std::move_only_function`
- `std::visit` + Overloaded Pattern
- C++ Coroutines
- C++26 Contracts
- C++26 Static Reflection
- Curiously Recurring Template Pattern (CRTP)
- Deducing `this`
- Expert Rust Design
- Railway-Oriented Programming
- Senders and Receivers (`std::execution`)
- Structured Concurrency
- Type Driven Development
Sources
- Raw/C++/Modern C++ Design Patterns (C++23 and Beyond).md