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 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
Read Next
- anti-patterns.md — Full legacy-to-modern migration table (30+ patterns)
- cpp20-features.md — Concepts, ranges, span, format, coroutines
- cpp23-features.md — expected, print, deducing this, flat_map
- cpp26-features.md — Reflection, contracts, memory safety improvements
- compiler-hardening.md — Flags, sanitizers, hardened libc++
- safe-idioms.md — Security patterns by vulnerability class
1---2name: modern-cpp3description: 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.4---5
6# Modern C++
7
8Guide 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.
9
10## When to Use This Skill
11
12- Writing new C++ functions, classes, or libraries
13- Modernizing existing C++ code (pre-C++20 patterns)
14- Choosing between legacy and modern approaches
15- Working on security-critical or safety-sensitive C++
16- Reviewing C++ code for modern idiom adoption
17
18## When NOT to Use This Skill
19
20- **User explicitly requires older standard**: Respect constraints (embedded, legacy ABI)
21- **Pure C code**: This skill is C++-specific
22- **Build system questions**: CMake, Meson, Bazel configuration is out of scope
23- **Non-C++ projects**: Mixed codebases where C++ isn't primary
24
25## Anti-Patterns to Avoid
26
27| Avoid | Use Instead | Why |
28|-------|-------------|-----|
29| `new`/`delete` | `std::make_unique`, `std::make_shared` | Eliminates leaks, double-free |
30| Raw owning pointers | `std::unique_ptr`, `std::shared_ptr` | RAII ownership semantics |
31| C arrays (`int arr[N]`) | `std::array<int, N>` | Bounds-aware, value semantics |
32| Pointer + length params | `std::span<T>` | Non-owning, bounds-checkable |
33| `printf` / `sprintf` | `std::format`, `std::print` | Type-safe, no buffer overflow |
34| C-style casts `(int)x` | `static_cast<int>(x)` | Explicit intent, auditable |
35| `#define` constants | `constexpr` variables | Scoped, typed, debuggable |
36| SFINAE / `enable_if` | Concepts + `requires` | Readable constraints and errors |
37| Error codes + out params | `std::expected<T, E>` | Composable, type-safe errors |
38| `union` | `std::variant` | Type-safe, no silent UB |
39| Raw `mutex.lock()/unlock()` | `std::scoped_lock` | Exception-safe, no deadlocks |
40| `std::thread` | `std::jthread` | Auto-join, stop token support |
41| `assert()` macro | `contract_assert` (C++26) | Visible to tooling, configurable |
42| Manual CRTP | Deducing `this` (C++23) | Simpler, no template boilerplate |
43| Macro code generation | Reflection (C++26) | Zero-overhead, composable |
44
45See [anti-patterns.md](./references/anti-patterns.md) for the full table (30+ patterns).
46
47## Decision Tree
48
49```
50What are you doing?
51|
52+-- Writing new C++ code?
53| +-- Use modern idioms by default (C++20/23)
54| +-- Choose the newest standard your compiler supports
55| +-- See Feature Tiers below
56|
57+-- Modernizing existing code?
58| +-- Start with Tier 1 (C++20/23) replacements
59| +-- Prioritize by security impact (memory > types > style)
60| +-- See anti-patterns.md for the migration table
61|
62+-- Security-critical code?
63| +-- Enable compiler hardening flags (see below)
64| +-- Enable hardened libc++ mode
65| +-- Run sanitizers in CI
66| +-- See safe-idioms.md and compiler-hardening.md
67|
68+-- Using C++26 features?
69 +-- Reflection: YES, plan for it (GCC 16+)
70 +-- Contracts: cautiously, for new API boundaries
71 +-- std::execution: wait for ecosystem maturity
72 +-- See cpp26-features.md
73```
74
75## Feature Tiers
76
77Features are ranked by **practical usability today**, not by standard version.
78
79### Tier 1: Use Today (C++20/23, solid compiler support)
80
81| Feature | Replaces | Standard |
82|---------|----------|----------|
83| Concepts + `requires` | SFINAE, `enable_if` | C++20 |
84| Ranges + views | Raw iterator loops | C++20 |
85| `std::span<T>` | Pointer + length | C++20 |
86| `std::format` | `sprintf`, iostream chains | C++20 |
87| Three-way comparison `<=>` | Manual comparison operators | C++20 |
88| `std::jthread` | `std::thread` + manual join | C++20 |
89| Designated initializers | Positional struct init | C++20 |
90| `std::expected<T,E>` | Error codes, exceptions at boundaries | C++23 |
91| `std::print` / `std::println` | `printf`, `std::cout <<` | C++23 |
92| Deducing `this` | CRTP, const/non-const duplication | C++23 |
93| `std::flat_map` | `std::map` for read-heavy use | C++23 |
94| Monadic `std::optional` | Nested if-checks on optionals | C++23 |
95
96See [cpp20-features.md](./references/cpp20-features.md) and [cpp23-features.md](./references/cpp23-features.md).
97
98### Tier 2: Deploy Now (no standard bump needed)
99
100These improve safety without changing your C++ standard version:
101
102- **Compiler hardening flags** — `-D_FORTIFY_SOURCE=3`, `-fstack-protector-strong`, `-ftrivial-auto-var-init=zero`
103- **Hardened libc++** — `-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST` for ~0.3% overhead bounds-checking
104- **Sanitizers in CI** — ASan + UBSan as minimum; TSan for concurrent code
105- **Warning flags** — `-Wall -Wextra -Wpedantic -Werror`
106
107See [compiler-hardening.md](./references/compiler-hardening.md).
108
109### Tier 3: Plan For (C++26, worth restructuring around)
110
111**Reflection** is the single most transformative C++26 feature. It eliminates:
112- Serialization boilerplate (one generic function replaces per-struct `to_json`)
113- Code generators (protobuf codegen, Qt MOC)
114- Macro-based registration and enum-to-string hacks
115
116GCC 16 (April 2026) has reflection merged. Plan new code to benefit from it.
117
118### Tier 4: Watch (C++26, needs maturation)
119
120- **Contracts** (`pre`/`post`/`contract_assert`) — Better than `assert()`, but no virtual function support and limited compiler support. Adopt cautiously for new API boundaries.
121- **std::execution** (senders/receivers) — Powerful async framework, but steep learning curve, no scheduler ships with it, and poor documentation. Wait for ecosystem maturity.
122
123See [cpp26-features.md](./references/cpp26-features.md).
124
125## Compiler Hardening Quick Reference
126
127### Essential Flags (GCC + Clang)
128
129```
130-Wall -Wextra -Wpedantic -Werror
131-D_FORTIFY_SOURCE=3
132-fstack-protector-strong
133-fstack-clash-protection
134-ftrivial-auto-var-init=zero
135-fPIE -pie
136-Wl,-z,relro,-z,now
137```
138
139### Clang-Specific
140
141```
142-Wunsafe-buffer-usage
143```
144
145### Hardened libc++ (Clang/libc++ only)
146
147```
148-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
149```
150
151Google deployed this across Chrome and their server fleet: ~0.3% overhead, 1000+ bugs found, 30% reduction in production segfaults.
152
153See [compiler-hardening.md](./references/compiler-hardening.md) for the full guide.
154
155## Rationalizations to Reject
156
157| Rationalization | Why It's Wrong |
158|----------------|----------------|
159| "It compiles without warnings" | Warnings depend on which flags you enable. Add `-Wall -Wextra -Wpedantic`. |
160| "ASan is too slow for production" | Use GWP-ASan for sampling-based production detection (~0% overhead). |
161| "We only use safe containers" | Iterator invalidation and unchecked `optional` access are still exploitable. |
162| "Smart pointers are slower" | `std::unique_ptr` has zero overhead vs raw pointers. Measure before claiming. |
163| "Our code doesn't have memory bugs" | Google found 1000+ bugs when enabling hardened libc++. So did everyone else. |
164| "C++26 features aren't available yet" | C++20/23 features are. Hardening flags work on any standard. Start there. |
165| "Modern C++ is harder to read" | `std::expected` is more readable than checking error codes across 5 out-params. |
166
167## Best Practices Checklist
168
169- [ ] Use smart pointers for ownership, raw pointers only for non-owning observation
170- [ ] Prefer `std::span` over pointer + length for function parameters
171- [ ] Use `std::expected` for functions that can fail with typed errors
172- [ ] Constrain templates with concepts, not SFINAE
173- [ ] Enable compiler hardening flags and hardened libc++ in all builds
174- [ ] Run ASan + UBSan in CI; add TSan for concurrent code
175- [ ] Use `constexpr` / `consteval` where possible (UB-free by design)
176- [ ] Mark functions `[[nodiscard]]` when ignoring the return value is likely a bug
177- [ ] Prefer value semantics; use `std::variant` over `union`, `enum class` over `enum`
178- [ ] Initialize all variables at declaration
179
180## Read Next
181
182- [anti-patterns.md](./references/anti-patterns.md) — Full legacy-to-modern migration table (30+ patterns)
183- [cpp20-features.md](./references/cpp20-features.md) — Concepts, ranges, span, format, coroutines
184- [cpp23-features.md](./references/cpp23-features.md) — expected, print, deducing this, flat_map
185- [cpp26-features.md](./references/cpp26-features.md) — Reflection, contracts, memory safety improvements
186- [compiler-hardening.md](./references/compiler-hardening.md) — Flags, sanitizers, hardened libc++
187- [safe-idioms.md](./references/safe-idioms.md) — Security patterns by vulnerability class