Claude Skill

modern-cpp

Guides C++ code toward modern idioms (C++20/23/26). Use when writing new C++ code, modernizing legacy patterns, or working on security-critical C++. Replaces raw pointers with smart pointers, SFINAE with concepts, printf with std::print, error codes with std::expected.

LLM Mart · 0 points · 7 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download trailofbits-skills-plugins_modern-cpp_skills_modern-cpp-321ccfe.zip · 22 KB
trailofbits/skills 7234 616 forks CC-BY-SA-4.0 Updated 8h ago
Part of trailofbits/skills — 100 skills

Install

skills CLI npx skills add https://github.com/trailofbits/skills/tree/main/plugins/modern-cpp/skills/modern-cpp
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install trailofbits-skills@llmmart
Git git clone https://github.com/trailofbits/skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole trailofbits/skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Modern C++

Guide for writing modern C++ using C20, C23, and C++26 idioms. Focuses on patterns that eliminate vulnerability classes and reduce boilerplate, with a security emphasis from Trail of Bits.

When to Use This Skill

  • Writing new C++ functions, classes, or libraries
  • Modernizing existing C++ code (pre-C++20 patterns)
  • Choosing between legacy and modern approaches
  • Working on security-critical or safety-sensitive C++
  • Reviewing C++ code for modern idiom adoption

When NOT to Use This Skill

  • User explicitly requires older standard: Respect constraints (embedded, legacy ABI)
  • Pure C code: This skill is C++-specific
  • Build system questions: CMake, Meson, Bazel configuration is out of scope
  • Non-C++ projects: Mixed codebases where C++ isn't primary

Anti-Patterns to Avoid

Avoid Use Instead Why
new/delete std::make_unique, std::make_shared Eliminates leaks, double-free
Raw owning pointers std::unique_ptr, std::shared_ptr RAII ownership semantics
C arrays (int arr[N]) std::array<int, N> Bounds-aware, value semantics
Pointer + length params std::span<T> Non-owning, bounds-checkable
printf / sprintf std::format, std::print Type-safe, no buffer overflow
C-style casts (int)x static_cast<int>(x) Explicit intent, auditable
#define constants constexpr variables Scoped, typed, debuggable
SFINAE / enable_if Concepts + requires Readable constraints and errors
Error codes + out params std::expected<T, E> Composable, type-safe errors
union std::variant Type-safe, no silent UB
Raw mutex.lock()/unlock() std::scoped_lock Exception-safe, no deadlocks
std::thread std::jthread Auto-join, stop token support
assert() macro contract_assert (C++26) Visible to tooling, configurable
Manual CRTP Deducing this (C++23) Simpler, no template boilerplate
Macro code generation Reflection (C++26) Zero-overhead, composable

See anti-patterns.md for the full table (30+ patterns).

Decision Tree

What are you doing?
|
+-- Writing new C++ code?
|   +-- Use modern idioms by default (C++20/23)
|   +-- Choose the newest standard your compiler supports
|   +-- See Feature Tiers below
|
+-- Modernizing existing code?
|   +-- Start with Tier 1 (C++20/23) replacements
|   +-- Prioritize by security impact (memory > types > style)
|   +-- See anti-patterns.md for the migration table
|
+-- Security-critical code?
|   +-- Enable compiler hardening flags (see below)
|   +-- Enable hardened libc++ mode
|   +-- Run sanitizers in CI
|   +-- See safe-idioms.md and compiler-hardening.md
|
+-- Using C++26 features?
    +-- Reflection: YES, plan for it (GCC 16+)
    +-- Contracts: cautiously, for new API boundaries
    +-- std::execution: wait for ecosystem maturity
    +-- See cpp26-features.md

Feature Tiers

Features are ranked by practical usability today, not by standard version.

Tier 1: Use Today (C++20/23, solid compiler support)

Feature Replaces Standard
Concepts + requires SFINAE, enable_if C++20
Ranges + views Raw iterator loops C++20
std::span<T> Pointer + length C++20
std::format sprintf, iostream chains C++20
Three-way comparison <=> Manual comparison operators C++20
std::jthread std::thread + manual join C++20
Designated initializers Positional struct init C++20
std::expected<T,E> Error codes, exceptions at boundaries C++23
std::print / std::println printf, std::cout << C++23
Deducing this CRTP, const/non-const duplication C++23
std::flat_map std::map for read-heavy use C++23
Monadic std::optional Nested if-checks on optionals C++23

See cpp20-features.md and cpp23-features.md.

Tier 2: Deploy Now (no standard bump needed)

These improve safety without changing your C++ standard version:

  • Compiler hardening flags — -D_FORTIFY_SOURCE=3, -fstack-protector-strong, -ftrivial-auto-var-init=zero
  • Hardened libc++ — -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST for ~0.3% overhead bounds-checking
  • Sanitizers in CI — ASan + UBSan as minimum; TSan for concurrent code
  • Warning flags — -Wall -Wextra -Wpedantic -Werror

See compiler-hardening.md.

Tier 3: Plan For (C++26, worth restructuring around)

Reflection is the single most transformative C++26 feature. It eliminates:

  • Serialization boilerplate (one generic function replaces per-struct to_json)
  • Code generators (protobuf codegen, Qt MOC)
  • Macro-based registration and enum-to-string hacks

GCC 16 (April 2026) has reflection merged. Plan new code to benefit from it.

Tier 4: Watch (C++26, needs maturation)

  • Contracts (pre/post/contract_assert) — Better than assert(), but no virtual function support and limited compiler support. Adopt cautiously for new API boundaries.
  • std::execution (senders/receivers) — Powerful async framework, but steep learning curve, no scheduler ships with it, and poor documentation. Wait for ecosystem maturity.

See cpp26-features.md.

Compiler Hardening Quick Reference

Essential Flags (GCC + Clang)

-Wall -Wextra -Wpedantic -Werror
-D_FORTIFY_SOURCE=3
-fstack-protector-strong
-fstack-clash-protection
-ftrivial-auto-var-init=zero
-fPIE -pie
-Wl,-z,relro,-z,now

Clang-Specific

-Wunsafe-buffer-usage

Hardened libc++ (Clang/libc++ only)

-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST

Google deployed this across Chrome and their server fleet: ~0.3% overhead, 1000+ bugs found, 30% reduction in production segfaults.

See compiler-hardening.md for the full guide.

Rationalizations to Reject

Rationalization Why It's Wrong
"It compiles without warnings" Warnings depend on which flags you enable. Add -Wall -Wextra -Wpedantic.
"ASan is too slow for production" Use GWP-ASan for sampling-based production detection (~0% overhead).
"We only use safe containers" Iterator invalidation and unchecked optional access are still exploitable.
"Smart pointers are slower" std::unique_ptr has zero overhead vs raw pointers. Measure before claiming.
"Our code doesn't have memory bugs" Google found 1000+ bugs when enabling hardened libc++. So did everyone else.
"C++26 features aren't available yet" C++20/23 features are. Hardening flags work on any standard. Start there.
"Modern C++ is harder to read" std::expected is more readable than checking error codes across 5 out-params.

Best Practices Checklist

  • Use smart pointers for ownership, raw pointers only for non-owning observation
  • Prefer std::span over pointer + length for function parameters
  • Use std::expected for functions that can fail with typed errors
  • Constrain templates with concepts, not SFINAE
  • Enable compiler hardening flags and hardened libc++ in all builds
  • Run ASan + UBSan in CI; add TSan for concurrent code
  • Use constexpr / consteval where possible (UB-free by design)
  • Mark functions [[nodiscard]] when ignoring the return value is likely a bug
  • Prefer value semantics; use std::variant over union, enum class over enum
  • Initialize all variables at declaration

Read Next

Files (skills)
  • references
    • anti-patterns.md 5.5 KB
      # Anti-Patterns: Legacy to Modern C++
      
      Comprehensive reference of legacy C++ patterns and their modern replacements. Each entry explains WHY the modern version is better — not just that it exists.
      
      ## Memory Management
      
      | Avoid | Use Instead | Standard | Why |
      |-------|-------------|----------|-----|
      | `new T` / `delete p` | `std::make_unique<T>()` | C++14 | Eliminates memory leaks, double-free; exception-safe construction |
      | `new T[]` / `delete[] p` | `std::vector<T>` or `std::make_unique<T[]>(n)` | C++14 | Automatic sizing, bounds-aware with `.at()` |
      | Raw owning `T*` | `std::unique_ptr<T>` | C++11 | RAII ownership; zero overhead vs raw pointer |
      | Raw shared `T*` | `std::shared_ptr<T>` via `std::make_shared` | C++11 | Reference-counted lifetime; thread-safe refcount |
      | C arrays `int arr[N]` | `std::array<int, N>` | C++11 | Value semantics, `.size()`, no decay to pointer |
      | `malloc`/`free` | Containers or smart pointers | C++11 | Type-safe, exception-safe, RAII |
      | `memcpy(dst, src, n)` | `std::copy(src, src+n, dst)` or container assignment | C++98 | Type-safe, works with non-trivial types |
      | `memset(buf, 0, n)` | Value initialization or `std::fill` | C++98 | No risk of zeroing non-trivially-constructible types |
      | Pointer + length parameter pairs | `std::span<T>` | C++20 | Carries size, bounds-checkable with hardened mode |
      | `const char*` for non-owning strings | `std::string_view` | C++17 | Carries length, no null-terminator assumption |
      | Nullable `T*` for optional values | `std::optional<T>` | C++17 | Explicit intent, no null dereference risk |
      
      ## Type Safety
      
      | Avoid | Use Instead | Standard | Why |
      |-------|-------------|----------|-----|
      | C-style cast `(int)x` | `static_cast<int>(x)` | C++98 | Explicit intent, greppable, won't silently reinterpret |
      | `reinterpret_cast` for type punning | `std::bit_cast<T>(x)` | C++20 | Defined behavior, `constexpr`-compatible |
      | `union` for variants | `std::variant<A, B, C>` | C++17 | Type-safe access via `std::visit`, no silent UB |
      | `void*` type erasure | `std::any`, `std::variant`, or templates | C++17 | Type-safe, no manual casting |
      | Plain `enum` | `enum class` | C++11 | Scoped, no implicit int conversion |
      | `NULL` or `0` | `nullptr` | C++11 | Unambiguous null pointer, no overload confusion |
      | Implicit single-arg constructors | Mark `explicit` | C++98 | Prevents surprising implicit conversions |
      | Unchecked return values | `[[nodiscard]]` | C++17 | Compiler warns when return value is ignored |
      | `= delete` without message | `= delete("reason")` | C++26 | Documents why the overload is forbidden |
      
      ## Error Handling
      
      | Avoid | Use Instead | Standard | Why |
      |-------|-------------|----------|-----|
      | Error codes + out-params | `std::expected<T, E>` | C++23 | Composable with `.and_then()`, `.transform()`, `.or_else()` |
      | `assert()` macro | `contract_assert` | C++26 | Visible to tooling, configurable enforcement modes |
      | Unchecked `errno` | `std::expected` or exceptions | C++23 | Forces caller to handle the error path |
      | Throwing in constructor for expected failures | Factory returning `std::expected` | C++23 | No exception overhead for expected failure paths |
      
      ## I/O and Formatting
      
      | Avoid | Use Instead | Standard | Why |
      |-------|-------------|----------|-----|
      | `printf` / `sprintf` | `std::format` / `std::print` | C++20/23 | Type-safe, no format string vulnerabilities |
      | `snprintf` for string building | `std::format` | C++20 | Returns `std::string`, no buffer management |
      | `std::cout << a << b << c` | `std::println("{} {} {}", a, b, c)` | C++23 | Readable, no stream state issues, faster |
      
      ## Concurrency
      
      | Avoid | Use Instead | Standard | Why |
      |-------|-------------|----------|-----|
      | `mutex.lock()` / `mutex.unlock()` | `std::scoped_lock` | C++17 | Exception-safe, supports multiple mutexes (no deadlock) |
      | `std::thread` + manual `.join()` | `std::jthread` | C++20 | Auto-joins on destruction, supports stop tokens |
      | `volatile` for thread sync | `std::atomic<T>` | C++11 | Actually guarantees atomic operations and memory ordering |
      | Manual condition variable notify | `std::latch`, `std::barrier` | C++20 | Higher-level, less error-prone synchronization |
      | Hand-rolled atomic CAS loops | `std::atomic::fetch_max/fetch_min` | C++26 | Standard, correct, optimized |
      
      ## Metaprogramming
      
      | Avoid | Use Instead | Standard | Why |
      |-------|-------------|----------|-----|
      | `std::enable_if` / SFINAE | Concepts + `requires` | C++20 | Readable constraints, clear error messages |
      | CRTP for static polymorphism | Deducing `this` | C++23 | No template boilerplate, works with lambdas |
      | Macros for code generation | Static reflection | C++26 | Zero-overhead, composable, type-safe |
      | `std::tuple_element` for pack access | Pack indexing `T...[N]` | C++26 | Direct access, no recursive template machinery |
      | `std::function` for non-owning callbacks | `std::function_ref` | C++26 | No allocation, passable in registers |
      
      ## Containers
      
      | Avoid | Use Instead | Standard | Why |
      |-------|-------------|----------|-----|
      | `std::map` for read-heavy lookups | `std::flat_map` | C++23 | Cache-friendly, 8-17x faster lookup for small-medium maps |
      | `boost::static_vector` | `std::inplace_vector` | C++26 | Standard, no Boost dependency, fallible API |
      | `std::function` (when non-owning) | `std::function_ref` | C++26 | Zero allocation overhead |
      | Raw pointer polymorphism | `std::indirect<T>`, `std::polymorphic<T>` | C++26 | Value semantics, copyable, no manual lifetime management |
      | Manual iterator loops | `std::ranges::` algorithms + views | C++20 | Composable, lazy, no off-by-one |
      
    • compiler-hardening.md 7.3 KB
      # Compiler Hardening
      
      Security-focused compiler and linker configuration for C++ projects. Based on the [OpenSSF Compiler Options Hardening Guide](https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html) and Trail of Bits recommendations.
      
      ## Essential Compiler Flags
      
      ### Warnings
      
      | Flag | Purpose | GCC | Clang |
      |------|---------|-----|-------|
      | `-Wall` | Core warnings | Yes | Yes |
      | `-Wextra` | Additional warnings | Yes | Yes |
      | `-Wpedantic` | Strict ISO compliance | Yes | Yes |
      | `-Werror` | Treat warnings as errors | Yes | Yes |
      | `-Wconversion` | Implicit narrowing conversions | Yes | Yes |
      | `-Wsign-conversion` | Signed/unsigned conversion | Yes | Yes |
      | `-Wformat=2` | Format string issues | Yes | Yes |
      | `-Wunsafe-buffer-usage` | Raw pointer arithmetic | No | Yes |
      
      ### Stack Protection
      
      | Flag | Purpose | GCC | Clang |
      |------|---------|-----|-------|
      | `-fstack-protector-strong` | Stack buffer overflow detection | Yes | Yes |
      | `-fstack-clash-protection` | Prevent stack clash attacks | Yes | Yes |
      
      ### Fortification
      
      ```
      -D_FORTIFY_SOURCE=3
      ```
      
      Adds runtime bounds checks to `memcpy`, `strcpy`, `sprintf`, and other libc functions. Level 3 (GCC 12+, Clang 16+) provides the most coverage.
      
      **Warning:** This flag is silently ignored if optimization is disabled (`-O0`). Always verify it's active:
      
      ```bash
      # Check that _FORTIFY_SOURCE is defined in preprocessor output
      echo '#include <stdio.h>' | gcc -E -dM -D_FORTIFY_SOURCE=3 -O2 - | grep FORTIFY
      ```
      
      Trail of Bits found [20+ projects on GitHub](https://blog.trailofbits.com/2023/04/20/typos-that-omit-security-features-and-how-to-test-for-them/) where `_FORTIFY_SOURCE` was misspelled (e.g., `-FORTIFY_SOURCE=2` instead of `-D_FORTIFY_SOURCE=2`).
      
      ### Auto-Initialization
      
      ```
      -ftrivial-auto-var-init=zero
      ```
      
      Zero-initializes all local variables that would otherwise be uninitialized. Eliminates information leaks from stack variables at minimal performance cost.
      
      ### Position-Independent Code
      
      ```
      -fPIE -pie              # for executables
      -fPIC -shared           # for shared libraries
      ```
      
      Required for ASLR to work effectively.
      
      ### Linker Hardening
      
      | Flag | Purpose |
      |------|---------|
      | `-Wl,-z,relro` | Read-only relocations (partial RELRO) |
      | `-Wl,-z,now` | Immediate binding (full RELRO, protects GOT) |
      | `-Wl,-z,noexecstack` | Non-executable stack |
      
      ### Control Flow Integrity
      
      ```
      -fcf-protection=full    # GCC/Clang on x86 (Intel CET)
      ```
      
      ## Hardened libc++ (Clang/libc++)
      
      libc++ provides hardening modes that add bounds-checking to standard containers with minimal overhead.
      
      ### Modes
      
      | Mode | Flag | What It Checks | Overhead |
      |------|------|---------------|----------|
      | Fast | `_LIBCPP_HARDENING_MODE_FAST` | Bounds on `[]`, `front()`, `back()` | ~0.3% |
      | Extensive | `_LIBCPP_HARDENING_MODE_EXTENSIVE` | Fast + iterator validity, internal invariants | Higher |
      | Debug | `_LIBCPP_HARDENING_MODE_DEBUG` | All checks including O(n) validations | Significant |
      
      ### How to Enable
      
      ```
      -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
      ```
      
      ### Recommendation
      
      - **All builds**: Enable `_FAST` mode — Google measured 0.3% overhead across their fleet
      - **Security-critical code**: Enable `_EXTENSIVE` mode
      - **Development/testing**: Enable `_DEBUG` mode
      
      ### Google's Results
      
      Deployed across Chrome (2022) and entire server fleet:
      - ~0.3% average performance overhead
      - 1000+ previously undetected bugs found
      - 30% reduction in production segfault rate
      
      ## Sanitizers
      
      Runtime instrumentation for detecting bugs that static analysis misses.
      
      ### AddressSanitizer (ASan)
      
      Detects: heap/stack buffer overflow, use-after-free, double-free, memory leaks.
      
      ```
      -fsanitize=address -fno-omit-frame-pointer
      ```
      
      Overhead: ~2x slowdown, ~3x memory. Use in CI, not production.
      
      ### UndefinedBehaviorSanitizer (UBSan)
      
      Detects: signed integer overflow, null pointer dereference, division by zero, misaligned access, out-of-bounds array access.
      
      ```
      -fsanitize=undefined -fno-sanitize-recover=all
      ```
      
      Overhead: minimal (~5-10%). Safe for most CI runs.
      
      ### ThreadSanitizer (TSan)
      
      Detects: data races, lock-order inversions, deadlocks.
      
      ```
      -fsanitize=thread
      ```
      
      Overhead: ~5-15x slowdown. Use for concurrent code testing.
      
      ### MemorySanitizer (MSan)
      
      Detects: reads of uninitialized memory.
      
      ```
      -fsanitize=memory -fno-omit-frame-pointer
      ```
      
      Overhead: ~3x slowdown. Clang-only. Requires all linked code to be instrumented.
      
      ### Combining Sanitizers
      
      | Combination | Compatible? |
      |-------------|-------------|
      | ASan + UBSan | Yes — recommended default |
      | ASan + TSan | **No** — mutually exclusive |
      | ASan + MSan | **No** — mutually exclusive |
      | TSan + UBSan | Yes |
      | MSan + UBSan | Yes |
      
      ### CI Integration
      
      Recommended CI matrix:
      
      ```
      1. Build with ASan + UBSan → run all tests
      2. Build with TSan → run concurrency tests
      3. (Optional) Build with MSan → run all tests (Clang only)
      ```
      
      ### Production: GWP-ASan
      
      For production environments where full ASan is too expensive, use GWP-ASan — a sampling-based allocator that detects heap bugs with near-zero overhead.
      
      - Enabled by default in Chrome, Android, Apple platforms
      - Catches use-after-free and buffer overflow in production
      - See [Trail of Bits guide to GWP-ASan](https://blog.trailofbits.com/2025/12/16/use-gwp-asan-to-detect-exploits-in-production-environments/)
      
      ## Verifying Hardening
      
      Don't trust that flags are applied — verify the binary:
      
      ### checksec
      
      ```bash
      checksec --file=./my_binary
      ```
      
      Checks: RELRO, stack canary, NX, PIE, FORTIFY.
      
      ### readelf
      
      ```bash
      # Check for RELRO
      readelf -l ./my_binary | grep GNU_RELRO
      
      # Check for stack canary symbols
      readelf -s ./my_binary | grep __stack_chk
      
      # Check for FORTIFY
      readelf -s ./my_binary | grep _chk
      ```
      
      ### Compiler Explorer
      
      For quick verification during development, compile on [Compiler Explorer](https://godbolt.org/) and inspect the generated assembly for canary checks and fortified function calls.
      
      ## Complete Hardened Compilation Example
      
      ```bash
      # GCC
      g++ -std=c++23 \
          -Wall -Wextra -Wpedantic -Werror -Wconversion \
          -D_FORTIFY_SOURCE=3 -O2 \
          -fstack-protector-strong -fstack-clash-protection \
          -ftrivial-auto-var-init=zero \
          -fcf-protection=full \
          -fPIE -pie \
          -Wl,-z,relro,-z,now -Wl,-z,noexecstack \
          main.cpp -o my_binary
      
      # Clang (with hardened libc++)
      clang++ -std=c++23 -stdlib=libc++ \
          -Wall -Wextra -Wpedantic -Werror -Wconversion \
          -Wunsafe-buffer-usage \
          -D_FORTIFY_SOURCE=3 -O2 \
          -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST \
          -fstack-protector-strong -fstack-clash-protection \
          -ftrivial-auto-var-init=zero \
          -fcf-protection=full \
          -fPIE -pie \
          -Wl,-z,relro,-z,now -Wl,-z,noexecstack \
          main.cpp -o my_binary
      ```
      
      ## References
      
      - [OpenSSF Compiler Options Hardening Guide](https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html)
      - [libc++ Hardening Modes](https://libcxx.llvm.org/Hardening.html)
      - [Trail of Bits: Typos That Omit Security Features](https://blog.trailofbits.com/2023/04/20/typos-that-omit-security-features-and-how-to-test-for-them/)
      - [Trail of Bits: Use GWP-ASan in Production](https://blog.trailofbits.com/2025/12/16/use-gwp-asan-to-detect-exploits-in-production-environments/)
      - [Google: Retrofitting Spatial Safety](https://security.googleblog.com/2024/11/retrofitting-spatial-safety-to-hundreds.html)
      
    • cpp20-features.md 7 KB
      # C++20 Features (Default Practice)
      
      These features are mature, well-supported (GCC 12+, Clang 14+, MSVC 17.0+), and should be the default way to write C++.
      
      ## Concepts
      
      Constrain templates with readable, declarative requirements.
      
      ### What They Replace
      
      SFINAE and `std::enable_if` — the arcane template tricks that produced unreadable error messages.
      
      ### Standard Concepts
      
      Use concepts from `<concepts>` before writing custom ones:
      
      ```cpp
      #include <concepts>
      
      template <std::integral T>
      T gcd(T a, T b) { return b == 0 ? a : gcd(b, a % b); }
      
      template <std::invocable<int> F>
      void apply(F&& fn, int value) { fn(value); }
      ```
      
      Key standard concepts: `std::integral`, `std::floating_point`, `std::invocable`, `std::copyable`, `std::movable`, `std::regular`, `std::predicate`, `std::ranges::range`.
      
      ### Custom Concepts
      
      ```cpp
      template <typename T>
      concept Serializable = requires(T obj, std::ostream& os) {
          { obj.serialize(os) } -> std::same_as<void>;
          { T::deserialize(os) } -> std::same_as<T>;
      };
      
      void save(const Serializable auto& obj, std::ostream& os) {
          obj.serialize(os);
      }
      ```
      
      ### requires Clauses vs concept Keyword
      
      - **`concept`**: reusable, named constraint — define when used in 2+ places
      - **`requires`**: inline, one-off constraint
      
      ```cpp
      // One-off: use requires
      template <typename T>
          requires (sizeof(T) <= 16)
      void small_copy(T value);
      
      // Reusable: define a concept
      template <typename T>
      concept SmallType = sizeof(T) <= 16;
      ```
      
      ### Before / After
      
      ```cpp
      // BEFORE: SFINAE — cryptic, fragile
      template <typename T,
                typename = std::enable_if_t<std::is_arithmetic_v<T>>>
      T square(T x) { return x * x; }
      // Error: "no matching function for call to 'square'" + 50 lines of template noise
      
      // AFTER: concepts — readable, clear errors
      template <typename T>
          requires std::is_arithmetic_v<T>
      T square(T x) { return x * x; }
      // Error: "constraint 'is_arithmetic_v<string>' not satisfied"
      ```
      
      ## Ranges
      
      Composable, lazy algorithms that operate on range objects instead of iterator pairs.
      
      ### Key Patterns
      
      ```cpp
      #include <ranges>
      #include <algorithm>
      
      std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
      
      // View pipeline — lazy, no intermediate allocations
      auto result = data
          | std::views::filter([](int x) { return x % 2 == 0; })
          | std::views::transform([](int x) { return x * x; });
      
      // Range algorithms — accept ranges directly
      std::ranges::sort(data);
      auto it = std::ranges::find(data, 42);
      auto [mn, mx] = std::ranges::minmax(data);
      ```
      
      To materialize a lazy view into a concrete container, use `std::ranges::to<>()` from C++23 — see [cpp23-features.md](./cpp23-features.md).
      
      ### Useful Views
      
      | View | Purpose |
      |------|---------|
      | `views::filter` | Keep elements matching predicate |
      | `views::transform` | Apply function to each element |
      | `views::take(n)` | First n elements |
      | `views::drop(n)` | Skip first n elements |
      | `views::split(delim)` | Split range on delimiter |
      | `views::join` | Flatten nested ranges |
      | `views::enumerate` | Pairs of (index, element) — C++23 |
      | `views::zip` | Zip multiple ranges — C++23 |
      | `views::chunk(n)` | Group into chunks of n — C++23 |
      | `views::concat` | Concatenate ranges — C++26 |
      
      ## std::span<T>
      
      Non-owning view over contiguous memory. Replaces pointer + length pairs.
      
      ```cpp
      #include <span>
      
      // BEFORE: dangerous — no size information
      void process(int* data, size_t len);
      
      // AFTER: carries size, bounds-checkable
      void process(std::span<int> data) {
          for (auto& val : data) { /* safe iteration */ }
          data[0];    // unchecked by default; bounds-checked under hardened libc++
                      // (_LIBCPP_HARDENING_MODE). span has no .at() until C++26.
      }
      
      // Works with any contiguous container
      std::vector<int> vec = {1, 2, 3};
      std::array<int, 3> arr = {4, 5, 6};
      int c_arr[] = {7, 8, 9};
      
      process(vec);    // implicit conversion
      process(arr);    // implicit conversion
      process(c_arr);  // implicit conversion
      ```
      
      Use `std::span<const T>` for read-only views.
      
      ## std::format
      
      Type-safe string formatting, replacing `sprintf` and iostream chains.
      
      ```cpp
      #include <format>
      
      std::string s = std::format("Hello, {}!", name);
      std::string t = std::format("{:>10.2f}", 3.14159);   // right-aligned, 2 decimal
      std::string u = std::format("{:#x}", 255);            // 0xff
      std::string v = std::format("{:%Y-%m-%d}", std::chrono::system_clock::now());
      ```
      
      ### Custom Formatters
      
      ```cpp
      template <>
      struct std::formatter<Point> {
          constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
          auto format(const Point& p, format_context& ctx) const {
              return std::format_to(ctx.out(), "({}, {})", p.x, p.y);
          }
      };
      
      // Now works:
      std::println("Position: {}", my_point);
      ```
      
      ## Three-Way Comparison (<=>)
      
      Generate all comparison operators from one declaration.
      
      ```cpp
      #include <compare>
      
      struct Point {
          int x, y;
          auto operator<=>(const Point&) const = default;
          // Generates: ==, !=, <, >, <=, >=
      };
      ```
      
      For custom ordering:
      ```cpp
      struct Version {
          int major, minor, patch;
          std::strong_ordering operator<=>(const Version& other) const {
              if (auto cmp = major <=> other.major; cmp != 0) return cmp;
              if (auto cmp = minor <=> other.minor; cmp != 0) return cmp;
              return patch <=> other.patch;
          }
          bool operator==(const Version&) const = default;
      };
      ```
      
      ## Designated Initializers
      
      Named field initialization for aggregates.
      
      ```cpp
      struct Config {
          int port = 8080;
          bool verbose = false;
          int max_connections = 100;
      };
      
      // Clear which fields are being set
      Config cfg = {.port = 3000, .verbose = true};
      // .max_connections gets its default value (100)
      ```
      
      ## std::jthread
      
      Thread with automatic joining and cooperative cancellation.
      
      ```cpp
      #include <thread>
      
      // BEFORE: forgetting to join causes std::terminate
      std::thread t(work);
      // ... if exception thrown here, t.join() never called → crash
      
      // AFTER: auto-joins on destruction
      std::jthread t(work);
      // destructor calls request_stop() then join() — always safe
      
      // Cooperative cancellation via stop token
      std::jthread worker([](std::stop_token stoken) {
          while (!stoken.stop_requested()) {
              do_work();
          }
      });
      worker.request_stop();  // signal the thread to exit
      // destructor waits for thread to finish
      ```
      
      ## Coroutines
      
      Suspendable functions for lazy computation and async I/O.
      
      ### When to Use
      
      - I/O-bound async work (network, file system)
      - Lazy sequence generation (use `std::generator` from C++23)
      - State machines that would otherwise be complex
      
      ### When NOT to Use
      
      - CPU-bound parallel work (coroutines cooperate, they don't parallelize)
      - Simple synchronous code (unnecessary complexity)
      - Reimplementing coroutine types (`generator`, `task`) that libraries already provide
      
      Custom awaiters are fine when adapting async libraries to coroutines — that's often the only way to bridge callback-based APIs.
      
      ### With std::generator (C++23)
      
      ```cpp
      std::generator<int> range(int start, int end) {
          for (int i = start; i < end; ++i)
              co_yield i;
      }
      
      for (auto val : range(0, 10)) {
          std::println("{}", val);
      }
      ```
      
    • cpp23-features.md 7 KB
      # C++23 Features (Usable Today)
      
      These features have solid compiler support (GCC 13+, Clang 17+, MSVC 17.4+) and deliver immediate value. Adopt them now.
      
      ## std::expected<T, E>
      
      Type-safe error handling with monadic chaining. The most impactful C++23 feature for everyday code.
      
      ### What It Replaces
      
      Error codes + output parameters, nullable returns, exceptions at API boundaries where failure is expected (not exceptional).
      
      ### Basic Usage
      
      ```cpp
      #include <expected>
      
      enum class ParseError { empty_input, invalid_format, overflow };
      
      std::expected<int, ParseError> parse_int(std::string_view s) {
          if (s.empty())
              return std::unexpected(ParseError::empty_input);
          // ... parsing logic ...
          return 42;
      }
      
      // Caller must handle both paths
      auto result = parse_int("123");
      if (result) {
          use(*result);  // or result.value()
      } else {
          handle(result.error());
      }
      ```
      
      ### Monadic Chaining
      
      Chain operations without nested if-checks:
      
      ```cpp
      std::expected<Config, Error> load_config(const Path& p) {
          return read_file(p)                    // expected<string, Error>
              .and_then(parse_toml)              // expected<TomlValue, Error>
              .and_then(validate_config)         // expected<Config, Error>
              .transform(apply_defaults);        // expected<Config, Error>
      }
      ```
      
      - `.and_then(f)` — if value, call `f(value)` which returns a new `expected`
      - `.transform(f)` — if value, call `f(value)` and wrap result in `expected`
      - `.or_else(f)` — if error, call `f(error)` which returns a new `expected`
      - `.transform_error(f)` — if error, call `f(error)` and wrap result
      
      ### When to Use vs Exceptions
      
      - **Use `std::expected`**: expected failures (file not found, parse error, validation failure), performance-critical paths, codebases that ban exceptions
      - **Use exceptions**: truly exceptional conditions (out of memory, programmer error), deep call stacks where propagation cost matters
      
      ### Compiler Support
      
      GCC 13+, Clang 17+, MSVC 17.4+
      
      ## std::print / std::println
      
      Type-safe formatted output. Essentially `fmt::print` standardized.
      
      ### Basic Usage
      
      ```cpp
      #include <print>
      
      std::println("Hello, {}!", name);              // with newline
      std::print("x = {}, y = {}\n", x, y);         // without auto-newline
      std::println("{:>10.2f}", 3.14159);            // format spec: right-aligned, 2 decimal
      std::println("{0} is {0}", value);             // reuse argument by index
      ```
      
      ### What It Replaces
      
      ```cpp
      // BEFORE: printf (type-unsafe, format string vulnerabilities)
      printf("Name: %s, Age: %d\n", name.c_str(), age);
      
      // BEFORE: iostream (verbose, stateful, slow)
      std::cout << "Name: " << name << ", Age: " << age << std::endl;
      
      // AFTER: type-safe, readable, fast
      std::println("Name: {}, Age: {}", name, age);
      ```
      
      ### Relationship to {fmt}
      
      `std::print` IS the `{fmt}` library adopted into the standard, by the same author (Victor Zverovich). If you already use `{fmt}`, switching to `std::print` removes one dependency. `{fmt}` still has extras `std::print` lacks (color output, named arguments).
      
      ### Compiler Support
      
      GCC 14+, Clang 18+, MSVC 17.4+
      
      ## Deducing this
      
      Eliminates const/non-const overload duplication and simplifies CRTP.
      
      ### The Problem It Solves
      
      ```cpp
      // BEFORE: duplicated logic for const and non-const
      class Buffer {
          std::vector<char> data_;
      public:
          char& operator[](size_t i) { return data_[i]; }
          const char& operator[](size_t i) const { return data_[i]; }
          // Often 4 overloads: const/non-const x lvalue/rvalue
      };
      
      // AFTER: one function handles all qualifications
      class Buffer {
          std::vector<char> data_;
      public:
          template <typename Self>
          auto&& operator[](this Self&& self, size_t i) {
              // forward_like, not forward: vector::operator[] is not ref-qualified,
              // so forwarding the object alone would drop the rvalue case
              return std::forward_like<Self>(self.data_[i]);
          }
      };
      ```
      
      ### Simplified CRTP
      
      ```cpp
      // BEFORE: arcane template pattern
      template <typename Derived>
      struct Base {
          void interface() {
              static_cast<Derived*>(this)->implementation();
          }
      };
      
      // AFTER: no template parameter needed
      struct Base {
          void interface(this auto&& self) {
              self.implementation();
          }
      };
      ```
      
      ### Recursive Lambdas
      
      ```cpp
      // BEFORE: std::function or Y-combinator
      std::function<int(int)> fib = [&](int n) {
          return n <= 1 ? n : fib(n-1) + fib(n-2);
      };
      
      // AFTER: direct recursion
      auto fib = [](this auto&& self, int n) -> int {
          return n <= 1 ? n : self(n-1) + self(n-2);
      };
      ```
      
      ### Compiler Support
      
      GCC 14+, Clang 18+, MSVC 19.32+
      
      ## std::ranges::to (Materializing Views)
      
      Converts lazy range views into concrete containers. The missing piece from C++20 ranges.
      
      ```cpp
      #include <ranges>
      
      auto vec = data
          | std::views::filter(is_valid)
          | std::views::transform(process)
          | std::ranges::to<std::vector>();  // materialize into a vector
      
      // Works with any container
      auto set = data
          | std::views::transform(normalize)
          | std::ranges::to<std::set>();
      ```
      
      Without `std::ranges::to`, you had to manually construct containers from view iterators — awkward and error-prone. This completes the C++20 ranges story.
      
      ### Compiler Support
      
      GCC 14+, Clang 17+, MSVC 17.4+
      
      ## std::flat_map / std::flat_set
      
      Cache-friendly sorted containers backed by contiguous storage.
      
      ### When to Prefer Over std::map
      
      - **Read-heavy workloads** — lookup is 8-17x faster than `std::map` for small-medium sizes
      - **Small-to-medium maps** (up to ~1000 elements)
      - **Cache-sensitive code** — contiguous memory vs node-based tree
      
      ### When NOT to Use
      
      - **Insert-heavy workloads** — insertion is O(n) due to shifting elements
      - **Large maps with frequent mutations** — `std::map`'s O(log n) insert wins
      - **Iterator stability required** — flat containers invalidate iterators on insert
      
      ### Usage
      
      ```cpp
      #include <flat_map>
      
      std::flat_map<std::string, int> scores;
      scores["alice"] = 100;
      scores["bob"] = 85;
      
      auto it = scores.find("alice");  // fast binary search on sorted vector
      ```
      
      ### Compiler Support
      
      GCC 15+, Clang 18+, MSVC 17.6+
      
      ## std::generator<T>
      
      Lazy coroutine generators for producing sequences on demand.
      
      ### Usage
      
      ```cpp
      #include <generator>
      
      std::generator<int> fibonacci() {
          int a = 0, b = 1;
          while (true) {
              co_yield a;
              auto next = a + b;
              a = b;
              b = next;
          }
      }
      
      // Lazy — only computes as many values as consumed
      for (auto val : fibonacci() | std::views::take(10)) {
          std::println("{}", val);
      }
      ```
      
      ### Compiler Support
      
      MSVC 17.4+, GCC 14+ (partial), Clang catching up. Wait for broader support before relying on it.
      
      ## import std
      
      Import the entire standard library as a module.
      
      ```cpp
      import std;  // replaces ALL #include <...> for standard headers
      
      int main() {
          std::println("Hello from modules!");
      }
      ```
      
      ### Caveats
      
      - Build system support varies (CMake 3.28+ with some generators)
      - Third-party libraries are mostly not module-ready
      - Mixing headers and modules requires care
      - Best for new projects; migration of existing code is nontrivial
      
      ### Compiler Support
      
      GCC 15+, Clang 18+ (partial), MSVC 17.5+
      
    • cpp26-features.md 7.5 KB
      # C++26 Features
      
      C++26 was finalized in March 2026 — the most significant release since C++11. This reference covers the features worth knowing about, ranked by practical impact.
      
      ## Reflection (Game-Changer)
      
      Static reflection is the single most impactful C++26 feature. It lets programs inspect types, members, and functions at compile time and generate code with zero runtime overhead.
      
      ### What It Eliminates
      
      - Per-struct serialization functions (JSON, protobuf, XML)
      - Code generators (protobuf codegen, Qt MOC, flatbuffers)
      - Macro-based registration systems (enum-to-string, type registries)
      - Verbose template metaprogramming with `std::tuple` tricks
      
      ### Core Syntax
      
      The `^^` operator ("reflection operator") reflects a construct into a `std::meta::info` value:
      
      ```cpp
      #include <meta>
      
      constexpr auto type_info = ^^int;  // reflect the type 'int'
      constexpr auto member_info = ^^MyStruct::field;  // reflect a member
      ```
      
      Key `std::meta::` functions:
      - `nonstatic_data_members_of(^^T)` — enumerate struct members
      - `identifier_of(member)` — get member name as string
      - `type_of(member)` — get member type
      - `enumerators_of(^^E)` — enumerate enum values
      
      The splice operator `[:expr:]` converts a reflection back into code.
      
      ### Before / After
      
      ```cpp
      // BEFORE: write this for EVERY struct, update when fields change
      json to_json(const Player& p) {
          return {{"name", p.name}, {"health", p.health},
                  {"x", p.x}, {"y", p.y}, {"score", p.score}};
      }
      
      // AFTER: one function, works for ANY struct, never needs updating
      template <typename T>
      json to_json(const T& obj) {
          json result;
          template for (constexpr auto member :
                        std::meta::nonstatic_data_members_of(^^T)) {
              result[std::meta::identifier_of(member)] = obj.[:member:];
          }
          return result;
      }
      ```
      
      ### Enum-to-String (Classic Pain Point)
      
      ```cpp
      // BEFORE: manual mapping, breaks when you add values
      const char* to_string(Color c) {
          switch (c) {
              case Color::Red: return "Red";
              case Color::Green: return "Green";
              case Color::Blue: return "Blue";
          }
      }
      
      // AFTER: works for any enum, automatically
      template <typename E>
          requires std::is_enum_v<E>
      std::string to_string(E value) {
          template for (constexpr auto e : std::meta::enumerators_of(^^E)) {
              if (value == [:e:])
                  return std::string(std::meta::identifier_of(e));
          }
          return "<unknown>";
      }
      ```
      
      ### Compiler Support
      
      - **GCC 16** (April 2026): Reflection merged in trunk
      - **Clang**: Bloomberg experimental fork ([github.com/bloomberg/clang-p2996](https://github.com/bloomberg/clang-p2996)), mainline catching up
      - **MSVC**: Not yet
      
      ### When to Use / Not Use
      
      - **Use for**: Serialization, deserialization, ORM mapping, debug printing, enum conversion, logging, plugin systems
      - **Don't use for**: Simple code where templates or concepts suffice; reflection adds compile-time complexity
      
      ## Contracts
      
      Language-level preconditions, postconditions, and assertions.
      
      ### Syntax
      
      ```cpp
      // Precondition — checked before function body executes
      void transfer(Account& from, Account& to, int amount)
          pre (amount > 0)
          pre (from.balance >= amount)
          post (from.balance >= 0)
      {
          from.balance -= amount;
          to.balance += amount;
      }
      
      // In-body assertion
      void process(std::span<int> data) {
          contract_assert(!data.empty());
          // ...
      }
      ```
      
      ### Enforcement Modes
      
      | Mode | Behavior |
      |------|----------|
      | `enforce` | Check, call violation handler, terminate if it returns |
      | `observe` | Check, call violation handler, continue execution |
      | `ignore` | Skip the check entirely |
      | `quick_enforce` | Check, terminate immediately (no handler) |
      
      Mode is selected at build time, not per-contract.
      
      ### Before / After
      
      ```cpp
      // BEFORE: preconditions buried in body, invisible to callers
      int divide(int a, int b) {
          assert(b != 0);  // only in debug builds, invisible to IDE
          return a / b;
      }
      
      // AFTER: preconditions on declaration, visible to callers and tools
      int divide(int a, int b)
          pre (b != 0);
      ```
      
      ### Honest Limitations
      
      - **No virtual function contracts** — deferred to a future standard
      - **Controversial design** — Bjarne Stroustrup has publicly criticized the design
      - **Compiler support is experimental** — GCC trunk has it, not production-ready
      - **Side effects in contract expressions are unspecified**
      
      ### Recommendation
      
      Adopt cautiously. Use for new API boundaries where preconditions should be visible. Don't rewrite existing assert() usage wholesale until compilers mature.
      
      ## Memory Safety Improvements
      
      ### Erroneous Behavior for Uninitialized Reads
      
      Uninitialized local variable reads are no longer undefined behavior. They become "erroneous behavior" — well-defined (the variable holds a valid but unspecified value) but diagnosable.
      
      **Practical impact:** Compilers can no longer use "this read is UB, so I can optimize away your null check" reasoning. Just recompiling as C++26 gives you this protection.
      
      **Opt-out:** `[[indeterminate]]` attribute for performance-critical code where you can prove the variable is written before read.
      
      ### Hardened Standard Library
      
      Bounds-checking preconditions added to `operator[]`, `front()`, `back()`, etc. on `std::vector`, `std::span`, `std::string`, `std::string_view`, `std::array`, `std::optional`, `std::expected`.
      
      **Google's deployment results:**
      - Deployed across Chrome and entire server fleet
      - ~0.3% average performance overhead
      - 1000+ previously undetected bugs found
      - 30% reduction in production segfault rate
      
      **Enable today** (no need to wait for C++26):
      ```
      -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
      ```
      
      See [compiler-hardening.md](./compiler-hardening.md) for details.
      
      ## Smaller Wins
      
      ### #embed
      
      Embed binary data at compile time without external tools:
      ```cpp
      // BEFORE: xxd -i data.bin > data.h (slow, 70+ seconds for 40MB)
      static const unsigned char data[] = { 0x89, 0x50, 0x4E, ... };
      
      // AFTER: instant, handled by compiler
      static constexpr unsigned char data[] = {
          #embed "data.bin"
      };
      ```
      
      GCC 15+ and Clang 19+ support `#embed`.
      
      ### std::inplace_vector<T, N>
      
      Fixed-capacity, dynamically-resizable vector with no heap allocation. Replaces `boost::static_vector`.
      
      ```cpp
      std::inplace_vector<int, 64> small_buf;  // max 64 elements, stack-allocated
      small_buf.push_back(42);  // works like vector
      // small_buf.push_back() when full: throws (or use try_push_back for fallible API)
      ```
      
      ### std::function_ref
      
      Non-owning, non-allocating callable wrapper. The `std::string_view` of callables.
      
      ```cpp
      // BEFORE: std::function allocates, type-erases, is nullable
      void for_each(std::span<int> data, std::function<void(int)> fn);
      
      // AFTER: zero allocation, passable in registers
      void for_each(std::span<int> data, std::function_ref<void(int)> fn);
      ```
      
      ### std::indirect<T> and std::polymorphic<T>
      
      Value-semantic wrappers for heap-allocated objects:
      - `std::indirect<T>` — like `unique_ptr` but copyable (deep copies)
      - `std::polymorphic<T>` — adds virtual dispatch with value semantics
      
      ### Placeholder Variables
      
      ```cpp
      auto [x, _, _] = get_triple();     // ignore second and third
      std::lock_guard _(mutex);          // unnamed RAII guard
      for (auto _ : std::views::iota(0, 5)) { /* repeat 5 times */ }
      ```
      
      ### Other Notable Additions
      
      - **Pack indexing** `T...[N]` — direct variadic pack access
      - **Saturation arithmetic** — `std::add_sat`, `std::sub_sat`, `std::mul_sat`
      - **`= delete("reason")`** — document why an overload is deleted
      - **`std::hive`** — bucket container with stable pointers and O(1) insert/erase
      - **`<simd>`** — data-parallel SIMD types
      - **`<linalg>`** — BLAS-based linear algebra
      
    • safe-idioms.md 8.1 KB
      # Safe C++ Idioms
      
      Security patterns organized by vulnerability class. Each section explains what exploitable bugs the pattern prevents and what modern C++ features eliminate them.
      
      ## Memory Safety
      
      ### Ownership: Smart Pointers
      
      Use smart pointers for all owning relationships. Raw pointers are only for non-owning observation.
      
      ```cpp
      // Unique ownership (default choice)
      auto widget = std::make_unique<Widget>(args...);
      process(*widget);  // borrow via reference
      
      // Shared ownership (only when genuinely shared)
      auto shared = std::make_shared<Config>(args...);
      auto copy = shared;  // reference count incremented
      
      // Non-owning observation
      Widget* observer = widget.get();  // does NOT own, must not delete
      std::weak_ptr<Config> watcher = shared;  // non-owning, expires safely
      ```
      
      **Selection guide:**
      - `std::unique_ptr` — default. Zero overhead vs raw pointer. Use for exclusive ownership.
      - `std::shared_ptr` — only when multiple owners genuinely need to extend lifetime. Has overhead (refcount, control block).
      - `std::weak_ptr` — observe shared objects without preventing destruction. Use to break cycles.
      - Raw `T*` / `T&` — non-owning only. Never `delete` a raw pointer you didn't `new`.
      
      ### Non-Owning Views
      
      ```cpp
      // DANGEROUS: pointer + length — no size information
      void process(const char* data, size_t len);
      
      // SAFE: carries size, bounds-checkable
      void process(std::span<const char> data);
      
      // DANGEROUS: null-terminated assumption
      void log(const char* message);
      
      // SAFE: carries length, works with string and string_view
      void log(std::string_view message);
      ```
      
      **Rules:**
      - Function parameters: `std::span<T>` for arrays, `std::string_view` for strings
      - Return values: return owned types (`std::vector`, `std::string`), not views
      - Never return a `span` or `string_view` to a local
      
      ### Optional vs Nullable Pointers
      
      ```cpp
      // DANGEROUS: null pointer used as "no value"
      Widget* find(int id);  // caller might forget to check
      
      // SAFE: explicit optionality
      std::optional<Widget> find(int id);  // caller must unwrap
      
      // Check before use
      if (auto w = find(42)) {
          use(w.value());  // or *w
      }
      ```
      
      ### Value Semantics by Default
      
      Prefer value types over heap-allocated pointer indirection. C++26 adds `std::indirect<T>` and `std::polymorphic<T>` for cases where heap allocation is needed but value semantics are desired.
      
      ## Type Safety
      
      ### Variant Over Union
      
      ```cpp
      // DANGEROUS: union — accessing wrong member is UB
      union Value { int i; double d; std::string s; };  // UB: string in union
      
      // SAFE: variant — type-checked access
      std::variant<int, double, std::string> value;
      value = "hello";
      
      // Visit pattern — compiler ensures all types handled
      std::visit([](auto&& v) { std::println("{}", v); }, value);
      
      // Throws std::bad_variant_access if wrong type
      auto& s = std::get<std::string>(value);
      ```
      
      ### Safe Type Punning
      
      ```cpp
      // DANGEROUS: reinterpret_cast — undefined behavior for most types
      float f = 3.14f;
      int bits = *reinterpret_cast<int*>(&f);  // UB!
      
      // SAFE: bit_cast — defined behavior, constexpr-compatible
      int bits = std::bit_cast<int>(f);  // OK, well-defined
      ```
      
      ### Enum Class
      
      ```cpp
      // DANGEROUS: plain enum — implicit int conversion, leaks names
      enum Color { Red, Green, Blue };
      int x = Red;  // compiles silently
      if (Red == 0)  // compiles silently
      
      // SAFE: enum class — scoped, no implicit conversion
      enum class Color : uint8_t { Red, Green, Blue };
      // int x = Color::Red;     // compile error
      // if (Color::Red == 0)    // compile error
      auto c = Color::Red;       // must use scope
      ```
      
      ### Preventing Misuse
      
      ```cpp
      // Mark functions where ignoring the return is likely a bug
      [[nodiscard]] std::expected<Data, Error> load(const Path& p);
      
      // Prevent implicit conversions
      class UserId {
      public:
          explicit UserId(int64_t id) : id_(id) {}
          // UserId u = 42;  // compile error — must be explicit
      private:
          int64_t id_;
      };
      
      // Document why an overload is forbidden (C++26)
      void process(std::string_view s);
      void process(std::nullptr_t) = delete("passing nullptr is a bug — use empty string");
      ```
      
      ## Integer Safety
      
      ### Narrowing Detection
      
      ```cpp
      #include <utility>
      
      void safe_convert(int64_t big_value) {
          // CHECK: is the value representable in the target type?
          if (std::in_range<int32_t>(big_value)) {
              int32_t small = static_cast<int32_t>(big_value);
              use(small);
          } else {
              handle_overflow();
          }
      }
      ```
      
      ### Saturation Arithmetic (C++26)
      
      ```cpp
      #include <numeric>
      
      // For signal processing, image manipulation, counters
      uint8_t pixel = std::add_sat<uint8_t>(200, 100);  // 255, not 44
      
      // Useful for counters that should not wrap
      size_t count = std::add_sat(count, increment);
      ```
      
      ### Compiler Warnings
      
      Enable `-Wconversion` and `-Wsign-conversion` to catch implicit narrowing at compile time. These flags alone prevent a significant class of integer bugs.
      
      ## Concurrency Safety
      
      ### Lock Management
      
      ```cpp
      // DANGEROUS: manual lock/unlock — exception-unsafe, easy to forget
      std::mutex mtx;
      mtx.lock();
      do_work();      // if this throws, mutex is never unlocked
      mtx.unlock();
      
      // SAFE: scoped_lock — exception-safe, supports multiple mutexes
      {
          std::scoped_lock lock(mtx);
          do_work();  // mutex released when lock goes out of scope
      }
      
      // Multiple mutexes — deadlock-free (sorted lock acquisition)
      std::scoped_lock lock(mutex_a, mutex_b);
      ```
      
      ### Thread Management
      
      ```cpp
      // DANGEROUS: std::thread — forgetting join() calls std::terminate
      std::thread t(work);
      // if exception before join → crash
      
      // SAFE: jthread — auto-joins, supports cooperative cancellation
      std::jthread t([](std::stop_token stoken) {
          while (!stoken.stop_requested()) {
              do_work();
          }
      });
      // destructor calls request_stop() then join()
      ```
      
      ### Atomics
      
      ```cpp
      // DANGEROUS: volatile does NOT provide atomicity or ordering
      volatile int counter = 0;  // data race in multi-threaded code
      
      // SAFE: atomic with explicit memory ordering
      std::atomic<int> counter{0};
      counter.fetch_add(1, std::memory_order_relaxed);  // specify ordering
      ```
      
      ## Initialization Safety
      
      ### constexpr / consteval
      
      Computation at compile time is UB-free by design — the compiler rejects any undefined behavior in constant expressions.
      
      ```cpp
      constexpr int factorial(int n) {
          // If this has UB (e.g., signed overflow), it's a compile error
          int result = 1;
          for (int i = 2; i <= n; ++i) result *= i;
          return result;
      }
      
      consteval int must_be_compile_time(int n) {
          return factorial(n);  // guaranteed compile-time evaluation
      }
      ```
      
      ### Always Initialize
      
      ```cpp
      // DANGEROUS: uninitialized variable
      int x;           // indeterminate value (erroneous behavior in C++26)
      use(x);          // bug
      
      // SAFE: initialize at declaration
      int x = 0;
      int y{};         // value-initialized (zero for scalars)
      auto z = compute_value();
      ```
      
      Enable `-ftrivial-auto-var-init=zero` to catch cases you miss.
      
      ### Rule of Five / Zero
      
      ```cpp
      // Rule of Zero: prefer this — let the compiler generate everything
      struct Config {
          std::string name;
          std::vector<int> values;
          // No destructor, copy/move constructors, or assignment needed
          // Compiler-generated versions do the right thing
      };
      
      // Rule of Five: if you must manage a resource manually
      class FileHandle {
          int fd_;
      public:
          ~FileHandle();
          FileHandle(const FileHandle&);
          FileHandle& operator=(const FileHandle&);
          FileHandle(FileHandle&&) noexcept;
          FileHandle& operator=(FileHandle&&) noexcept;
      };
      // But prefer RAII wrappers (unique_ptr with custom deleter) over this
      ```
      
      ## Iterator Safety
      
      ### Prefer Algorithms Over Raw Loops
      
      ```cpp
      // RISKY: off-by-one, iterator invalidation
      for (auto it = vec.begin(); it != vec.end(); ++it) {
          if (should_remove(*it)) {
              vec.erase(it);  // BUG: invalidates iterator
          }
      }
      
      // SAFE: erase-remove idiom
      std::erase_if(vec, should_remove);  // C++20
      
      // SAFE: range-based algorithms
      auto results = data
          | std::views::filter(is_valid)
          | std::views::transform(process);
      ```
      
      ### Iterator Invalidation Awareness
      
      Know which operations invalidate iterators on which containers. When in doubt, use indices or rebuild the container. Trail of Bits' [Itergator](https://github.com/trailofbits/itergator) tool detects iterator invalidation bugs via CodeQL.
      
  • SKILL.md 8.5 KB
    ---
    name: modern-cpp
    description: Guides C++ code toward modern idioms (C++20/23/26). Use when writing new C++ code, modernizing legacy patterns, or working on security-critical C++. Replaces raw pointers with smart pointers, SFINAE with concepts, printf with std::print, error codes with std::expected.
    ---
    
    # Modern C++
    
    Guide for writing modern C++ using C++20, C++23, and C++26 idioms. Focuses on patterns that eliminate vulnerability classes and reduce boilerplate, with a security emphasis from Trail of Bits.
    
    ## When to Use This Skill
    
    - Writing new C++ functions, classes, or libraries
    - Modernizing existing C++ code (pre-C++20 patterns)
    - Choosing between legacy and modern approaches
    - Working on security-critical or safety-sensitive C++
    - Reviewing C++ code for modern idiom adoption
    
    ## When NOT to Use This Skill
    
    - **User explicitly requires older standard**: Respect constraints (embedded, legacy ABI)
    - **Pure C code**: This skill is C++-specific
    - **Build system questions**: CMake, Meson, Bazel configuration is out of scope
    - **Non-C++ projects**: Mixed codebases where C++ isn't primary
    
    ## Anti-Patterns to Avoid
    
    | Avoid | Use Instead | Why |
    |-------|-------------|-----|
    | `new`/`delete` | `std::make_unique`, `std::make_shared` | Eliminates leaks, double-free |
    | Raw owning pointers | `std::unique_ptr`, `std::shared_ptr` | RAII ownership semantics |
    | C arrays (`int arr[N]`) | `std::array<int, N>` | Bounds-aware, value semantics |
    | Pointer + length params | `std::span<T>` | Non-owning, bounds-checkable |
    | `printf` / `sprintf` | `std::format`, `std::print` | Type-safe, no buffer overflow |
    | C-style casts `(int)x` | `static_cast<int>(x)` | Explicit intent, auditable |
    | `#define` constants | `constexpr` variables | Scoped, typed, debuggable |
    | SFINAE / `enable_if` | Concepts + `requires` | Readable constraints and errors |
    | Error codes + out params | `std::expected<T, E>` | Composable, type-safe errors |
    | `union` | `std::variant` | Type-safe, no silent UB |
    | Raw `mutex.lock()/unlock()` | `std::scoped_lock` | Exception-safe, no deadlocks |
    | `std::thread` | `std::jthread` | Auto-join, stop token support |
    | `assert()` macro | `contract_assert` (C++26) | Visible to tooling, configurable |
    | Manual CRTP | Deducing `this` (C++23) | Simpler, no template boilerplate |
    | Macro code generation | Reflection (C++26) | Zero-overhead, composable |
    
    See [anti-patterns.md](./references/anti-patterns.md) for the full table (30+ patterns).
    
    ## Decision Tree
    
    ```
    What are you doing?
    |
    +-- Writing new C++ code?
    |   +-- Use modern idioms by default (C++20/23)
    |   +-- Choose the newest standard your compiler supports
    |   +-- See Feature Tiers below
    |
    +-- Modernizing existing code?
    |   +-- Start with Tier 1 (C++20/23) replacements
    |   +-- Prioritize by security impact (memory > types > style)
    |   +-- See anti-patterns.md for the migration table
    |
    +-- Security-critical code?
    |   +-- Enable compiler hardening flags (see below)
    |   +-- Enable hardened libc++ mode
    |   +-- Run sanitizers in CI
    |   +-- See safe-idioms.md and compiler-hardening.md
    |
    +-- Using C++26 features?
        +-- Reflection: YES, plan for it (GCC 16+)
        +-- Contracts: cautiously, for new API boundaries
        +-- std::execution: wait for ecosystem maturity
        +-- See cpp26-features.md
    ```
    
    ## Feature Tiers
    
    Features are ranked by **practical usability today**, not by standard version.
    
    ### Tier 1: Use Today (C++20/23, solid compiler support)
    
    | Feature | Replaces | Standard |
    |---------|----------|----------|
    | Concepts + `requires` | SFINAE, `enable_if` | C++20 |
    | Ranges + views | Raw iterator loops | C++20 |
    | `std::span<T>` | Pointer + length | C++20 |
    | `std::format` | `sprintf`, iostream chains | C++20 |
    | Three-way comparison `<=>` | Manual comparison operators | C++20 |
    | `std::jthread` | `std::thread` + manual join | C++20 |
    | Designated initializers | Positional struct init | C++20 |
    | `std::expected<T,E>` | Error codes, exceptions at boundaries | C++23 |
    | `std::print` / `std::println` | `printf`, `std::cout <<` | C++23 |
    | Deducing `this` | CRTP, const/non-const duplication | C++23 |
    | `std::flat_map` | `std::map` for read-heavy use | C++23 |
    | Monadic `std::optional` | Nested if-checks on optionals | C++23 |
    
    See [cpp20-features.md](./references/cpp20-features.md) and [cpp23-features.md](./references/cpp23-features.md).
    
    ### Tier 2: Deploy Now (no standard bump needed)
    
    These improve safety without changing your C++ standard version:
    
    - **Compiler hardening flags** — `-D_FORTIFY_SOURCE=3`, `-fstack-protector-strong`, `-ftrivial-auto-var-init=zero`
    - **Hardened libc++** — `-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST` for ~0.3% overhead bounds-checking
    - **Sanitizers in CI** — ASan + UBSan as minimum; TSan for concurrent code
    - **Warning flags** — `-Wall -Wextra -Wpedantic -Werror`
    
    See [compiler-hardening.md](./references/compiler-hardening.md).
    
    ### Tier 3: Plan For (C++26, worth restructuring around)
    
    **Reflection** is the single most transformative C++26 feature. It eliminates:
    - Serialization boilerplate (one generic function replaces per-struct `to_json`)
    - Code generators (protobuf codegen, Qt MOC)
    - Macro-based registration and enum-to-string hacks
    
    GCC 16 (April 2026) has reflection merged. Plan new code to benefit from it.
    
    ### Tier 4: Watch (C++26, needs maturation)
    
    - **Contracts** (`pre`/`post`/`contract_assert`) — Better than `assert()`, but no virtual function support and limited compiler support. Adopt cautiously for new API boundaries.
    - **std::execution** (senders/receivers) — Powerful async framework, but steep learning curve, no scheduler ships with it, and poor documentation. Wait for ecosystem maturity.
    
    See [cpp26-features.md](./references/cpp26-features.md).
    
    ## Compiler Hardening Quick Reference
    
    ### Essential Flags (GCC + Clang)
    
    ```
    -Wall -Wextra -Wpedantic -Werror
    -D_FORTIFY_SOURCE=3
    -fstack-protector-strong
    -fstack-clash-protection
    -ftrivial-auto-var-init=zero
    -fPIE -pie
    -Wl,-z,relro,-z,now
    ```
    
    ### Clang-Specific
    
    ```
    -Wunsafe-buffer-usage
    ```
    
    ### Hardened libc++ (Clang/libc++ only)
    
    ```
    -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
    ```
    
    Google deployed this across Chrome and their server fleet: ~0.3% overhead, 1000+ bugs found, 30% reduction in production segfaults.
    
    See [compiler-hardening.md](./references/compiler-hardening.md) for the full guide.
    
    ## Rationalizations to Reject
    
    | Rationalization | Why It's Wrong |
    |----------------|----------------|
    | "It compiles without warnings" | Warnings depend on which flags you enable. Add `-Wall -Wextra -Wpedantic`. |
    | "ASan is too slow for production" | Use GWP-ASan for sampling-based production detection (~0% overhead). |
    | "We only use safe containers" | Iterator invalidation and unchecked `optional` access are still exploitable. |
    | "Smart pointers are slower" | `std::unique_ptr` has zero overhead vs raw pointers. Measure before claiming. |
    | "Our code doesn't have memory bugs" | Google found 1000+ bugs when enabling hardened libc++. So did everyone else. |
    | "C++26 features aren't available yet" | C++20/23 features are. Hardening flags work on any standard. Start there. |
    | "Modern C++ is harder to read" | `std::expected` is more readable than checking error codes across 5 out-params. |
    
    ## Best Practices Checklist
    
    - [ ] Use smart pointers for ownership, raw pointers only for non-owning observation
    - [ ] Prefer `std::span` over pointer + length for function parameters
    - [ ] Use `std::expected` for functions that can fail with typed errors
    - [ ] Constrain templates with concepts, not SFINAE
    - [ ] Enable compiler hardening flags and hardened libc++ in all builds
    - [ ] Run ASan + UBSan in CI; add TSan for concurrent code
    - [ ] Use `constexpr` / `consteval` where possible (UB-free by design)
    - [ ] Mark functions `[[nodiscard]]` when ignoring the return value is likely a bug
    - [ ] Prefer value semantics; use `std::variant` over `union`, `enum class` over `enum`
    - [ ] Initialize all variables at declaration
    
    ## Read Next
    
    - [anti-patterns.md](./references/anti-patterns.md) — Full legacy-to-modern migration table (30+ patterns)
    - [cpp20-features.md](./references/cpp20-features.md) — Concepts, ranges, span, format, coroutines
    - [cpp23-features.md](./references/cpp23-features.md) — expected, print, deducing this, flat_map
    - [cpp26-features.md](./references/cpp26-features.md) — Reflection, contracts, memory safety improvements
    - [compiler-hardening.md](./references/compiler-hardening.md) — Flags, sanitizers, hardened libc++
    - [safe-idioms.md](./references/safe-idioms.md) — Security patterns by vulnerability class
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related