C++26 was finalized at the Croydon meeting in March 2026 (Sutter’s trip report). For memory safety the headline result is good news mixed with one significant deferral: the standard-library hardening + contracts + reflection trio shipped, but the [[profiles::enforce]] attribute did not — it moves to C++29 via SG23’s continued work on Stroustrup’s P3984 type-safety profile, built on Dos Reis’s P3589 general framework. That deferral is widely misreported. This page is the honest-and-actionable version: what you can turn on today for memory safety in C++26, what’s deferred to C++29, and the reflection-driven user-library bridge that works on stock clang and GCC right now.
Today
What’s in C++26 (finalized 2026-03-28)
| Paper | Author(s) | What it gives you |
|---|---|---|
| P2900 Contracts | Berne, Doumler, Krzemieński, et al. | pre / post clauses on functions; contract_assert in bodies. Multiple evaluation semantics (ignore / observe / enforce). Adopted Hagenberg Feb 2025; Croydon vote 114-12-3 (the contested feature). |
| P3471 Hardened standard library | Varlamov, Dionne | First standard user of contracts: turns common UB in the stdlib (out-of-range vector::operator[], deref of null unique_ptr, string overruns) into contract violations when the implementation runs in hardened mode. |
| P2996 Reflection | Childers, Sutton, Revzin, Yang, et al. | Compile-time introspection of types, members, annotations. Powers the user-library profile demo below. Covered in depth in first-reflection and the rest of the reflection series. |
What’s NOT in C++26 (deferred to C++29)
| Paper | Author(s) | Status |
|---|---|---|
| P3081 Core safety profiles | Herb Sutter | R2 dated Feb 2025; not adopted in C++26. The proposal that defined [[profiles::enforce(bounds, type, lifetime)]] and the per-rule mechanics. |
| P3589 Profiles: the framework | Gabriel Dos Reis | R2 May 2025. The general framework (separate from any specific profile). SG23 working draft for C++29. |
| P3543 Response to P3081 | Gill, Jabot, Lakos, Berne, Doumler | R0 2024. Bloomberg-led counter-paper that contributed to the deferral. Argues granularity / error-reporting / migration path all need rework. |
| P3984 A type-safety profile | Bjarne Stroustrup | D-R-A-F-T R0 dated Jan 28 2026. The type-safety profile re-cast on top of the Dos Reis framework. SG23 focus at Croydon. |
| P4158 Subsetting and restricting C++ for memory safety | Oliver Hunt (Apple, WebKit) | Practical experience report on hardening 4M LOC at WebKit; validates the subsetting approach. |
No shipping compiler implements [[profiles::enforce]] today. clang-p2996 and GCC 16.1 ship reflection (P2996) and partial contracts (P2900) support; neither advertises the profile attribute. The mechanisms in the next subsection are what gets you profile-equivalent coverage on real toolchains right now.
Pre-Brno update (May 2026 mailing)
The May 2026 WG21 mailing (the pre-Brno mailing, 116 papers, dropped early May) sharpens the C++29 direction with three load-bearing papers:
| Paper | Author(s) | What it does |
|---|---|---|
| P2000R0 Direction for ISO C++29 | Vandevoorde, Garland, McKenney, Orr, Stroustrup, Wong (Feb 2026) | The formal direction-setting paper for C++29, naming safety as the headline axis. |
| D3704R0 A type-safety profile | Stroustrup (Jan 2026) | Revision of P3984; explicit alignment to Dos Reis’s P3589 framework. SG23 focus at Brno. |
| P3970R0 Profiles and Safety: a call to action | Vandevoorde, Garland, McKenney, Orr, Stroustrup, Wong (Jan 2026) | Co-signed by all the C++29-direction authors; argues for concrete profile timelines and reaffirms safety as a non-negotiable C++29 deliverable. |
Brno (8–13 June 2026) is the next milestone; SG23 is expected to refine the type-safety profile against P3589’s framework. What this changes for the page above: nothing in today’s recommendations — you still flip the hardened-stdlib macro, you still run clang-tidy with the safety check sets, you still use the reflection-driven user-library profile below. The May mailing is signal that the C++29 timeline is being defended, not slipping further. Re-checked 2026-05-17.
What you CAN flip on today
For the bug classes that the deferred profile attribute would have caught, real workable mechanisms exist on shipping toolchains. The following table is the “what to actually turn on” view — the rest of this section walks each row in detail.
| Mechanism | How to enable | Catches |
|---|---|---|
| Hardened stdlib | clang: -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FASTGCC: -D_GLIBCXX_ASSERTIONS (or -fhardened)MSVC: _ITERATOR_DEBUG_LEVEL=1 | vector::operator[] OOB, deref of null unique_ptr, string overruns |
clang-tidy pro-* checks | clang-tidy 21+ (cross-tool: works with clang, GCC, MSVC builds) | Pointer arithmetic, array decay, unchecked container access, unsafe casts (the literal Core Guidelines profile rules) |
| MSVC Lifetime Profile | MSVC: /analyze with EspXEngine.(no clang/GCC equivalent in mainline) | Dangling pointers, use-after-scope (Sutter’s original Lifetime Profile design, 2018-onward) |
[[clang::lifetimebound]] | clang: [[clang::lifetimebound]]GCC: [[gnu::access]] cousinMSVC: partial | API-boundary lifetime hints |
| C++26 Contracts (P2900) | clang-p2996: -fcontracts=enforceGCC 16.1: warnings only MSVC: roadmap | User-written precondition / postcondition violations |
Hardened standard library (P3471)
This is the highest-leverage thing you can do today for memory safety in C++26. It’s standardised, deployed at scale, and free of code change at call sites. Google’s deployment across hundreds of millions of LOC reports 0.3% performance impact and 1000+ bugs found, including security-critical ones (cited in P3471R4).
Three implementations, three knobs:
# libc++ (clang) -- four modes; FAST is the production default
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST # security-critical only
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE # + cheap logic-error checks
-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG # + expensive checks
# libstdc++ (GCC)
-D_GLIBCXX_ASSERTIONS # lightweight precondition checks
-fhardened # umbrella: ASSERTIONS + FORTIFY_SOURCE=3 + stack
# protector + PIE + RELRO (GCC-only)
-D_GLIBCXX_DEBUG # heavy: catches iterator invalidation; ABI-breaking
# MSVC
_ITERATOR_DEBUG_LEVEL=1 # release-mode iterator checks
The libc++ modes are non-ABI-breaking by default; iterator-bounds checks require an ABI opt-in build (_LIBCPP_ABI_BOUNDED_ITERATORS_IN_VECTOR). The libstdc++ light path is also non-ABI; _GLIBCXX_DEBUG is ABI-breaking and catches more (including iterator invalidation that libc++‘s hardened mode misses — see LLVM #128627).
CMake snippet for the production-default fast mode:
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR
CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
add_compile_definitions(
_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST
)
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
add_compile_options(-fhardened) # GCC 14+ umbrella
elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
add_compile_definitions(_ITERATOR_DEBUG_LEVEL=1)
endif()
For more, see libc++‘s Hardening modes documentation and the OpenSSF Compiler Options Hardening Guide.
clang-tidy cppcoreguidelines-pro-* checks
The “Pro” prefix in these clang-tidy checks stands for Profile — they’re the Core Guidelines bounds + type profiles, implemented in a static analyzer. Available for years; works on stock clang-tidy across clang, GCC, and MSVC builds (clang-tidy is a cross-tool).
# .clang-tidy fragment for a sample bounds + type profile gate
Checks: >
cppcoreguidelines-pro-bounds-pointer-arithmetic,
cppcoreguidelines-pro-bounds-constant-array-index,
cppcoreguidelines-pro-bounds-array-to-pointer-decay,
cppcoreguidelines-pro-bounds-avoid-unchecked-container-access,
cppcoreguidelines-pro-type-cstyle-cast,
cppcoreguidelines-pro-type-reinterpret-cast,
cppcoreguidelines-pro-type-static-cast-downcast,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-pro-type-union-access,
cppcoreguidelines-pro-type-vararg
WarningsAsErrors: '*'
Tune the disable list for C-API boundaries — pointer arithmetic on a char* from a C library is unavoidable in some adapters, and turning the check on without exemptions produces noise. The official check list lives at clang.llvm.org/extra/clang-tidy/checks/list.html.
MSVC Lifetime Profile via /analyze
Sutter’s original Lifetime Profile (the source of the Core Guidelines lifetime story) has had a partial MSVC implementation since 2018, by Neil MacIntosh and Kyle Reed. It runs in MSVC’s static analyzer, surfaces in Visual Studio as live IDE diagnostics, and catches a meaningful subset of dangling-pointer + use-after-scope cases. Enable via /analyze (or /analyze:plugin EspXEngine.dll for the Code Analysis variant). Not standard, not portable, but the most mature lifetime-profile implementation that exists today on any compiler.
The clang side has an experimental -Wlifetime fork by Matthias Gehre and Gábor Horváth (mgehre/llvm-project) — partial, not in mainline, last actively-maintained around 2020-2021.
[[clang::lifetimebound]] and [[gnu::access]] attributes
Narrow but deployable. [[clang::lifetimebound]] on a parameter says “the return value’s lifetime is bound to this parameter”; the compiler then warns when the bound lifetime would outlive the parameter. [[gnu::access]] (GCC) gives buffer-size hints the compiler uses for bounds analysis. Pair these on API boundaries where you control both sides.
// clang: warns when the returned reference outlives `s`.
[[clang::lifetimebound]] auto first(std::span<int> s) -> int&;
// GCC: tells the compiler buf has at least `n` writeable elements,
// enabling bounds warnings at call sites.
void fill(int* buf, size_t n) [[gnu::access(write_only, 1, 2)]];
Contracts (P2900)
Now in C++26. pre / post clauses on function declarations + contract_assert in bodies, with multiple evaluation semantics (ignore / observe / enforce / quick-enforce). Compiler implementations are partial:
- clang-p2996: parses the syntax; enforcement opt-in via
-fcontracts=enforce. - GCC 16.1: emits warnings only.
- MSVC: roadmap, not yet shipped.
The hardened stdlib (P3471) is the first user of contracts — it formalises bound-checks-as-contract-violations. For application-side preconditions, contracts let you state the invariant once on the function and have the toolchain enforce it. Full coverage of contracts in production is a planned cpp-contracts-2026 toolset entry.
What regulated industries do today
For the safety-critical end of the spectrum — automotive, aerospace, medical — the C++ standard alone has never been enough. The active baseline is:
- MISRA C++:2023 — the current unified C++ coding standard. Targets C++17, defines 179 rules + directives. Result of the MISRA + AUTOSAR consortia merger announced in January 2019; MISRA C++:2023 (October 2023) supersedes both MISRA C++ 2008 and AUTOSAR C++14 with a single source of truth. Clang-tidy carries a partial implementation; commercial tools (Helix QAC, PRQA, Coverity, Parasoft, LDRA, Axivion) carry certified rule sets.
- AUTOSAR C++14 — the last standalone AUTOSAR C++ coding guideline (2017-2018). After the 2019 merger no separate AUTOSAR C++17 / C++20 release exists; new Adaptive AUTOSAR projects target MISRA C++:2023 instead. Legacy ASIL-rated codebases that pre-date the merger may still be on AUTOSAR C++14 + a transition plan.
- SEI CERT C++ — secure-coding standard organised by category (STR, INT, MEM, …). Open, freely available; clang-tidy’s
cert-*checks implement a portion. - C++ Core Guidelines — the lingua franca; clang-tidy’s
cppcoreguidelines-*checks enforce a portion. Cited by everything else.
When [[profiles::enforce]] lands in C++29, these cluster into a smaller set of language-level guarantees. Until then, the cluster section cpp-coding-standards (incoming MR11) covers the rule-set choice in depth.
Reflection today (C++26, clang-p2996 + GCC 16.1)
For per-class structural rules (“no raw pointer members”, “no C-style arrays”, “all members trivially copyable”), C++26 reflection lets a user library enforce profile-equivalent constraints today, on stock clang or GCC, without waiting for the C++29 attribute. The pattern: a consteval predicate that walks the class’s data members and static_asserts on each one. Add a forbidden member type, the build refuses; remove it, the build proceeds.
template <typename T>
consteval bool meets_basic_safety_profile() {
constexpr auto ctx = std::meta::access_context::unchecked();
template for (constexpr auto m
: std::define_static_array(
std::meta::nonstatic_data_members_of(^^T, ctx))) {
using FieldT = [:std::meta::type_of(m):];
static_assert(!std::is_pointer_v<FieldT>,
"safety profile violation: raw pointer member");
static_assert(!std::is_array_v<FieldT>,
"safety profile violation: C-style array member");
}
return true;
}
struct GoodSession {
std::string username;
std::vector<std::byte> buffer;
std::unique_ptr<int> counter;
std::array<std::byte, 16> tag;
};
static_assert(meets_basic_safety_profile<GoodSession>());
Add int* raw_count; as a member of GoodSession and the build fails with static_assert failed: "safety profile violation: raw pointer member". The error message is the same shape a future P3984-enforced profile would emit, just sourced from a user library instead of the language.
What this aligns with:
- C++ Core Guidelines I.11 (never transfer ownership by raw pointer), F.7 / F.20 / F.43 (ownership rules), ES.42 (keep use of pointers simple).
- MISRA C++:2023 (the unified MISRA + AUTOSAR successor) prohibitions on raw pointer arithmetic and unbounded C arrays at the API level. The reflection-driven check enforces the equivalent rule one class at a time, by construction.
- SEI CERT C++ STR (string-handling) and MEM (memory-management) categories converge on the same property: typed access at the boundary, no raw-pointer gymnastics.
The full demo (posts/toolset/memory-safety-cpp26-and-beyond/examples/reflect-profile.cpp ) ships with two commented-out BadSession* structs the reader can uncomment to see the static_assert fire with the matching profile-violation message:
docker run --rm -it \
-v "$PWD":/work -w /work \
ghcr.io/wrocpp/cpp-reflection:2026-05 \
bash -c 'clang++ -std=c++26 -freflection-latest -stdlib=libc++ posts/toolset/memory-safety-cpp26-and-beyond/examples/reflect-profile.cpp -o /tmp/profile && LD_LIBRARY_PATH=/opt/p2996/clang/lib/aarch64-unknown-linux-gnu /tmp/profile'expected output
GoodSession passes the basic safety profile check.
Try uncommenting BadSession* in the source to see the
static_assert fire with the profile-violation reason.What this user-library check does NOT solve:
- Function-body checks (e.g. “no raw pointer arithmetic inside a function”): need the C++29 profile attribute to enforce at statement-level.
- Cross-translation-unit checks (e.g. “no callsite passes a non-
std::spanto a[[profile-enforced]]function”): need the language attribute and link-time tooling. - Migration tooling: a profile attribute can carry a phased “warn-only” mode; user-library
static_assertis binary “compiles or doesn’t”.
For new code at the type-shape level, the reflection-driven harness already gives you “this class meets the profile, by construction” — and the same harness composes with [[profiles::enforce]] the day it becomes available, rather than competing with it.
Where this is heading (C++29)
The committee did not abandon profiles — they restructured. Per the Croydon trip report, SG23 (Safety and Security) continues work on profiles targeting C++29, now built around three load-bearing papers:
- P3589 (Dos Reis) — the general framework. Specifies what a profile is, the
[[profiles::enforce]]and[[profiles::suppress]]attribute syntax, granularity rules (per-statement, per-block, per-TU), and the rule that opting out of one profile does not accidentally opt out of others. Separates the framework from any specific profile. - P3984 (Stroustrup) — a type-safety profile. Re-casts the type-safety + resource-safety story on top of the P3589 framework. SG23’s primary work item at Croydon. Targets C++29.
- P4158 (Hunt, Apple/WebKit) — experience report. WebKit hardened over 4 million lines of C++ using a subsetting approach — the practical evidence that a profile-shaped restriction works at production scale.
The illustrative shape (subject to revision before C++29 freezes):
// Pseudo-syntax (P3589 + P3984, C++29 target).
[[ profiles::enforce(type, lifetime) ]]
namespace safe_io {
auto read(std::span<const std::byte> in) -> ParsedDoc;
auto write(std::span<std::byte> out, const ParsedDoc&)
-> std::expected<void, error>;
}
// Inside this namespace: raw pointer arithmetic refuses to compile,
// uninitialised reads refuse to compile, lifetimebound violations fire
// hard diagnostics. Per-block opt-out via [[profiles::suppress(...)]].
Layered on top of profiles, C++29 token injection (P3294 Revzin / Alexandrescu / Vandevoorde) lets the same annotation INJECT the safe accessors — not just enforce them. Today the developer writes the safe API by hand and the profile only enforces what the developer chose to write. With injection, the schema becomes the source of truth and the compiler writes the safe implementations:
// Pseudo-syntax (P3294, C++29 target).
[[ profiles::enforce(bounds, type, lifetime), inject(safe_api) ]]
struct SensorReading {
std::uint16_t sensor_id;
std::int32_t raw_value;
std::uint8_t status_flags;
};
// Injected by the compiler:
// static auto parse(std::span<const std::byte>) ->
// std::expected<SensorReading, parse_error>;
// auto serialize() const -> std::array<std::byte, wire_size_v<SensorReading>>;
// bool operator==(SensorReading const&) const = default;
// Each injected function is itself profile-conformant by construction.
Sidebar: cppfront — Sutter’s profile design as a working compiler
For readers who want to see Sutter’s original profile design operating today, cppfront is his experimental Cpp2-syntax-to-Cpp1 compiler. Cpp2 has type / bounds / initialization / lifetime safety by default: use of an uninitialized variable is statically diagnosed, container access is bounds-checked, mixed-sign comparisons and integer divide-by-zero are checked at runtime. The opposite of standard C++‘s “you have to opt IN to safety” — you opt OUT (per scope, with explicit unchecked or per-flag -no-subscript-checks etc.) when a hot path needs it.
It’s not standard C++; not a [[profiles::enforce]] implementation. It’s a syntax-2 testbed Sutter uses to prototype the design intent that later feeds into the standard.
A 25-line Cpp2 demo runs end-to-end on Compiler Explorer in tree mode — a single test.cpp2 paired with a GCC 16.1 executor; godbolt auto-invokes cppfront for the .cpp2 extension before handing off to the C++ compiler. The bounds violation on items[5] fires at runtime, exactly as cppfront’s safety-by-default model promises:
Source: posts/toolset/memory-safety-cpp26-and-beyond/examples/cppfront-safety.cpp2 . cppfront’s command-line options and contracts documentation cover the per-check opt-out flags and the pre<bounds_safety> / post<> syntax for explicit contracts grouped by safety category. If the Croydon trip report’s “C++26 is the first step into a fundamentally new era” framing intrigues you, cppfront is where the next step is being prototyped.
The state of the codebase one decade out
Profiles enforce the safe subset at the language level (C++29). Token injection generates the safe implementations from a schema (C++29). Hardened stdlib catches the runtime corners that compile-time analysis misses (already shipped). Sanitizers stay in CI for legacy paths and for catching the corners reflection hasn’t yet reached (already shipped). The CVE class — “developer wrote a parser by hand and missed a bounds check” — becomes “the developer didn’t put the annotation on the struct”, which is a lint warning, not an exploit.
Cross-references: sanitizers-2026 covers the runtime safety net that profiles aim to complement (not replace); testing-for-safety-2026 covers how to exercise the harden-stdlib + contracts stack via property-based + fuzz tests. The cluster section cpp-coding-standards (incoming MR11) covers MISRA C++:2023 + CERT C++ + Core Guidelines tooling for the regulated end of the spectrum.