C++23
C++23, formally known as ISO/IEC 14882:2024, is the seventh edition of the international standard specifying requirements for implementations of the C++ programming language, a general-purpose language extending the ISO/IEC 9899:2018 C standard with object-oriented and generic programming facilities such as classes, templates, exceptions, namespaces, and operator overloading.[1] Published by the International Organization for Standardization (ISO) in October 2024, it succeeds the C++20 standard (ISO/IEC 14882:2020) and incorporates technical corrections alongside new features designed to enhance code expressiveness, safety, performance, and modularity in areas like constant evaluation and error handling.[2][1]
Core Language Enhancements
C++23 introduces several refinements to the language syntax and semantics to address modern programming needs. Notable additions include support for explicit object member functions (also known as "explicit this"), which allow non-static member functions to explicitly declare their object parameter for improved clarity in generic code and forwarding scenarios.[2] The standard also adds multidimensional subscript operators, enabling operator[] to accept multiple arguments for more intuitive access to multi-dimensional data structures like arrays or matrices.[2] Other key language features encompass new preprocessing directives #elifdef and #elifndef for conditional compilation based on macro definitions, the auto(x) decay-copying declaration for initializing variables with decayed types, and explicit lifetime extension mechanisms to prevent dangling references in temporary expressions.[2] Additionally, C++23 improves Unicode support for better handling of international text and extends constexpr evaluation to more library functions, allowing compile-time computations in a broader range of contexts.[2]
Standard Library Improvements
The C++23 standard library receives significant updates focused on usability and efficiency, including the introduction of standard library modules that export the core library contents (excluding macros) to facilitate faster compilation and better encapsulation in modular codebases.[2] New types such as std::expected provide a standardized way to handle errors and optional results, similar to monadic patterns in other languages, while std::generator supports coroutine-based iteration for lazy evaluation and asynchronous data production.[2] Library enhancements also feature string formatting and printing facilities inspired by std::format from C++20 but expanded for more flexible output, new container and view types like std::mdspan for multi-dimensional arrays, and algorithms optimized for ranges.[2] Further additions include the <spanstream> header for stream-like operations on spans and the std::stacktrace library for capturing and inspecting call stacks at runtime.[3]
These changes deliver targeted advancements in productivity and reliability for systems programming, embedded development, and high-performance applications.[2] Compiler support for C++23 features is progressively rolling out in major implementations like GCC, Clang, and MSVC, with full conformance expected in subsequent releases.[4]
Introduction
Overview
C++23 is the current ISO standard for the C++ programming language, formally designated as ISO/IEC 14882:2024 and published in October 2024.[1] The technical work on the standard was completed in February 2023 by the ISO/IEC JTC1/SC22/WG21 committee, marking the culmination of efforts to evolve the language while maintaining backward compatibility.[5] This edition succeeds C++20 and represents a measured advancement in the language's development.
The primary goals of C++23 include enhancing executability through better integration of existing features, simplifying common programming patterns to improve developer productivity, and bolstering compile-time capabilities for more efficient code generation.[6] It also prioritizes the incorporation of prior Technical Specifications, such as modules and ranges, into the core standard to promote stability and avoid introducing highly experimental elements that could complicate adoption.[7]
In scope, C++23 offers a smaller yet more focused evolution compared to the transformative changes in C++20, with an emphasis on library enhancements for greater productivity and refinements to language precision for safer and more expressive code.[6] Its impact on the C++ ecosystem includes enabling improved support for modern hardware via optimized data structures and multidimensional views, facilitating safer code through refined error handling, and diminishing dependence on traditional headers through mature module support.[6] These advancements collectively streamline development workflows and enhance performance in contemporary applications.
Modern "Hello, World!" Example
A quintessential demonstration of C++23's advancements is the simplified "Hello, World!" program, which leverages the new standard library module and formatted output functions to eliminate traditional header inclusions and stream manipulations.[8]
cpp
import std;
int main() {
std::println("Hello, world!");
}
import std;
int main() {
std::println("Hello, world!");
}
In this example, import std; brings in the entire standard library as a module, replacing the conventional #include directives and providing faster compilation times with better encapsulation by avoiding global namespace pollution from macros.[8] Modules support for the standard library, including std, is detailed in the dedicated section on Modules and Import Support. Meanwhile, std::println from the <print> header outputs the formatted string to stdout followed by a newline, streamlining I/O operations that previously required std::cout, insertion operators, and explicit flushing via std::endl.
To compile this program, a C++23-compliant compiler is required, such as GCC 13 or later invoked with the -std=c++23 flag (e.g., g++ -std=c++23 hello.cpp -o hello), though full module support may vary by implementation and could necessitate additional build system configurations like CMake for prebuilt standard modules. As of 2025, GCC 15 or later offers full support for modules and std::println.[4]
For comparison, the C++20 equivalent relies on headers and streams, resulting in more verbose code:
cpp
#include <iostream>
int main() {
std::cout << "Hello, world!" << std::endl;
}
#include <iostream>
int main() {
std::cout << "Hello, world!" << std::endl;
}
This contrast underscores C++23's emphasis on conciseness and modernity, reducing the program's length while maintaining equivalent functionality.[9]
Standardization Process
Development Timeline
Following the finalization of C++20 at the WG21 meeting in Prague from February 10 to 15, 2020, the committee shifted focus to C++23, adopting a development plan outlined in paper P0592R4 that emphasized completing foundational work on modules—initially introduced in C++20—and enhancing ranges support to improve library usability.[10]
The COVID-19 pandemic disrupted in-person gatherings, leading to the cancellation of the planned June 2020 meeting in Varna, Bulgaria, and a transition to virtual meetings throughout 2020 and 2021.[11] During this period, key proposals advanced significantly, including explicit object parameters (P0849R8) for simplifying member function calls and std::expected (P0323R15) for error-handling utilities, both of which progressed toward adoption in C++23 through reflector discussions and virtual plenaries, such as the November 9, 2020, Zoom meeting.[12]
In 2022, the committee continued with virtual sessions early in the year, including the February virtual meeting, before resuming hybrid in-person formats with the November 7-12 meeting in Kona, Hawaii, hosted by the Standard C++ Foundation.[13][14] This period approached feature freeze, achieved at the July 25 virtual plenary, where core features like coroutines library support (including std::generator) and multidimensional arrays via std::mdspan were finalized for inclusion.[15][16]
The February 6-11, 2023, meeting in Issaquah, Washington, marked the completion of C++23's technical specifications, leading to the final committee draft N4950 dated May 10, 2023.[12][17] Subsequent meetings in Varna, Bulgaria (June 12-17), and Kona, Hawaii (November 6-11), handled defect reports, wording refinements, and ballot preparations, leading to national body ratification.[12][17]
In 2024, the International Organization for Standardization (ISO) published C++23 as ISO/IEC 14882:2024 on October 1, formalizing the standard after editorial review.[1]
Post-publication, major compilers integrated C++23 support progressively; as of November 2025, GCC 15, Clang 19, and Visual Studio 2022 version 17.14 offer substantial to near-complete implementation, enabling widespread adoption.[18][19][20]
Key Milestones and Meetings
The ISO/IEC JTC1/SC22/WG21 working group, known as the C++ standards committee, is responsible for evolving the C++ programming language standard through a collaborative process involving national body representatives and individual experts.[21] This group reviews technical papers submitted by members, conducts discussions in working group sessions during triannual meetings, and uses polls to gauge consensus on proposed changes before forwarding recommendations for inclusion in the working draft.[22]
Key decisions shaping C++23 included several pivotal votes at WG21 meetings. At the February 2023 meeting in Issaquah, Washington, USA, the committee approved the final draft of the C++23 standard by unanimous consent after resolving all national body comments, marking the technical completion of the specification.[23] Earlier, in June 2022 at the St. Louis meeting, WG21 polled in favor of including P2465R3, which introduced standard library modules such as std and std.compat to facilitate modular imports of the C++ library.[24] However, the Contracts Technical Specification was rejected for integration into C++23 due to insufficient consensus stemming from concerns over semantic complexity and implementation challenges.[23]
C++23 incorporated features from several Technical Specifications (TS) into the core standard, promoting stability and adoption. The Library Fundamentals V3 TS contributed components like std::expected for error handling and std::mdspan for multidimensional array views.[25] Enhancements from the Ranges TS, including additional views and algorithms, were fully integrated to extend C++20's ranges foundation.[26] The Coroutines TS provided the basis for std::generator, enabling lazy sequence generation in standard library coroutines.[27] These integrations were approved via polls in LEWG (Library Evolution Working Group) sessions leading up to the feature freeze in July 2022.[16]
Following the February 2023 feature freeze, WG21 addressed post-freeze defects through targeted resolutions. The Core Working Group (CWG) and Library Working Group (LWG) reviewed and fixed ambiguities in ranges, such as iterator invalidation rules, and attributes, including clarification of [[likely]] and [[unlikely]] semantics, via immediate-issue polls at the Issaquah meeting.[28]
WG21's evolution policy employs a decoupled model, where experimental features are first previewed in separate Technical Specifications to allow real-world testing and refinement without delaying the main standard's publication cycle. This approach enabled the progressive maturation of ranges, coroutines, and library fundamentals from TS to full standardization in C++23.
Language Features
Core Syntax Enhancements
C++23 introduces core syntax enhancements that streamline member function definitions, operator overloads, literal expressions, and namespace declarations, thereby increasing code expressiveness while minimizing redundancy in common programming patterns.
A key feature is deducing this (P0847R8), which permits non-static member functions to deduce their return type based on the cv- and ref-qualifiers of the implicit this parameter, eliminating the need for separate overloads to handle lvalue and rvalue contexts. This enables a unified implementation for operations like assignment or chaining, where the return type adjusts automatically—for instance, returning a reference for lvalues and an rvalue reference for rvalues. Consider this example in a class definition:
cpp
struct Widget {
auto operator+=(const auto& rhs) { /* implementation */ return *this; }
};
struct Widget {
auto operator+=(const auto& rhs) { /* implementation */ return *this; }
};
Here, the deduced return type adapts seamlessly, fostering more concise fluent interfaces without sacrificing type safety. The explicit object parameter syntax, integral to the same proposal, further refines this by allowing this to be named as the first explicit parameter in member function signatures, treating the function akin to a free function for improved template forwarding and lambda integration. For example:
cpp
struct Foo {
void print(this Foo& self) { /* use self */ }
};
struct Foo {
void print(this Foo& self) { /* use self */ }
};
This approach enhances generic programming by enabling perfect forwarding of the object parameter and simplifies capturing in lambdas, where explicit object handling aligns with non-member function patterns.
The multidimensional subscript operator (P2128R6) extends operator[] to accept multiple arguments, supporting direct multi-index access for multidimensional data structures without relying on deprecated comma expressions or nested calls. This syntactic upgrade is particularly beneficial for user-defined types representing arrays or views, allowing overloads with fixed or variadic parameters that include the explicit object for consistency. An illustrative declaration is:
cpp
struct Matrix {
double& operator[](this Matrix&& self, size_t row, size_t col);
};
struct Matrix {
double& operator[](this Matrix&& self, size_t row, size_t col);
};
Usage such as matrix[1, 2] = 3.14; becomes idiomatic, promoting readability in numerical and scientific computing while integrating with library features like multidimensional views.[29]
To mitigate common type promotion issues with size-related constants, C++23 adds literal suffixes for std::size_t and its signed counterpart (std::ptrdiff_t or equivalent), as specified in P0330R8. The 'uz' (or 'UZ') suffix denotes an unsigned size_t literal, while 'z' (or 'Z') indicates the signed variant, preventing implicit conversions and associated warnings. For instance:
cpp
std::vector<int> vec(42uz);
std::vector<int> vec(42uz);
This precise typing ensures safer array sizing and indexing operations across platforms, where size_t may differ from built-in unsigned integers.[30]
C++23 also clarifies and reinforces support for nested inline namespaces within enclosing inline namespaces, enhancing library evolution through transitive visibility for versioned APIs. Inline declarations propagate through nesting levels, allowing unqualified access to inner members as if declared in the outer scope, which simplifies maintaining backward-compatible hierarchies without altering lookup rules. This builds on C++11 foundations to facilitate cleaner, scalable namespace organization in large projects.
Compile-Time Improvements
C++23 introduces several enhancements to compile-time evaluation, enabling more sophisticated computations during compilation while maintaining runtime efficiency. These improvements build on prior standards by expanding the scope of constant expressions, allowing developers to perform operations that were previously deferred to runtime. Key advancements include conditional branching based on evaluation context, mechanisms for ensuring immediate evaluation in functions, and extended support for standard library containers in constant expressions.
One significant addition is the if consteval construct, which permits conditional compilation depending on whether a function is invoked in a constant evaluation context. This feature addresses limitations in distinguishing between compile-time and runtime invocations within constexpr and consteval functions, reducing reliance on the std::is_constant_evaluated() function and enabling more flexible code that behaves differently at compile time versus runtime. For example, the following code snippet demonstrates its use:
cpp
consteval int compute(int x) {
if consteval {
return x * 2; // Optimized for [compile-time](/page/Compile_time)
} else {
return x * 3; // Fallback for [runtime](/page/Runtime)
}
}
consteval int compute(int x) {
if consteval {
return x * 2; // Optimized for [compile-time](/page/Compile_time)
} else {
return x * 3; // Fallback for [runtime](/page/Runtime)
}
}
This allows for compile-time-only paths that might involve heavier computations, such as those simulating dynamic allocation, without affecting runtime performance.[31]
To facilitate immediate functions—those required to evaluate at compile time—C++23 introduces parenthesized auto(x) initialization. This syntax creates a prvalue from x as if it were passed by value to a function, ensuring the expression is suitable for constant evaluation without odr-use (one definition rule) violations. It is particularly useful in lambda expressions or function definitions where compile-time guarantees are needed. Consider this example for a compile-time square function:
cpp
constexpr auto square = [](auto(x)) { return x * x; };
static_assert(square(5) == 25);
constexpr auto square = [](auto(x)) { return x * x; };
static_assert(square(5) == 25);
This mechanism simplifies writing immediate functions by avoiding explicit casting or temporary materialization, promoting safer and more intuitive compile-time programming.[32]
C++23 builds on C++20's support for using standard library containers like std::vector and std::string in constant expressions under the transient allocation model, where any simulated dynamic allocation must be deallocated within the same constant evaluation. This enables temporary use of these containers for compile-time computations, such as generating data, but they cannot persist beyond the evaluation (persistent allocation is proposed for future standards). For instance, a constexpr function can use a vector internally to compute a value:
cpp
constexpr int fib(int n) {
std::vector<int> v{0, 1};
for (int i = 2; i < n; ++i) {
v.push_back(v[i-1] + v[i-2]);
}
return v.back();
}
static_assert(fib(6) == 8);
constexpr int fib(int n) {
std::vector<int> v{0, 1};
for (int i = 2; i < n; ++i) {
v.push_back(v[i-1] + v[i-2]);
}
return v.back();
}
static_assert(fib(6) == 8);
This capability supports advanced metaprogramming, such as parsing or generating lookup tables at compile time, while adhering to the transient model to avoid actual runtime allocation. Similar support applies to std::string for compile-time string operations like concatenation.[33]
To enhance portability in internationalized code, C++23 mandates UTF-8 as the portable source file encoding, ensuring compilers accept UTF-8 input by default. This change standardizes handling of Unicode characters in source code, reducing encoding-related portability issues across platforms and compilers. It affects literal encoding and execution character sets, making it easier to write code with non-ASCII identifiers and strings without vendor-specific flags.[34]
Additionally, C++23 allows static operator[] as a member function, enabling array-like subscript access in constant expressions without requiring an object instance or triggering odr-use. This is valuable for stateless classes or constexpr contexts where traditional non-static operators would necessitate unnecessary object creation. For example:
cpp
struct ArrayView {
static constexpr int operator[](size_t i) { return data[i]; }
static constexpr int data[5] = {1, 2, 3, 4, 5};
};
static_assert(ArrayView::operator[](2) == 3);
struct ArrayView {
static constexpr int operator[](size_t i) { return data[i]; }
static constexpr int data[5] = {1, 2, 3, 4, 5};
};
static_assert(ArrayView::operator[](2) == 3);
This feature streamlines compile-time data access patterns, particularly in template metaprogramming, by treating the operator as a static utility without instance overhead.
Attribute and Diagnostic Features
C++23 introduces several attributes and diagnostic enhancements designed to provide programmers with finer control over compiler optimizations and improved error reporting during compilation. These features build on prior standards by standardizing optimization hints and refining how diagnostics are generated and displayed, particularly in template contexts. The [[assume]] attribute, for instance, enables developers to inform the compiler of assumed conditions without runtime evaluation, facilitating aggressive optimizations while introducing undefined behavior if the assumption proves false. Similarly, the [[likely]] and [[unlikely]] attributes, now applicable in broader contexts, offer branch prediction hints to guide code generation for performance-critical paths.[35][36]
The [[assume]] attribute allows a programmer to assert that a given constant expression evaluates to true at a specific point in the code, enabling the compiler to optimize accordingly without verifying the condition at runtime. For example, consider the following code snippet where the assumption optimizes away unnecessary checks:
int process_positive(int x) {
[[assume(x > 0)]];
return x * 2; // Compiler can assume x > 0, potentially simplifying operations
}
int process_positive(int x) {
[[assume(x > 0)]];
return x * 2; // Compiler can assume x > 0, potentially simplifying operations
}
If execution reaches a point where the assumed expression is false, the behavior is undefined, placing the responsibility on the programmer to ensure correctness. This feature standardizes compiler-specific extensions like MSVC's __assume and is particularly useful in performance-sensitive algorithms where domain knowledge guarantees certain invariants.[35]
Branch prediction hints are provided by the [[likely]] and [[unlikely]] attributes, which advise the compiler on the expected frequency of control flow paths to improve instruction scheduling and cache utilization. These attributes can be applied to labels, statements, or if statements, with [[likely]] indicating a path that is anticipated to be taken most often and [[unlikely]] the opposite. An illustrative example is error handling in a function:
void handle_request(bool error) {
if ([[unlikely]](error)) {
// Rare error path: compiler may place this code out-of-line
log_error();
return;
}
// Common success path
process_data();
}
void handle_request(bool error) {
if ([[unlikely]](error)) {
// Rare error path: compiler may place this code out-of-line
log_error();
return;
}
// Common success path
process_data();
}
By hinting at branch probabilities, these attributes help generate more efficient machine code, especially in loops or conditionals with skewed distributions, though their impact depends on the compiler's implementation.
Diagnostics in C++23 see enhancements through clarifications to static_assert, particularly in how messages are reported within template instantiations and dependent contexts. Previously ambiguous behaviors in templates are now well-defined, allowing static_assert declarations to trigger diagnostics more reliably even when the assertion is conditionally evaluated. This includes support for attaching contextual notes to error messages, improving readability for complex template errors. For instance:
template<typename T>
void check_type() {
static_assert(sizeof(T) == 4, "T must be a 32-bit type"); // Now reliably emits in dependent contexts
}
template<typename T>
void check_type() {
static_assert(sizeof(T) == 4, "T must be a 32-bit type"); // Now reliably emits in dependent contexts
}
Such improvements aid library authors in providing informative compile-time feedback without relying on non-standard extensions.
In response to feedback from national bodies during the standardization process, C++23 reverts the deprecation of compound assignment operators on volatile-qualified objects, restoring their original semantics for compatibility with embedded systems and hardware-interfacing code. This decision addresses concerns that the C++20 deprecation would break legacy code in domains like device drivers, where volatile ensures memory operations are not optimized away. The affected operators, such as += and -= on volatile lvalues, now behave as before, performing a read-modify-write sequence atomically with respect to the abstract machine. This reversion maintains support for low-level programming without introducing new undefined behaviors.
Library Features
Modules and Import Support
C++23 advances the modules feature originally introduced in C++20 by standardizing support for the entire standard library as a module, enabling more efficient and encapsulated code organization without relying on traditional header inclusions. This integration resolves lingering issues from the earlier Modules Technical Specification, such as inconsistent compiler behaviors and limited library exposure, providing a production-ready mechanism for modular programming. Modules promote faster build times by compiling interface information once and reusing it, while reducing macro-related side effects and name collisions inherent in header files.
A major addition in C++23 is the named module std, accessible via import std;, which exposes all declarations within namespace std from C++ standard headers (e.g., std::vector from <vector>) and C wrapper headers (e.g., std::fopen from <cstdio>), along with global operators like ::operator new from <new>. This design excludes global namespace equivalents to prevent namespace pollution, offering a clean import path for new code. For legacy compatibility, the std.compat module, imported as import std.compat;, includes everything from std plus the corresponding global namespace functions (e.g., ::fopen), easing the transition for codebases that rely on C-style globals. These modules are defined in the C++23 standard to balance adoption and modernity, with minimal impact on compilation throughput due to modular optimizations.
Module partitions enhance scalability by allowing a single named module to be divided into a primary interface unit and multiple partition interface units, where partitions can be internal (non-exported) for private implementation or interface partitions for shared components. This structure supports better encapsulation in large projects by isolating details within partitions that are not visible outside the module. The global module fragment, delimited by module; before the module declaration, permits inclusions of headers (e.g., system headers like <windows.h>) that remain visible to importers without being exported, facilitating compatibility with non-modular code. Complementing this, the private module fragment—declared with module :private; and restricted to the primary module interface unit—hides internal declarations and definitions from importers, further strengthening encapsulation by preventing unintended exposure of implementation specifics.
Header units in C++23 provide a bridge for existing headers, allowing them to be imported directly as import <header-name>; without full rewriting into module syntax. This treats the header's content as a self-contained module unit, preserving macro definitions and inline functions while benefiting from module caching for repeated uses. C++23 refines header unit compatibility, improving integration with named modules and reducing parsing overhead in mixed header-module environments.
Export mechanisms in C++23 offer refined handling of templates within modules, permitting template declarations and definitions to be exported via the module interface without requiring full instantiation in every importing unit, unlike traditional headers where definitions must often be visible at the point of use. This enables more efficient template distribution, as the module's binary interface includes necessary template information for instantiation on demand, addressing prior limitations in the Modules TS.
Ranges and Algorithms
C++23 introduces several enhancements to the Ranges library, building on the foundation laid in C++20 by standardizing additional views and algorithms from the Ranges Technical Specification (TS) while adding new functionality to improve composability and expressiveness in data processing. These changes emphasize lazy evaluation, better support for heterogeneous data, and efficient reduction operations, enabling more idiomatic and performant code for iterating and transforming sequences without unnecessary copies. The updates follow the prioritization outlined in the C++23 Ranges plan, which categorizes features into tiers based on maturity and utility, ensuring that core views and algorithms are fully integrated into the standard library.[37]
One key addition is the family of fold algorithms, which provide flexible ways to reduce a range to a single value using a binary operation. The primary functions are std::ranges::fold_left and std::ranges::fold_right, which perform left-associative and right-associative folds, respectively, starting from an initial value. For example, std::ranges::fold_left applies the operation cumulatively from left to right, equivalent to the expression init op x1 op x2 op ... op xn, and is particularly useful for operations like summing or concatenating elements where order matters. These algorithms support both forwarding and input ranges, with overloads that deduce the initial value from the first element if not provided, and they reside in <algorithm> to align with general-purpose reductions rather than numeric-specific ones. Additionally, std::ranges::fold_left_with_iter allows specifying a starting iterator for partial folds, enhancing control over the reduction process. This design addresses limitations in prior standards, such as the lack of guaranteed left-to-right evaluation in std::reduce, and promotes safer, more predictable computations in parallel contexts when combined with execution policies in future standards.[38]
The std::views::join view is another significant enhancement, allowing the flattening of a nested range of ranges into a single flat sequence. It takes a view of ranges—such as a vector of vectors—and produces a concatenated view where inner ranges are seamlessly iterated as if they were a continuous sequence, preserving the input range's category (e.g., input or random access). For instance, applying views::join to a range of strings yields a single stream of characters, useful for text processing or aggregating subcollections without materialization. This view requires the outer range to model input_range and each inner range to model input_range as well, with support for prvalue non-views through relaxed constraints to avoid unnecessary temporaries. An extension, std::views::join_with, inserts a delimiter between joined elements, such as commas in CSV-like data, further aiding serialization tasks. These features stem from efforts to make nested data handling more composable, directly addressing common patterns in functional-style programming.[39][40]
Improvements to the zip view in C++23 enhance its utility for combining multiple heterogeneous ranges into tuples, supporting variadic numbers of input views with better type deduction and proxy iterator integration. The std::views::zip adaptor produces a view of tuple-like elements, where each tuple holds the i-th element from each zipped range, stopping at the shortest range's end to prevent out-of-bounds access. Unlike earlier experimental versions, the C++23 implementation uses std::tuple for more than two ranges and std::pair for exactly two, ensuring compatibility with structured bindings and generic code; it also supports heterogeneous types by allowing arbitrary view arguments without common reference type requirements. This enables scenarios like parallel iteration over disparate containers, such as zipping a vector of keys with a map of values for dictionary operations. The design includes optimizations for proxy references in common views like transform, making it efficient for lazy pipelines.[41]
New range-based algorithms expand search capabilities, including std::ranges::find_last and its variants (find_last_if, find_last_if_not), which locate the last occurrence of a value or predicate-satisfying element in a range. These return an iterator to the found position or std::ranges::dangling if none exists, supporting bidirectional and random-access ranges for efficiency. For example, std::ranges::find_last_if can identify the last even number in a sequence, complementing forward-only searches. Similarly, std::ranges::adjacent_find detects the first pair of consecutive elements satisfying a binary predicate, rangified from the legacy version to work with views and return a subrange or iterator pair for easier chaining. These additions fill gaps in backward and pairwise searching, promoting more expressive code without manual reverse iteration.[42]
C++23 fully standardizes several features from the Ranges TS, notably std::views::zip and std::views::enumerate, integrating them as Tier 1 views for immediate usability. The enumerate view pairs each element with its zero-based index in a tuple, facilitating indexed operations like mapping with position awareness, and models the same range category as its underlying view. This standardization ensures these adaptors are lightweight, composable, and free of the TS's experimental constraints, allowing seamless use in standard containers and algorithms.[43][37]
C++23 introduces several enhancements to the standard library's utilities, focusing on improved error handling, formatted output, associative containers, and time zone management. These additions aim to provide more robust, type-safe, and efficient tools for common programming tasks, building on prior standards while addressing longstanding needs in text processing and data organization.
One key addition is std::print and std::println, which offer type-safe formatted output directly to streams like stdout or stderr, avoiding the overhead of intermediate string construction required by std::format. These functions use a printf-like syntax with compile-time checks for format specifiers, ensuring arguments match the placeholders. For instance, the code std::print("Value: {:d}\n", 42); outputs "Value: 42" followed by a newline, while std::println automatically appends a newline. This design supports Unicode output portably and integrates with the <print> header, providing a convenient alternative to std::cout for simple logging and debugging.
To improve error handling, C++23 standardizes std::expected<T, E>, a monadic type that encapsulates either a successful value of type T or an error of type E, akin to optional results in other languages. It supports operations like value_or to retrieve the value with a default on error, and or_else for chaining error recovery, promoting functional-style error propagation without exceptions. Constructors allow in-place construction of the value or error, and it integrates with concepts for generic usage. For example, a function might return std::expected<int, std::error_code>, enabling callers to handle success or failure explicitly. This feature addresses the need for lightweight error handling in performance-critical code.[44]
Format string handling receives refinements through enhanced support in std::format, incorporating concepts for compile-time validation of user-defined formatters and better integration with standard types. Proposals extend formatting for ranges and tuples with customizable debug output. These changes leverage C++20's std::format foundation, adding specializations for library types like std::span and improving locale-aware formatting without altering core syntax.
New associative containers std::flat_map and std::flat_set provide sorted, unique key-based storage using a contiguous underlying vector, offering faster iteration and smaller memory footprints compared to tree-based std::map and std::set for small-to-medium datasets. Elements are kept sorted by key, with insertion maintaining order via binary search, achieving O(log n) lookups and O(n log n) construction. Unlike traditional maps, they support reserving capacity to avoid reallocations, and provide views like keys() for key-only access. An example usage is std::flat_map<std::string, int> m; m.emplace("key", 42);, ideal for cache-friendly scenarios where iteration speed matters more than frequent insertions. Variants std::flat_multimap and std::flat_multiset allow duplicates.
Time zone support in <chrono> is bolstered by std::chrono::zoned_time, which pairs a time_point with a time_zone for unambiguous local time representation, including automatic handling of ambiguities like daylight saving transitions. C++23 clarifies locale-dependent formatting for zoned times. This enables code like auto zt = zoned_time{current_zone(), sys_time{std::chrono::system_clock::now()}}; std::print("{:%F %T %Z}", zt); to output locale-aware dates with zone abbreviations, enhancing cross-platform date-time applications. Brief integration with ranges allows formatting sequences of times, as detailed elsewhere.
Multidimensional Arrays and Views
C++23 introduces std::mdspan, a non-owning multidimensional view that provides a multi-index abstraction over a contiguous sequence of objects in memory, enabling efficient representation of arrays with multiple dimensions without owning the underlying data.[45] This class template is defined in the <mdspan> header and interprets the memory as a domain defined by extents in each dimension, facilitating portable and performant access patterns in multidimensional data structures.[45]
The extents of an mdspan can be specified at compile time using std::extents, such as std::mdspan<int, std::extents<std::size_t, 3, 4>> view(arr);, which creates a view over a contiguous array arr representing a 3-by-4 matrix.[46] Dynamic extents are supported through the std::dynamic_extent sentinel value, allowing runtime specification of sizes; for example, std::mdspan<float, std::dynamic_extent, std::dynamic_extent> dynamic_view(data, rows, cols); constructs a view with dimensions provided at runtime.[46] Extents can mix static and dynamic specifications, such as std::mdspan<float, 20, std::dynamic_extent> mixed_view(data, num_cols);, where the first dimension is fixed at 20 while the second varies.
Layout policies in std::mdspan control the mapping of multidimensional indices to linear memory offsets, with std::layout_right providing row-major order (last index varies fastest, as in C-style arrays) and std::layout_left providing column-major order (first index varies fastest, as in Fortran).[45] These policies are specified as template parameters, e.g., std::mdspan<int, std::extents<std::size_t, 3, 4>, std::layout_right> row_major_view(arr);, ensuring compatibility with diverse hardware and numerical libraries.[45]
Integration with C++23's multidimensional subscript operator allows intuitive access like view[i][j], where the language feature (detailed in Core Syntax Enhancements) enables operator[] to accept multiple arguments, returning nested views for subsequent indexing.[47] This syntax supports zero or more arguments in operator[] overloads, aligning mdspan access with familiar array notations while preserving type safety and efficiency.[47]
In high-performance computing (HPC) and scientific computing, std::mdspan addresses the need for lightweight, non-owning views over large datasets, avoiding the overhead of full array copies and enabling performance portability across accelerators and CPUs without vendor-specific extensions.[46] It facilitates kernel algorithms in domains like linear algebra and simulations by providing a standard interface for multidimensional indexing, reducing boilerplate and improving interoperability with libraries such as those in the Kokkos ecosystem.[46]
Other Library Additions
C++23 introduces the <stacktrace> header as part of the diagnostics library, providing facilities to capture, store, and output call stack information at runtime.[48] The core class std::stacktrace represents a sequence of stack frames, obtained via std::stacktrace::current() to capture the current call stack or constructed from an exception object for error handling.[48] Each frame is described by std::stacktrace_entry, which includes the source file name, function name, and address, enabling detailed debugging output through operator overloading for streams like std::cout.[48]
To support lazy evaluation in ranges using coroutines—building on the coroutine syntax introduced in C++20—std::[generator](/page/Generator) is added to the standard library.[49] This class template generates a sequence of values on demand by suspending and resuming the coroutine returned from a generator function, integrating seamlessly with range-based algorithms via co_yield statements.[49] For example, a generator can produce elements of type T or optionally std::optional<T> to signal completion, avoiding the need for custom coroutine types in common iterable scenarios.[50]
The <bit> header gains std::byteswap, a constexpr function that reverses the byte order of an integral value, aiding endianness conversions in network protocols and serialization without platform-specific code.[51] It participates in overload resolution only for integral types and produces the same type as its argument, ensuring type safety and compile-time evaluation where possible.[51]
Complementing this, std::ranges::views::as_rvalue (also known as std::views::as_rvalue) provides a range view adaptor that treats each element as an rvalue reference, equivalent to wrapping the range's iterators in std::move_iterator, to facilitate moving from containers in algorithms without explicit wrapping.[52]
For C compatibility and low-level string conversion, std::to_chars for integral types is made constexpr in C++23, enabling allocation-free, compile-time conversion of integers to character buffers in a specified base (decimal, hexadecimal, or octal).[53] This update, per proposal P2291R3, ensures round-trip guarantees and locale independence, useful for embedded systems or performance-critical parsing without dynamic memory use.[54]
Deprecations and Removals
Removed Language Features
In C++23, the ISO/IEC 14882 standard removed support for garbage collection interfaces that had been part of the language since C++11, as these features were never implemented in major standard library implementations and provided limited practical value. Specifically, the functions std::declare_reachable, std::undeclare_reachable, std::declare_no_pointers, std::undeclare_no_pointers, and std::get_pointer_safety from the <gc> header were eliminated, along with the std::pointer_safety enumeration and the __STDCPP_STRICT_POINTER_SAFETY__ macro. Additionally, core language wording related to strict pointer safety, which aimed to support conservative garbage collectors by guaranteeing pointer validity, was excised. This removal stemmed from the absence of any known compiler or library support for strict pointer safety over the past decade, with all major implementations defaulting to relaxed mode, rendering the feature obsolete and unhelpful for real-world garbage collection in virtual machines or custom allocators.[55]
Another language cleanup in C++23 involved the elimination of non-encodable wide character literals and multicharacter wide character literals, which were previously allowed but led to inconsistent behavior across implementations due to varying execution character set assumptions. Under the new rules, attempts to form such literals, like L'non-encodable-char' where the character cannot be represented in the execution wide character set, or L'abc' with multiple characters, now result in ill-formed code, requiring a diagnostic from the compiler. This change simplifies the grammar and reduces portability issues without impacting common use cases, as narrow character literals or user-defined literals serve equivalent purposes.[56]
C++23 also removed support for concatenating mixed wide string literals, such as u"q" U"p", which previously allowed inconsistent combinations of encoding prefixes (e.g., u8, u, U, L). These are now ill-formed, promoting consistent encoding practices and aligning with enhanced Unicode support in the standard. This cleanup addresses ambiguities in literal formation without affecting typical string usage.[57]
These removals contribute to streamlining the C++ core language by excising unused or contradictory provisions accumulated from prior standards, particularly those in Annex D (compatibility) that no longer align with modern hardware and implementation realities. For instance, certain legacy rules conflicting with cache-coherent multiprocessor systems were pruned to focus the specification on practical, widely-adopted behaviors, thereby reducing complexity and specification bloat without breaking existing codebases. The overall rationale emphasizes enhancing maintainability and clarity for developers and implementers, prioritizing features with demonstrated utility over vestigial elements.[58]
Deprecated Library Components
In C++23, the standard library deprecates several legacy components to promote safer, more efficient alternatives, continuing a process started in previous standards. The <codecvt> header, introduced in C++11 to support conversions between different character encodings via facets like std::codecvt_utf8, remains deprecated as established in C++17. This deprecation stems from the header's underspecification, absence of robust error handling, and potential security vulnerabilities in conversion operations. Although not removed in C++23, it is slated for elimination in C++26, with implementers advised to maintain backward compatibility in the interim. Developers are encouraged to migrate to third-party libraries such as ICU for comprehensive Unicode handling or leverage emerging standard facilities like std::text_encoding for basic encoding needs.[59]
C++23 newly deprecates std::aligned_storage and std::aligned_union from the <type_traits> header, which provided types for aligned storage allocation but are redundant with the more flexible std::align algorithm and modern alignment specifications introduced in C++11. These deprecations encourage use of aligned allocation functions and structured bindings for similar purposes, reducing reliance on utility classes that overlap with core language features.[60]
Additionally, the member std::numeric_limits::has_denorm is deprecated in C++23, as its behavior was underspecified for certain floating-point models and rarely used in practice. Developers should instead query denormalization support via std::numeric_limits::tininess_before or implementation-specific checks, aligning with updates to floating-point semantics in the standard.[61]
For migration from these deprecated components, C++23 emphasizes modern utilities like std::format for string and I/O operations that once relied on legacy encoding or shuffling patterns, offering type-safe and locale-aware formatting without the pitfalls of older facilities. Comprehensive tooling support in compilers flags these deprecations, facilitating gradual upgrades in large codebases.[58]
Major Compiler Implementations
GCC provides robust support for C++23, with core language features largely implemented starting in GCC 14, released in May 2024. Substantial completion, including advanced modules and multidimensional array support via std::mdspan, arrived in GCC 15.1 (April 2025), with further enhancements in GCC 15.2 (August 2025).[62] This version enhances experimental modules with built-in std and std.compat modules, alongside library additions like std::flat_map and std::flat_set.[63]
Clang, part of the LLVM project, began progressive implementation of C++23 features from Clang 13 in 2021, reaching substantial coverage by Clang 16 in 2023.[19] Full support, enabling the -std=c++23 flag for comprehensive compliance, was achieved in Clang 18, released in September 2024. This includes modules import syntax and standard library integrations, with ongoing refinements in subsequent point releases.[19]
Microsoft Visual C++ (MSVC) introduced C++23 support in Visual Studio 2022 version 17.10, released in May 2024, via the /std:c++23 compiler flag.[20] By mid-2025, in version 17.14 (May 2025), key features such as modules and the <print> header with std::print and std::println became fully available, along with support for new lambda attributes, if consteval, and static operators, aligning with ISO conformance goals.[64]
Other compilers exhibit varying degrees of adoption. Intel oneAPI DPC++/C++ Compiler version 2025.0, leveraging the Clang 19 frontend, provides full C++23 support across host and device code.[65] NVIDIA HPC SDK compilers, formerly PGI, offer complete C++23 compatibility in release 25.9 (September 2025), including GPU-accelerated standard library elements.[66] EDG-based frontends, used in tools like MSVC's IntelliSense, deliver partial C++23 implementation, covering core language but lagging on modules.[67]
To invoke C++23 mode, compilers typically require explicit flags: g++ -std=c++23 for GCC, clang++ -std=c++23 for Clang, and cl /std:c++23 for MSVC. These enable the standard's features while maintaining backward compatibility with prior modes.[20]
As of November 2025, major C++ compilers have achieved near-complete conformance to the C++23 core language features, with GCC 15.2, Clang 21, and MSVC in Visual Studio 2022 version 17.14 supporting approximately 95-100% of the standard's language requirements, though minor lags persist in niche areas such as nested inline namespaces and certain constexpr extensions.[19][20] For instance, Clang 21 provides full implementation of core features like explicit object parameters (P0847R7) and if consteval (P1938R3), while MSVC 17.14 has completed support for declaration order layout (P1847R4) and removed garbage collection provisions (P2186R2).[19][20] These advancements stem from ongoing efforts by the LLVM, GCC, and Microsoft teams to align with the ISO/IEC 14882:2024 standard finalized in 2023.
Library conformance shows more variability, with the std module reaching about 95% implementation across vendors—fully supported in Clang's libc++ 21 for import std usage, enabling modular standard library access without traditional includes.[68][69] The multidimensional array view std::mdspan is complete in all major libraries, including libstdc++ (GCC 15.2), libc++ (Clang 21), and MSVC STL, facilitating efficient multidimensional data handling in scientific computing.[70][68] In contrast, the stacktrace library support varies by platform and implementation; it is fully available on Linux with GCC and Clang but remains partial or platform-dependent on Windows with MSVC due to debugging API integrations.[71]
Known gaps include partial support for coroutine generators like std::generator in versions prior to MSVC 17.14 updates, where full resumption and yield mechanics are now stable, though core coroutines (P0912R5) are implemented since VS 2019.[20] UTF-8 source file encoding (P2295R6), mandating portable UTF-8 support, is fully implemented across all major compilers, ensuring consistent Unicode handling in source code.[72] Conformance is verified through ISO WG21 test suites and community-maintained resources like cppreference's compiler support tables, which track feature adoption via vendor reports and automated testing.[73]
Looking ahead, full C++23 conformance is anticipated across all major implementations by mid-2026, coinciding with early previews of C++26 features, as vendors prioritize ABI stability and library completeness in upcoming releases like GCC 16 and Clang 22.[74][75]