Fact-checked by Grok 2 weeks ago

C++20

C++20 is the informal name for the sixth edition of the ISO/IEC 14882 standard defining the , published by the (ISO) in December 2020. This revision supersedes the previous standard and represents a major update to the language, incorporating enhancements developed by the ISO/IEC JTC1/SC22/WG21 committee over several years. The standard spans approximately 1,853 pages and specifies requirements for C++ implementations, building upon the language (ISO/IEC 9899:2018) while adding advanced features for data abstraction, , , and concurrency. Among the most prominent additions in C++20 are concepts, which allow developers to constrain parameters with compile-time checks for improved error messages and code clarity; modules, enabling a more efficient alternative to header files for organizing and compiling code; coroutines, facilitating asynchronous and generator-style programming without external libraries; and the ranges library, providing composable algorithms for processing sequences in a functional manner. These core features address longstanding limitations in , build systems, and data manipulation, making C++ more expressive and performant for modern applications such as systems software, games, and high-performance computing. Further notable language improvements include the operator (<=>), which simplifies defining ordering for custom types; designated initializers for aggregates, akin to those ; and enhancements to expressions, such as templated parameters and explicit capture lists. On the library side, C++20 introduces std::span for non-owning views of contiguous data, std::format for type-safe string formatting, and the <chrono> extensions for calendars and time zones, enhancing portability across locales. Additionally, the standard deprecates or removes several outdated facilities from prior versions, promoting cleaner code while maintaining through gradual adoption options in compilers like , , and MSVC. Overall, C++20 advances the language toward greater safety, productivity, and alignment with contemporary programming paradigms.

Introduction

Standardization and Publication

The C++20 standard was developed and finalized by the ISO/IEC JTC1/SC22/WG21 committee, the working group tasked with maintaining and evolving the C++ programming language under the auspices of the (ISO) and the (IEC). The committee's technical work on C++20 culminated at its in-person meeting in , , from February 10 to 15, 2020, where delegates voted to advance a committee draft to the Draft International Standard (DIS) stage after resolving outstanding issues. In response to the , WG21 shifted to meetings for the remainder of 2020, including subgroup teleconferences and a plenary in June 2020 to conduct final editorial reviews, incorporate feedback, and prepare the DIS document N4860 dated , 2020. This DIS was submitted to ISO national member bodies for formal balloting, a key phase where representatives from participating countries review and vote on the draft. The ballot concluded successfully on September 4, 2020, achieving unanimous approval with no national body objections, thereby ratifying the content as the forthcoming . Following minor post-ballot editorial corrections by the committee, ISO published the finalized document as ISO/IEC 14882:2020 on December 15, 2020, marking the official release of the sixth edition of the . The published standard closely mirrors the ratified DIS (N4860), serving as the authoritative reference for C++20 implementations worldwide, with initial adoption phases commencing as vendors integrated conforming support into their toolchains post-publication.

Goals and Motivations

C++20 aimed to evolve the language by enhancing its expressiveness, performance, and usability, particularly in the domains of and concurrency, while preserving the core principles that have defined C++ since its inception. Building on the foundations laid by and refined in subsequent standards, the primary objectives included providing more intuitive abstractions for complex programming tasks without compromising efficiency. This involved addressing longstanding challenges such as the verbosity and error-proneness of template-based generic code, enabling better compile-time reasoning, and facilitating asynchronous operations natively within the language. These goals were driven by the need to keep C++ relevant in a landscape dominated by , systems, and large-scale , where developers require tools that balance power with simplicity. A key motivation was to rectify limitations inherited from , notably the issues of header pollution in template-heavy code and the absence of robust compile-time constraints, which often led to cumbersome and poor diagnostics. C++20 sought to introduce mechanisms for modular code organization to streamline compilation processes—potentially reducing build times by up to an —and to enforce constraints at , thereby improving code reliability and developer productivity. This response to prior standards' shortcomings emphasized "zero-overhead" abstractions, ensuring that high-level features impose no runtime penalties when not needed, aligning with C++'s philosophy of direct hardware control and efficiency. Industry demands further shaped these objectives, particularly the push for safer concurrency models to mitigate common pitfalls in multithreaded applications and the integration of asynchronous programming capabilities without reliance on external libraries. By prioritizing resource safety, , and compatibility with existing codebases, C++20 addressed the needs of sectors like , , and scientific computing, where and are paramount. was underscored as a deliberate feature, allowing gradual adoption while supporting diverse hardware architectures. These motivations reflect a pragmatic , informed by decades of deliberations and real-world , to sustain C++'s role as a versatile, high-performance language.

Core Language Changes

Concepts

Concepts in C++20 provide a mechanism for constraining template parameters by defining named sets of requirements on types, enabling more precise control over code. A is declared as a that evaluates to a constant expression based on its constraints, such as template<typename T> [concept](/page/Concept) Integral = std::is_integral_v<T>;. This allows developers to replace complex Substitution Failure Is Not An Error (SFINAE) techniques with explicit requires clauses, resulting in clearer and more maintainable definitions. For instance, a can be constrained directly as template<typename T> requires Integral<T> void process(T value) { /* ... */ }. Key syntax includes requires expressions, which test syntactic and semantic properties like requires (T x) { x + 1; }, ensuring operations are valid without instantiating the full . Concepts offer several benefits, including improved compiler error messages that pinpoint constraint violations rather than deep template instantiation failures, selective template instantiation only when constraints are satisfied, and seamless integration with auto in abbreviated function templates. For example, an abbreviated template can be written as template<Integral T> T add(T a, T b) { return a + b; }, where the concept infers the return type and applies constraints automatically. The C++20 standard library includes predefined concepts like std::integral, which is satisfied by integral types such as int or bool, and std::regular, which requires a type to be default constructible, copyable, and equality comparable. These simplify algorithm usage; for instance, std::sort can leverage a std::sortable concept to ensure iterator arguments support the necessary operations like < and swap, avoiding compilation errors for incompatible types.

Modules

Modules in C++20 introduce a fundamental shift in code organization, serving as a modern alternative to traditional header files by enabling better encapsulation and faster builds. A module begins with a declaration in the form export module ModuleName;, which defines the module's name and signals that it exports an interface. This declaration must appear before any other code in the translation unit, and the module name follows specific naming rules, such as using dot-separated identifiers for hierarchy (e.g., MyProject.Utils). The C++20 standard specifies that modules compile to binary module interface (BMI) files, which importers use without reparsing source code. C++20 distinguishes between interface units and implementation units to separate public and private code. An interface unit starts with export module ModuleName;, followed by export declarations for entities intended for external use, such as export class Widget { /* ... */ }; or export void process(int);. In contrast, an implementation unit begins with module ModuleName;, containing non-exported definitions like function bodies or private members, which link against the corresponding interface. To consume a module, a translation unit uses an import statement like import ModuleName;, which brings in all exported declarations into the current scope without the risks associated with macro expansion in headers. For integration, imports such as import std; or import <iostream>; (treating headers as header units) are supported, though full standard library modularization was partial in C++20. For scalability in large projects, C++20 supports module partitioning and a module fragment. Partitions allow splitting a into submodules, declared as export module ModuleName.PartitionName;, which can the primary and other partitions using import :PartitionName;. The module fragment, delimited by module; before the module declaration, provides a space for that applies globally, such as directives or imports that affect the entire program. This structure facilitates incremental builds in complex codebases. One of the primary advantages of modules is significantly reduced times, as the processes each only once and caches the , avoiding the repetitive parsing of included headers that can scale poorly in large projects. Modules also enhance encapsulation by hiding non-exported names, preventing accidental exposure of internal details unlike headers where everything is visible. Additionally, they mitigate pollution, as defined outside a do not automatically propagate into it, reducing conflicts and improving reliability. Despite these benefits, C++20 modules have limitations, notably the inability to export template specializations directly from modules; explicit or partial specializations must remain in headers or be handled via importable headers, with fuller support deferred to and later standards. While can constrain templates within modular interfaces, their integration primarily aids rather than module structure itself.

Coroutines

C++20 introduces native support for coroutines, enabling stackless for asynchronous programming and generator functions without relying on threads or external libraries. Coroutines allow functions to suspend execution at specific points and resume later, preserving local state across suspensions, which facilitates efficient handling of I/O-bound operations and iterable sequences. This feature is opt-in, requiring the inclusion of the <coroutine> header and the use of co_await on awaitable objects that define readiness, suspension, and resumption behaviors. The core keywords for defining coroutines are co_await, co_yield, and co_return, each triggering suspension points with distinct semantics. The co_await keyword suspends the until the awaited expression completes, resuming with its result; it evaluates an operand that must be awaitable, suspending via a call to the awaitable's await_suspend method if not ready. The co_yield keyword produces a value to the caller and suspends the , allowing resumption on the next invocation, commonly used in patterns. Finally, co_return terminates the , optionally providing a return value that is forwarded to the type for handling, after which the coroutine enters a final suspension state unless specified otherwise. Coroutine behavior is highly customizable through a user-defined promise_type, which the compiler instantiates to manage the coroutine's state, suspension, and return object. The promise_type must provide methods such as get_return_object(), which returns the coroutine's result object (often a handle or future-like type); initial_suspend(), which returns an awaiter controlling whether the coroutine suspends immediately upon entry (typically std::suspend_always or std::suspend_never); and final_suspend(), which determines post-termination suspension and must not throw exceptions. Additional required methods include return_value(T) and return_void() for handling co_return outcomes, yield_value(T) for co_yield values, and unhandled_exception() for propagating errors. These methods allow tailoring coroutines to specific needs, such as integrating with execution contexts or allocators. For manual management, C++20 provides std::coroutine_handle<Promise>, a non-owning to a coroutine's , enabling explicit resumption, destruction, and state queries outside the automatic mechanism. Key operations include resume(), which continues execution from the point (undefined if the coroutine is not suspended or has ); destroy(), which deallocates the ; and done(), which checks status. Handles can be obtained via from_promise or from_address, supporting scenarios like pooling or cross-thread resumption, though the latter has implementation-defined behavior regarding . The <coroutine> header also includes std::coroutine_traits for deducing promise types and std::noop_coroutine_handle for a handle. Coroutines excel in use cases like implementing generators for and asynchronous tasks for non-blocking operations. For generators, co_yield enables iterable sequences, such as a Fibonacci generator where values are produced on demand without full computation upfront. For async tasks, co_await integrates with awaitables like futures, allowing I/O or network operations without blocking the calling thread, thus improving scalability in event-driven systems. Such patterns reduce context-switching costs compared to traditional threading models.

Comparison Operators

C++20 introduces significant enhancements to comparison operators, primarily through the three-way comparison operator, known as the spaceship operator <=>, which enables more concise and consistent definitions of relational and operations for user-defined types. This operator performs a between two operands, returning one of several comparison category types defined in the <compare> header, such as std::strong_ordering for total orders (where is substitutable), std::weak_ordering for orders allowing equivalent but non-substitutable elements (e.g., floating-point comparisons), or std::partial_ordering for partial orders without total comparability. The spaceship operator can be explicitly defined for a or defaulted using = default, as in auto operator<=>(const Other&) = default;, where Other may differ from the class type to support heterogeneous comparisons. When defaulted, the synthesizes a memberwise or lexicographical comparison of the object's (bases and data members) in declaration order, recursing into arrays and stopping at the first non-equal result; the return type is deduced as std::common_comparison_category_t of the categories, defaulting to std::strong_ordering if all subobjects support it. This defaulted form requires that all subobjects are comparable with the corresponding subobjects of the other and that the class has no virtual bases or user-provided constructors that interfere. To reduce boilerplate, C++20 provides rewriting rules for the traditional two-way comparison operators (==, !=, <, >, <=, >=). When these are not user-defined or deleted, the compiler implicitly rewrites them in terms of <=>: for example, a == b becomes (a <=> b) == 0, a < b becomes (a <=> b) < 0, and so on, provided a suitable <=> candidate exists and the return type supports the necessary conversions. These rewritings apply even across different types if conversions allow, enabling automatic generation of all six relational operators from a single <=> definition. If the spaceship returns std::strong_ordering or std::partial_ordering, all operators can be synthesized; for std::weak_ordering, only <=> and == are fully supported, with relational operators potentially deleted. Equality comparisons also see improvements, particularly for standard library types like arrays and tuples. For std::array<T, N>, the equality operator == (present since ) now integrates with the new <=> for three-way comparisons, using std::lexicographical_compare_three_way on elements and returning std::strong_equality::equivalent if all match; relational operators are similarly enhanced. Likewise, std::tuple elements are compared lexicographically via <=>, with == returning std::strong_equality::equivalent for matching tuples, with relational operators defined in terms of the . These changes ensure consistent, short-circuiting behavior using std::strong_equality for bitwise or value-based equivalence where applicable. Overall, these features simplify the implementation of comparison operators for user-defined types by allowing a single <=> definition to generate the full set via , while supporting heterogeneous comparisons between compatible types without explicit overloads. This reduces code duplication, improves , and aligns with modern practices for , such as in the Ranges library where three-way comparisons facilitate and .

Designated Initializers

Designated initializers provide a syntax for explicitly initializing specific members of types by name, improving code readability and reducing errors from positional initialization. This feature, adopted in C++20, allows developers to specify values for named members in any order within the declaration sequence while ensuring and adherence to aggregate initialization rules. The syntax uses a dot followed by the member identifier and an equals sign, enclosed in braces for aggregate initialization. For example:
cpp
struct Point {
    int x;
    int y;
};
Point p{.x = 1, .y = 2};
This initializes p.x to 1 and p.y to 2, regardless of the order in the list, but the designators must appear in the declaration order of the members. Trailing commas are permitted, as with standard initializer lists. Partial initialization is supported; unspecified members receive value-initialization (typically zero-initialization for built-in types). Designated initializers apply exclusively to classes, including non-union structs and unions that lack user-provided constructors, or protected non-static data members, functions, or //protected base classes. They do not extend to non- types or plain arrays, where initialization remains positional. For unions, only a single designator is allowed, enabling initialization of any member rather than just the first. Rules prohibit duplicate designators, which result in a compile-time error, and require the designated members to form a of the class's non-static data members in declaration order—no skips or out-of-order specifications are permitted. Mixing is allowed only by following a designated-initializer-list with a and an additional initializer-list for the remaining members after the designated . This feature extends designated initializers to C++ aggregates but imposes stricter constraints for consistency and safety: designators must follow declaration order without omissions in the prefix, mixing is limited to trailing positional initializers, and nested or array-index designators are disallowed, unlike in . These limitations enhance predictability in member destruction order and side-effect sequencing, aligning with C++'s initialization semantics. C++ adds compile-time checks for narrowing conversions and type mismatches, providing greater robustness than C's more permissive model. Common use cases include initializing complex structs for configuration options or parameters without relying on default constructors or verbose setters, such as registers or request objects. For instance:
cpp
struct Config {
    int timeout;
    bool enabled;
    std::string name;
};
Config cfg{.timeout = 30, .enabled = true, .name = "default"};
This approach facilitates partial updates and , particularly beneficial in systems or performance-critical applications where aggregates avoid constructor overhead.

Other Language Features

C++20 introduced several enhancements to expressions, improving their flexibility and usability in . One key improvement allows capturing the enclosing class's *this by value, enabling the creation of independent copies of the object within the without modifying the original; this is specified using [=*this] in the capture list. Additionally, can now use explicit lists, such as []<typename T>(T x) { return x; }, which facilitates more precise control over type deduction in bodies. Stateless , those without captures, can now be implicitly converted to pointers, allowing them to be passed to C-style expecting pointers, as in int (*fp)() = [] { return 42; };. Pack expansions in captures further enable variadic handling, for example [...xs = std::move(xs)] to capture a pack into the . Constexpr functionality saw significant expansions in C++20, broadening the scope of compile-time computation. Virtual functions are now permitted in constexpr contexts, provided the call is made through a pointer or reference to a class type without virtual bases, allowing polymorphic behavior at compile time. Dynamic allocation using new and delete is supported within constexpr functions, enabling heap-like operations during constant evaluation, though the allocated memory must be deallocated within the same evaluation. Constexpr destructors are also introduced, requiring that all subobject destructors are constexpr and the class has no virtual base classes, thus permitting complete object lifetime management in constant expressions. Template syntax improvements in C++20 include abbreviated function templates, which use placeholder types like auto in parameter declarations to implicitly generate template specializations. For instance, void f(auto x) { } is equivalent to template<typename T> void f(T x) { }, simplifying the declaration of generic functions without explicit template headers. This syntax supports concepts as well, such as void g(Integral auto y) { }, which constrains the parameter to integral types. Pack expansions, already covered in lambdas, extend to these abbreviated forms for variadic templates. New attributes in C++20 provide hints for optimization and diagnostics. The [[likely]] and [[unlikely]] attributes allow developers to annotate statements or conditions to guide the compiler's branch prediction, for example if ([[unlikely]] (rare_condition)) { ... }, potentially improving runtime performance by favoring the expected code path. Refinements to existing attributes include extensions for [[nodiscard]] and [[maybe_unused]], which now apply more broadly to suppress warnings in contexts, such as unused structured bindings or discarded values in instantiations. The using enum declaration, introduced in C++20, simplifies access to enum values by bringing enumerators into the current scope without qualification. For example, enum class Color { Red, Green }; using enum Color; allows direct use of Red instead of Color::Red within the scope, reducing verbosity while avoiding name conflicts through qualified lookup. This feature is particularly useful in or scopes for cleaner code integration.

Standard Library Updates

Ranges Library

The Ranges library in C++20 provides a composable and expressive framework for working with sequences of elements, generalizing and extending the iterator-based algorithms from prior standards. It introduces the concept of a as any type that supports begin() and end() operations to access a , formalized in std::ranges. A key refinement is the view, defined as a range that is lightweight, non-owning, and supports constant-time copying and destruction, enabling without data duplication. Views act as composable that reference underlying data indirectly, allowing operations to be chained without immediate execution or allocation of intermediates. The library also defines refinements like , a range whose iterator category models std::input_or_output_iterator, ensuring compatibility with forward iteration semantics. Central to the library's usability is the pipe operator |, which facilitates fluent of views and adaptors, improving over traditional calls or manipulations. For instance, generating even squares from 1 to 9 can be expressed as std::views::iota(1, 10) | std::views::filter([](int i){ return i % 2 == 0; }) | std::views::transform([](int i){ return i * i; }), where [iota](/page/Iota) creates an , [filter](/page/Filter) selects even values lazily, and transform applies squaring only when iterated. This syntax leverages view factories like std::views::iota for generating arithmetic progressions, including potentially infinite ones such as std::views::iota(0) for non-negative integers. Algorithms in std::ranges, such as std::ranges::sort(rng) for sorting a mutable in place or std::ranges::for_each(rng, [](auto&& elem){ /* process */ }) for applying a to each element, accept ranges directly and integrate seamlessly with piped expressions, reducing the boilerplate of pairs. Range adaptors further enhance by transforming views lazily, deferring computation until consumption. Examples include std::views::take(n) to limit a to the first n elements, std::views::drop(n) to skip the first n elements, std::views::reverse to iterate in reverse order, and std::views::join to flatten a of ranges into a single sequence. These adaptors, applied via pipes like rng | std::views::take(5) | std::views::reverse, ensure operations remain non-owning and efficient, as they do not materialize new containers. The design draws from the Ranges TS and emphasizes these properties to support advanced patterns without performance penalties. By avoiding the creation of temporary containers for intermediate results, the Ranges library minimizes allocations and copies, particularly beneficial for large or streamed data. It enables infinite ranges through generators like iota, which would be impractical with owning containers, and offers greater expressiveness than raw iterators by abstracting away begin-end pairs and enabling higher-level compositions. This approach unifies algorithm interfaces, making them more intuitive and less error-prone while preserving the efficiency of the pre-C++20 standard library.

Chrono Library Enhancements

C++20 significantly extends the <chrono> library, originally introduced in C++11 for representing durations, time points, and clocks, by incorporating support for calendars, s, and enhanced duration handling. These additions enable precise manipulation of dates and times in a type-safe manner, addressing longstanding limitations in handling civil calendars and localized time adjustments without relying on external libraries. The enhancements stem from the integration of calendar and time zone facilities proposed in P0355R5, providing a robust for applications requiring temporal computations, such as scheduling, , and . Central to the calendar enhancements are new types for representing date components: std::chrono::year, std::chrono::month, std::chrono::day, and std::chrono::weekday. The year type models years in the range from -32767 to 32767, supporting arithmetic operations and leap year detection via is_leap(). Months are enumerated from 1 () to 12 (), with predefined constants like January and December, and support addition/subtraction using std::chrono::months. Days range from 1 to 31, compatible with std::chrono::days for duration arithmetic. Weekdays are indexed 0 () to 6 (), with circular arithmetic to handle week boundaries. These primitives combine into higher-level types such as year_month_day, which represents a full civil and can be constructed using the / , for example, sys_days{2020y / December / 25} to denote December 25, 2020, as a system-time day point since the Unix . This notation facilitates intuitive date literals while ensuring validation for invalid dates, like February 30, which results in an uninitialized state. Time zone support introduces std::chrono::time_zone and std::chrono::zoned_time, leveraging the IANA Time Zone Database for accurate handling of offsets, abbreviations, and transitions like daylight saving time (DST). The locate_zone function retrieves a time_zone by name, such as locate_zone("America/New_York"), returning a pointer to the corresponding zone or nullptr if not found. A zoned_time pairs a time point with a time zone, automatically adjusting for local rules; for instance, it can convert a UTC sys_time to local wall time, accounting for DST ambiguities or non-existent local times by selecting the UTC-equivalent local time. This avoids common pitfalls in pre-C++20 code, where manual offset calculations often led to errors during transitions. Additional clocks like utc_clock (including leap seconds) and local_t (wall-clock time) integrate seamlessly with these types. Durations in <chrono> now explicitly include calendar-aware units beyond the original seconds-based ratios, with typedefs for std::chrono::days, std::chrono::months, and std::chrono::years, enabling arithmetic on date-like quantities while respecting calendar irregularities like varying month lengths. The templated duration<Rep, Period> supports a vast range via std::ratio, from attoseconds (ratio<1, 1000000000000000000>) to yottaseconds (ratio<1000000000000000000000000, 1>), accommodating both integral and floating-point representations for Rep to handle sub-second precisions or approximate computations. For example, duration<double, atto> allows floating-point attosecond durations, though practical limits depend on Rep's precision. These extensions promote safer temporal differences, such as computing the exact number of months between two dates without overflow risks in edge cases. Formatting capabilities for chrono types are provided through std::to_stream and integration with the new <format> library, supporting ISO 8601-compliant strings and traditional strftime-like specifiers. For durations and time points, std::format uses specialized formatters; a sys_time can be formatted as "{:%Y-%m-%d %H:%M:%S %z}" to yield output like "2020-12-25 14:30:00 -0500" for a zoned time in America/New_York, including offset %z and abbreviation %Z. Parsing via std::from_stream mirrors this, enabling bidirectional conversion from strings to chrono objects with locale-aware options. This unifies output across streams and strings, reducing reliance on platform-specific APIs. Integration of these features emphasizes sys_time for UTC-based system time points (excluding leap seconds for consistency) and local_time for ambiguous wall-clock representations, which zoned_time resolves using the associated . Conversions between sys_time, local_time, and zoned_time handle DST folds and gaps automatically, ensuring that operations like addition of durations preserve semantics—for instance, adding 24 hours to a local time during a DST spring-forward yields the correct post-transition time without manual intervention. This design mitigates errors in cross-time-zone computations, making <chrono> suitable for global applications.

Atomic Operations

C++20 introduces several enhancements to the atomic operations library in <atomic>, improving support for lock-free concurrency and . These changes build on the memory model by providing more efficient primitives for access, reducing reliance on traditional mutex-based in certain scenarios. Key additions include atomic specializations for pointers, wait/notify mechanisms on atomic variables, refined bit-level operations, hardware-aware sizing constants, and clarifications to semantics. One significant addition is the support for atomic smart pointers, specifically std::atomic<std::shared_ptr<T>> and std::atomic<std::weak_ptr<T>>, which allow lock-free manipulation of reference-counted pointers in multithreaded environments. For std::atomic<std::shared_ptr<T>>, operations such as load, store, exchange, and compare-exchange functions (compare_exchange_strong and compare_exchange_weak) ensure that increments to the shared pointer's reference count are atomic, while decrements are sequenced after the operation. This enables safe, atomic updates without external locking, provided the implementation is lock-free via is_lock_free(). Similarly, std::atomic<std::weak_ptr<T>> provides the same compare-exchange operations, treating weak references atomically but without affecting the strong reference count directly; deallocation remains non-atomic but sequenced appropriately. These features are particularly useful for concurrent data structures like thread-safe caches or producer-consumer queues involving shared ownership. The following example demonstrates atomic exchange of a shared_ptr using compare-exchange:
cpp
#include <atomic>
#include <memory>
#include <iostream>

std::atomic<std::shared_ptr<int>> ptr{nullptr};

int main() {
    auto desired = std::make_shared<int>(42);
    std::shared_ptr<int> expected = nullptr;
    if (ptr.compare_exchange_strong(expected, desired)) {
        std::cout << "Successfully set pointer atomically.\n";
    }
    // Output: Successfully set pointer atomically.
}
This operation atomically checks if ptr holds nullptr and replaces it with desired if true, updating expected otherwise, all under sequential consistency by default. C++20 also adds wait and notify functions to std::atomic<T> and std::atomic_flag, enabling efficient condition variable-like behavior without mutexes. The wait(old_value, order) member function blocks the calling thread until the atomic's value changes from old_value, using an atomic load with the specified memory order (defaulting to std::memory_order_seq_cst); it unblocks on notification or . Complementing this are notify_one() and notify_all(), which wake waiting threads without requiring a preceding load or store. These methods facilitate low-overhead synchronization for scenarios like task completion tracking, where threads wait on an atomic counter. For std::atomic_flag, the new wait(order) overload blocks until the flag changes, enhancing its use in spinlocks or simple barriers. This combination allows implementing wait-notify patterns more efficiently than polling, though it remains susceptible to the due to bitwise comparison. Bit manipulation capabilities receive refinements, notably in std::atomic_flag::test_and_set(order), which now explicitly supports memory orders like std::memory_order_acquire or std::memory_order_release for finer control over . This atomically sets the to true and returns the prior , ensuring of prior writes in acquire mode or ordering subsequent accesses in release mode. Such updates make atomic_flag more versatile for implementing lock-free algorithms, such as locks, where relaxed orders can optimize performance on hardware with weak memory models. The test() function, also new in C++20 for atomic_flag, provides a lock-free read without modification. Another key addition is std::atomic_ref<T>, which provides atomic operations on a to a non-atomic object of type T, allowing thread-safe access to existing data structures without modifying their type. It supports the same operations as std::atomic<T> (load, , , compare_exchange, etc.) and can be used for lock-free updates on mutable objects, with is_always_lock_free indicating hardware support. For example:
cpp
#include <atomic>
#include <iostream>

int value = 0;
std::atomic_ref<int> ref{value};

int main() {
    ref.store(42, std::memory_order_relaxed);
    std::cout << ref.load(std::memory_order_relaxed) << '\n';  // Outputs: 42
}
This is useful for retrofitting atomicity to legacy code or when full atomic type replacement is undesirable, though it requires the referenced object to remain alive during use. To aid in optimizing concurrent data layouts, C++20 introduces std::hardware_destructive_interference_size (defined in <new>), a compile-time constant representing the minimum offset to avoid on cache lines, typically 64 bytes on modern . Programmers can use it with alignas to pad members in structures, ensuring they reside on separate cache lines and reducing contention in multiprocessor systems. For instance:
cpp
#include <new>
#include <atomic>

struct PaddedAtomic {
    alignas(std::hardware_destructive_interference_size) std::atomic<int> value;
};
This prevents destructive interference where unrelated atomics on the same cache line cause unnecessary invalidations, improving scalability in parallel workloads. A related constant, std::hardware_constructive_interference_size, defines the maximum size for beneficial sharing, but the destructive variant is key for atomics. Finally, C++20 refines the memory model with respect to release-consume ordering and synchronizes-with relations. The std::memory_order_consume is effectively lifted to std::memory_order_acquire in implementations, providing stronger guarantees for dependent data without full acquire semantics. Synchronizes-with edges now incorporate a "strongly happens-before" relation that excludes consume operations, ensuring for memory_order_seq_cst in a (S) consistent with modification orders. These changes address prior ambiguities in release sequences, allowing acquire operations to synchronize with s via any member of the sequence, enhancing portability across architectures.

Format Library

The Format Library in C++20 provides std::format as a modern, type-safe facility for generating formatted s, drawing from the design of Python's str. and the {fmt} library to offer an extensible replacement for legacy functions like and sprintf. This library resides in the <format> header and supports positional or argument binding through curly brace placeholders {} in the , enabling concise and readable code without manual type conversions or buffer overflows. For instance, the expression std::string greeting = std::format("Hello, {}!", "world"); produces the "Hello, !", with of the type for insertion. Format specifiers enhance control over output appearance, including alignment (< for left, > for right, ^ for center), fill characters, width, precision, and type-specific options such as (x) or fixed-point notation (f). These are appended after a colon within the braces, as in std::format("{:>10.2f}", 3.14159), which yields " 3.14" by right-aligning the value to a width of 10 characters with two decimal places. For user-defined types, customization occurs via of the std::formatter<T> , allowing parse and functions to handle type-specific logic while leveraging the library's core machinery. Invalid format strings or mismatched arguments trigger a std::format_error exception at , ensuring robustness without the common in C-style formatting. The library integrates seamlessly with the <chrono> header for formatting time points and durations, using a subset of the POSIX strftime specifiers prefixed by % within the format braces. For example, std::format("{:%Y-%m-%d %H:%M:%S}", std::chrono::sys_days{2020y/January/1} + 12h + 30min) outputs "2020-01-01 12:30:00", supporting locale-aware rendering of years (%Y), months (%m), hours (%H), and time zones (%Z). Alignment, width, and fill apply to chrono outputs as well, such as {0:>20%Y-%m-%d} for padded dates. As an alternative to iostreams, std::format delivers superior performance, with benchmarks in the proposal demonstrating it to be 10-20 times faster than std::ostringstream for common integer and floating-point formatting tasks while matching or exceeding sprintf speeds in most scenarios. Its compile-time type checking prevents mismatches that could lead to runtime errors in variadic functions like printf, promoting safer code without sacrificing expressiveness.

Other Library Additions

C++20 introduces several enhancements to the beyond the major updates to ranges, chrono, atomics, and formatting, focusing on concurrency primitives, mathematical utilities, , source code debugging aids, and improvements to type-safe unions. In the realm of concurrency, C++20 adds std::jthread, a joining thread class that automatically joins upon destruction, eliminating the need for explicit calls to join() or detach() and reducing the risk of resource leaks from unjoined threads. This class extends std::thread by integrating cooperative cancellation via std::stop_token, which allows threads to check for cancellation requests periodically and respond accordingly, enabling more graceful shutdowns in concurrent applications. For example, a std::jthread can be created as follows:
cpp
#include <thread>
#include <stop_token>

void worker(std::stop_token stoken) {
    while (!stoken.stop_requested()) {
        // Perform work
    }
}

int main() {
    std::jthread t(worker);
    // t joins automatically on destruction
}
Additionally, C++20 introduces semaphore types for synchronizing access to limited resources: std::counting_semaphore<N> for counting up to N permits and std::binary_semaphore as a specialized case limited to 0 or 1 permit, both supporting acquire and release operations with optional timeout support. These primitives facilitate producer-consumer patterns and resource pools without the overhead of full mutexes, as in:
cpp
#include <semaphore>

std::counting_semaphore<5> sem{5};  // Allow up to 5 concurrent accesses

void task() {
    sem.acquire();  // Wait if count is 0
    try {
        // Critical section
    } finally {
        sem.release();
    }
}
Further concurrency additions include std::latch, a single-use barrier that allows threads to notify arrival up to a specified count, after which all threads can proceed; it is non-resettable and ideal for one-time synchronization events like initialization. For example:
cpp
#include <latch>
#include <thread>

std::latch lat{4};  // Wait for 4 threads

void worker(int id) {
    // Do work
    lat.count_down();  // Signal completion
}

int main() {
    std::array<std::thread, 4> threads;
    for (int i = 0; i < 4; ++i) {
        threads[i] = std::thread(worker, i);
    }
    lat.wait();  // Wait for all to finish
    for (auto& t : threads) t.join();
}
Similarly, std::barrier provides a resettable synchronization point for a fixed number of threads, with an optional arrival notification function for phased parallelism, such as in parallel algorithms. The mathematical utilities in <cmath> are expanded with std::midpoint(a, b), which computes the while avoiding for integers and pointers, and std::lerp(a, b, t), a function that ensures monotonicity for floating-point types even when t is exactly 0 or 1. These functions address common numerical issues in , simulations, and algorithms requiring precise , such as:
cpp
#include <cmath>

double pos = std::lerp(0.0, 10.0, 0.5);  // Exactly 5.0, monotonic
int mid = std::midpoint(1, INT_MAX);     // Safe, no overflow
The new <bit> header provides a collection of free functions for bitwise operations on unsigned integers, including std::rotl and std::rotr for rotations, std::countl_zero for leading zeros, and std::bit_ceil for the smallest power-of-two greater than or equal to the input. These utilities standardize low-level bit manipulation previously reliant on implementation-defined intrinsics, useful in cryptography and data compression; for instance:
cpp
#include <bit>

unsigned x = 0b0001'1100;
unsigned rotated = std::rotl(x, 2);  // 0b0111'0001
int leading_zeros = std::countl_zero(x);  // 26 for 32-bit
For and , std::source_location offers a lightweight way to capture details like file name, line number, and without macros or __FILE__/__LINE__, populated at compile-time or via a current() at . This type integrates seamlessly with assertions and error reporting, as shown:
cpp
#include <source_location>
#include <iostream>

void log_error(std::source_location loc = std::source_location::current()) {
    std::cerr << loc.file_name() << ':' << loc.line() << ": error\n";
}
Another notable addition is std::span<T>, a non-owning view over a contiguous sequence of objects, similar to a or string_view for general types. It provides bounds-safe access via data(), size(), and iterators, with subtypes like span<const T> for read-only views. Spans can be constructed from s, containers, or pointers with sizes, enabling efficient passing of data without copies; for example:
cpp
#include <span>
#include <vector>
#include <algorithm>

void process(std::span<const int> s) {
    auto sum = std::reduce(s.begin(), s.end(), 0);
    // ...
}

int main() {
    std::vector<int> vec{1, 2, 3};
    process(vec);  // Implicit [conversion](/page/Conversion)
}
This promotes safer, more efficient interfaces in handling buffers or collections. These additions collectively improve the expressiveness and safety of concurrent, numerical, and diagnostic code in C++20.

Keywords, Deprecations, and Removals

New and Changed Keywords

C++20 introduces several new keywords to support advanced language features such as , coroutines, and modules. These keywords are reserved identifiers that cannot be used for variable names or other user-defined purposes, ensuring compatibility with existing codebases. The keyword [concept](/page/Concept) is used to define a named on parameters, enabling more expressive and readable programming by specifying requirements on types. For example, a might require a type to be equality comparable:
cpp
template<typename T>
[concept](/page/Concept) EqualityComparable = requires(T a, T b) {
    { a == b } -> std::same_as<bool>;
};
This facilitates compile-time checks and better error messages in generic code. The requires keyword introduces requirement clauses in template declarations, allowing constraints to be expressed directly within function or class templates. It supports various forms, including simple type requirements and nested requirement expressions, enhancing the precision of template instantiation. For instance:
cpp
template<typename T>
requires std::[integral](/page/Integral)<T>
void process(T value) { /* ... */ }
This keyword is to the concepts facility, promoting safer and more maintainable generic programming. Coroutine-related keywords include co_await, co_yield, and co_return, which enable asynchronous and generator-style programming by suspending and resuming function execution. co_await suspends a coroutine until an awaitable expression completes, co_yield yields a value to the caller while suspending, and co_return terminates the coroutine, potentially yielding a final value. These are used within coroutine bodies marked by co_await in contexts like asynchronous I/O operations. Module keywords import and module form the basis of the new module system, which improves build times and encapsulation compared to traditional headers. module declares a module interface or implementation unit, while import brings a module into scope, replacing many uses of #include. The keyword export gains contextual meaning in modules to specify exported declarations. For example:
cpp
export module Example;
export int add(int a, int b) { return a + b; }
And in another file: import Example; allows access to add. This system reduces compilation dependencies and name clashes. The type char8_t is introduced as a new keyword for character representation, providing explicit support for 8-bit characters and string literals prefixed with u8. This addresses previous ambiguities in handling in C++ and aligns with modern internationalization needs, such as: const char8_t* str = u8"Hello";. The consteval keyword specifies immediate functions that must be evaluated at , enhancing and constant expressions. For example:
cpp
consteval int square(int x) { return x * x; }
constexpr int y = square(5);  // Evaluated at [compile time](/page/Compile_time)
This ensures the function cannot be called at , promoting optimization and safety in compile-time computations. The constinit keyword requires variables to be initialized with constant expressions during static initialization, preventing dynamic initialization issues in global objects. For example:
cpp
constinit const int limit = 100;  // Must use constant initializer
This helps avoid order-of-initialization problems in multi-translation-unit programs. Changes to existing keywords include support for the inline specifier in nested namespace definitions, allowing inline to appear before any namespace name except the one. This extends C++11's inline namespaces for better library versioning:
cpp
namespace A::inline B::C { /* declarations */ }
which is equivalent to defining an inline namespace B within A containing C. This improves name lookup in nested scopes without affecting enclosing visibility. The using keyword is extended with using enum declarations, which import all enumerators from a scoped or unscoped into the current scope, reducing the need for qualified names. For scoped enums:
cpp
enum class Color { [Red](/page/Red), Green, Blue };
void func() {
    using enum Color;
    if (current == [Red](/page/Red)) { /* ... */ }  // No Color:: prefix needed
}
This enhances code readability in local contexts like functions or classes, without polluting the global . Additionally, override and final see minor refinements in virt-specifier sequences for better integration with C++20's immediate functions, though their core behavior remains from C++11. These additions impact C++20 by enabling modular, constraint-based, and asynchronous programming paradigms, with char8_t specifically improving UTF-8 handling for internationalized applications and modules enhancing build efficiency. To maintain compatibility, all new keywords are reserved, preventing conflicts with pre-C++20 user identifiers, though gradual adoption via compiler flags like -std=c++20 is recommended.

Deprecated Features

In C++20, several legacy features and usages were formally deprecated to encourage adoption of safer, more modern alternatives, with the intent of eventual removal in future standards. These deprecations target ambiguities, outdated semantics, and constructs that have been superseded by newer language mechanisms, such as modules and improved . The changes aim to streamline the language by eliminating rarely used or problematic elements while maintaining during a transition period. One significant deprecation involves certain uses of the volatile keyword, particularly in atomic operations and other contexts where its semantics are unclear or incompatible with multi-ed programming. Specifically, volatile-qualified arithmetic operations on types (e.g., std::atomic<int> volatile x; ++x;), volatile parameters and return types in functions, and volatile uses in increment/decrement expressions, assignments, and structured bindings are now . This addresses longstanding issues where volatile was misused for , as it does not provide the necessary guarantees; instead, developers are directed to use atomic operations or memory orders from <atomic>. The deprecation helps prevent subtle bugs in concurrent code by flagging such patterns at . Another deprecation targets the implicit capture of this in lambda expressions using the [=] capture-default, which previously captured the current object pointer implicitly. This behavior, while convenient, could lead to unexpected lifetime issues and non-explicit dependencies in object-oriented designs. In C++20, it is deprecated in favor of explicit captures like [this] or [=,*this], promoting clearer intent and reducing surprises in lambda usage within member functions. Additionally, the comma operator in array subscript expressions (e.g., array[i, j]) is deprecated due to its potential for ambiguity and confusion with intended indexing. This construct, inherited from C, evaluates to the value of the rightmost operand but can mislead readers into interpreting it as multi-dimensional access. Programmers are encouraged to use parentheses or restructure for clarity, aligning with C++'s emphasis on readable expressions. These deprecations, along with others like certain legacy library components, reflect a broader effort to clean up the standard by retiring features that no longer align with contemporary programming practices. While still compilable, their use triggers warnings in conforming compilers, urging migration to equivalents like the new ranges library or coroutines for more robust code. Note that some previously deprecated items, such as std::uncaught_exception(), were fully removed in C++20 rather than merely deprecated.

Removed Features

In C++20, several features deprecated in prior standards were fully removed to streamline the language and library, eliminating redundancies and encouraging the adoption of safer, more modern alternatives. This cleanup effort, documented in the working changes (P2131R0), targeted elements that had outlived their utility or posed maintenance burdens without significant benefits. Key removals include specific compatibility headers, outdated exception specifications, and various utilities. One prominent category of removals involves the excision of certain C compatibility headers that duplicated functionality already provided by C++ headers. Specifically, the headers <ccomplex>, <ciso646>, <cstdalign>, <cstdbool>, and <ctgmath> were removed, as they offered no unique value in a C++ context and encouraged reliance on legacy C-style includes. These headers, introduced for , are now obsolete, prompting developers to use native C++ equivalents like <complex>, <ciso646> alternatives via keywords, <align>, <stdbool.h> via built-ins, and <tgmath.h> through overloads. Language-level changes included the removal of the throw() exception specification syntax, which was deprecated in favor of noexcept. The throw() form, implying no exceptions, is no longer supported; code must migrate to noexcept for equivalent semantics, improving consistency and compile-time checking. Additionally, the std::uncaught_exception() function (no arguments) was removed, replaced by the plural std::uncaught_exceptions() to better handle nested exceptions in modern threading scenarios. In the , several deprecated components were eliminated. The function objects std::not1, std::not2, std::unary_negate, and std::binary_negate were removed, as they were superseded by lambda expressions and more flexible binders introduced in C++11. Type traits such as std::result_of and std::is_literal_type were excised; std::result_of is replaced by std::invoke_result, while std::is_literal_type lacks a direct substitute but is unnecessary given refined traits like std::is_trivial. Allocator-related members including pointer, const_pointer, reference, const_reference, rebind, address, construct, and destroy were removed from std::allocator, streamlining customization with modern allocator models. The iterator adaptor std::raw_storage_iterator and temporary buffer functions std::get_temporary_buffer and std::return_temporary_buffer were also deleted, with recommendations to use std::uninitialized_* algorithms and smart pointers for similar purposes. Finally, std::shared_ptr::unique() was removed, as its functionality is now covered by std::shared_ptr::use_count() == 1. These removals, while requiring code updates in legacy projects, promote cleaner, more efficient C++ usage by enforcing contemporary best practices such as via noexcept and through RAII with smart pointers. The changes have minimal impact on well-maintained codebases but necessitate targeted migrations for older software relying on these artifacts.

Incorporated Technical Specifications

Coroutines TS

The Coroutines Technical Specification (TS), published as ISO/IEC TS 22277:2017, originated from a series of proposals developed between 2015 and 2018 to introduce coroutine support into C++. Early work began with N4402 ("Resumable Functions," revision 4) in April 2015, authored by Gor Nishanov and others at , which outlined the foundational concepts for stackless coroutines as an extension to the language. This evolved through subsequent revisions and discussions in the ISO/IEC JTC1/SC22/WG21 committee, incorporating feedback from experimental implementations, culminating in the TS draft N4678 in July 2017. The committee process advanced the specification through iterative reviews, with the Coroutines TS receiving approval for publication as a Draft Technical Specification (PDTS) at the WG21 meeting in in July 2017. Initial attempts to merge it directly into the C++20 working draft occurred at meetings in Rapperswil (June 2018) and (November 2018), but consensus fell short due to concerns over integration with other features. Final approval for incorporation into C++20 came via paper P0912R5 at the meeting in 2019, merging the TS (as N4775) into the working draft N4800 with targeted refinements. At its core, the Coroutines TS defines a stackless coroutine model, where execution suspends by returning control to the caller without preserving a separate , instead transforming the coroutine body into a state machine that manages local state in heap-allocated frames. Key elements include the promise type, which encapsulates the coroutine's state and —such as handling returns, yields, and exceptions via methods like return_value() and unhandled_exception(); awaitables, objects returned by co_await expressions that define and resumption logic through await_ready(), await_suspend(), and await_resume(); and keywords (co_await, co_yield, co_return) that trigger these operations within functions. This design enables efficient asynchronous and generator-like patterns without runtime overhead beyond the state machine allocation. Integration into C++20 involved minor tweaks for consistency with the core language, such as restricting await expressions to non-static initializers and adding the <coroutine> header with std::coroutine_traits to decouple the coroutine's return type (typically a ) from the type, allowing greater customization via . Unlike the , where the return type directly served as the type, C++20's traits-based approach supports more flexible library designs, such as generators or tasks, while maintaining for implementations. Prior to standardization, the TS enabled experimental adoption through vendor implementations, including Microsoft Visual C++ since version 14.0 (2015 SP2) and since version 5 (2017), allowing developers to test coroutine patterns in real-world scenarios like asynchronous I/O and parallel simulations. These early supports, based on evolving drafts, facilitated feedback that refined the final C++20 feature set without introducing breaking changes.

Concepts TS

The Concepts Technical Specification (TS), formally known as ISO/IEC TS 19217:2015, introduced extensions to for constraining parameters using named predicates, known as , which specify requirements on types and other arguments. These served as compile-time checks to ensure that instantiations met specified syntactic and semantic criteria before substitution, improving error diagnostics and enabling better . Core ideas included fundamental predicates such as Same<T, U>, which evaluates to true if types T and U are identical, and Convertible<T, U>, which checks if T is implicitly convertible to U, providing a foundation for more complex definitions. The TS also proposed advanced features like maps, which allowed implicit adaptations of types to satisfy requirements (e.g., mapping a pointer type to an with associated types), and axioms, which encoded semantic properties (e.g., commutativity of operations) as optional compile-time assertions to aid optimization and verification tools without mandating runtime enforcement. The evolution of from the TS to C++20 involved significant simplification to prioritize and compiler feasibility, resulting in the adoption of "lightweight" that omitted axioms and concept maps in favor of more straightforward requires clauses for expressing constraints directly on templates. This shift focused on syntactic requirements as the primary mechanism, deferring deeper semantic checks like axioms to potential future standards while retaining the core predicate-based approach for better template error messages and code clarity. The directly influenced C++20's standardized by providing the foundational and , enabling their integration into the core and without the full overhead of the original . Prior to C++20, concepts from the were experimentally implemented in libraries such as .ConceptCheck, which emulated concept verification using macros and templates to enforce requirements at in pre-standard C++ codebases. These tools demonstrated practical benefits in generic libraries, paving the way for formal adoption, and the itself was referenced in other specifications like the to define and abstractions.

Ranges TS

The Ranges Technical Specification, designated ISO/IEC TS 21425:2017, was published in November 2017 by the (ISO) and the (IEC). This document outlined extensions to the to support operations on ranges of data, serving as the precursor to the full integration of the ranges library into C++20. Although the TS was later withdrawn following its incorporation into the ISO/IEC 14882:2020 standard for C++20, it marked a pivotal step in modernizing in C++. Central to the specification are features like iterator adaptors reimagined as views, which enable non-owning, lazily evaluated transformations on sequences of elements, avoiding the creation of intermediate containers. It introduces support for single-pass ranges, allowing algorithms to operate on data traversable only once, such as input streams, while providing composable algorithms with overloads that accept ranges directly rather than separate . These elements, including new components like counted_iterator, common_iterator, and the unreachable , facilitate more efficient and expressive iteration patterns. For inclusion in C++20, the specification underwent refinements, such as finalizing the pipe syntax with the | to chain views and adaptors—e.g., transforming a via std::views::[filter](/page/Filter) or std::views::transform in a fluent manner. Sentinels were further emphasized for performance gains, permitting end markers of types different from iterators to optimize traversal without default-initializing iterators. These updates addressed feedback from TS implementations, enhancing usability while preserving the core design. Before standardization, the Ranges TS profoundly shaped library evolution by popularizing lazy evaluation techniques, as demonstrated in reference implementations like ranges-v3, which inspired efficient, composable data pipelines in production code. The TS also extends the core language slightly by adapting the range-based for loop to work seamlessly with these new abstractions. The specification relies on concepts to define range categories, such as input_range for basic iteration and forward_range for multi-pass access, integrating with the separate Concepts TS to enforce constraints and improve metaprogramming safety in generic code.

Other Relevant TS

The Library Fundamentals Technical Specification Version 3 (drafted in 2019) introduced several foundational library components prototyped for potential integration into the , influencing C++20 by providing refined utilities for safer and more expressive code. Notably, it specified std::source_location, a lightweight structure for capturing details such as file name, , and function name at , which was directly incorporated into C++20's <source_location> header to support diagnostics and logging without runtime overhead via proposal P1542R2. Additionally, it refined std::span, enhancing its bounds-safe views over contiguous sequences with better customization points and extensions for heterogeneous types, elements of which informed C++20's std::span improvements for multidimensional arrays and dynamic extents via P0122R7. These features served as experimental grounds for library evolution, allowing the ISO C++ committee to test usability before standardization. The Parallelism Technical Specification Version 2 (ISO/IEC 19570:) extended the parallel execution model from earlier versions, focusing on advanced policies for standard algorithms to leverage and hardware concurrency more effectively. It proposed execution policies like std::execution::par_unseq with vectorization support, enabling algorithms such as std::sort and std::transform to exploit SIMD instructions, though full vector policies were deferred beyond C++20 in favor of simpler par and par_unseq integrations already present from C++17. This TS prototyped scalable parallelism primitives, influencing C++20's by validating execution policy mechanisms for future enhancements in threading and GPU offloading. The Networking Technical Specification Version 2 (draft N4718, 2018) built on its predecessor by specifying asynchronous I/O primitives for buffers, sockets, and executors, aiming to standardize network programming with composable async operations. It introduced concepts like net::buffer for efficient data handling and net::io_context for managing asynchronous operations, but core networking facilities—such as full socket APIs and protocol support—were deferred from C++20 due to ongoing design reviews, with only executor-related elements indirectly shaping C++20's concurrency model via related proposals like P0448. These prototypes facilitated experimentation with async patterns, providing a foundation for potential future inclusion in the standard library. The mathematical constants in the <numbers> header (e.g., std::numbers::pi_v<float>) were added in via proposal P0106R4 to promote precision and portability in mathematical expressions, standardizing values previously reliant on approximations or third-party libraries. This addition stemmed from the need for high-precision math support in generic algorithms, influenced by standards like ISO/IEC/IEEE 60559 but incorporated directly without reliance on a specific TS for (which were addressed separately in ISO/IEC 29124:2010 for ). Collectively, these Technical Specifications functioned as iterative prototypes, enabling the C++ committee to refine features through feedback and trials before their selective incorporation into C++20, ensuring robustness and minimal disruption to existing codebases.

Deferred Proposals

C++20 deferred several ambitious proposals due to constraints and unresolved design issues. As of the June 2025 feature freeze and the November 2025 ISO meeting in Kona, Hawaii, where final fit-and-finish work progressed, C++26 incorporates contracts and a limited form of but defers full to future standards.

Contracts

The Contracts proposal, formally known as P0542R5, aimed to introduce support for contract-based programming in C++, allowing developers to specify preconditions, postconditions, and assertions directly in code using attributes such as [[expects]] for preconditions and [[ensures]] for postconditions. These contracts would enable or compile-time verification of function behaviors, enhancing code reliability by enforcing expected conditions at entry and exit points. Despite achieving consensus for inclusion in C++20, the proposal was deferred due to unresolved complexities in error handling mechanisms, particularly the debate over whether contract violations should trigger an immediate abort or throw an exception, and challenges in integrating s seamlessly with C++'s existing exception system. These issues stemmed from a lack of experience and consensus on how violations should propagate without introducing or complicating adoption. By November 2025, contracts have been incorporated into the C++26 standard as a simplified version under proposal P2900R14, focusing on a minimal viable product with four semantics—ignore (no check), observe (check without termination), enforce (check and terminate), and quick_enforce (fast termination)—where checks are not enabled by default and can be controlled via implementation-defined modes. This iteration removes complexities like support for virtual functions and procedural assertions from the original design, prioritizing compile-time documentation and optional enforcement. In the absence of contracts in C++20, developers can approximate some benefits using static_assert for compile-time checks or for template constraints, though these lack the full and function-level assertions of contracts. The feature's intended impact is to promote that is easier to verify and maintain, reducing bugs through explicit behavioral guarantees without relying solely on external testing.

Reflection

The reflection proposal for C++20, detailed in P1240R2, aimed to introduce compile-time capabilities through a value-based system using the meta::info type and the reflection operator ^. This would enable querying properties of types, members, and functions at , such as retrieving member names and types via metafunctions like name_of and members_of. The proposal also included splicing mechanisms with syntax like [:refl:] to inject reflected information directly into , facilitating without runtime overhead. Key planned features encompassed generating metaprogramming code from reflections, such as iterating over members to produce functions or specializations. For instance, a struct's members could be enumerated at to auto-generate equality operators or converters. Use cases included libraries that avoid manual boilerplate or macro-based solutions, and GUI builders that dynamically create interface code from type definitions. These capabilities were envisioned to integrate with modules for scoping reflections to specific translation units, enhancing modularity in large projects. The proposal was deferred from C++20 primarily due to unresolved debates over syntax design, potential performance implications in compilers, and the overall ambition exceeding the standard's timeline constraints. By November 2025, the feature has been adopted for C++26 with a limited via P2996R12, introducing partial constexpr features like basic type and member queries but omitting full splicing and expression for future iterations. This scaled-back approach allows initial adoption for tasks while addressing earlier concerns about complexity.

Pattern Matching

Pattern matching was proposed as a significant enhancement to C++ to provide a more expressive and safe way to destructure and inspect algebraic data types, such as tuples, , and user-defined structures, building on existing facilities like structured bindings. The primary proposal, P1371R2, aimed to introduce an inspect statement that allows declarative matching against patterns, reducing the reliance on verbose if-else chains or patterns for handling types. This feature would enable developers to define patterns for literals, identifiers, structured bindings, alternatives, and extractors, including support for pattern guards via if clauses, all while maintaining composability and constexpr compatibility. The proposed syntax in P1371R2 centered on the inspect keyword, allowing forms like inspect (expr) { pattern: body; } for simple cases, or more complex ones such as:
inspect (point) {
  Point{ .x, .y }: std::cout << "Point at (" << x << ", " << y << ")\n";
  __: std::cout << "Not a point\n";
}
Here, Point{ .x, .y } uses inspector patterns to destructure members, with __ as a wildcard, extending to variants via angle-bracket notation like <int> i: .... This design supports primary patterns (wildcards, identifiers, literals) and compound patterns (structured bindings like [x, y], alternatives with |, and dereferences), facilitating safer deconstruction of optional-like types without null checks or explicit visitation. The proposal emphasized interoperability with emerging features like std::variant, allowing patterns to match held types and extract values directly. Despite positive feedback from the Evolution Working Group, was deferred from C++20 due to semantic ambiguities in pattern resolution, particularly overlaps with handling and the need for clearer integration with structured bindings, as discussed in committee meetings leading to the standard's finalization. The decision, made late in the C++20 cycle, prioritized completing core features like modules and coroutines over this more experimental addition. Ongoing work has evolved the design, with later proposals like P2688R5 exploring a match expression for single-pattern results, but it was not included in C++26 and is now targeting a future standard such as C++29. In C++20, precursors to full pattern matching appeared through enhancements to structured bindings, which were extended to support arrays of known bound and relaxed accessibility rules for member access during binding. For example, auto [a, b] = std::array{1, 2}; now works directly, providing a limited form of that hints at 's goals but lacks the expressive power for variants or guards. These improvements offer safer alternatives to manual indexing in if-else chains, though they fall short of the comprehensive matching envisioned in deferred proposals. Overall, pattern matching promises to make C++ code more concise and less error-prone for algebraic data handling, akin to features in languages like or , once standardized.

Implementation and Compiler Support

Support in Major Compilers

As of November 2025, major C++ compilers provide substantial support for C++20 core language features, with some aspects fully implemented and others recently stabilized, such as in 15 and 18. Developers can utilize the standard with flags like -std=c++20 in and , or /std:c++20 in MSVC, though modules and coroutines may require specific configurations in certain implementations. Edge cases in coroutines and module imports have largely been resolved, enabling production use with minimal workarounds. GCC offers production-ready C++20 support starting with version 11 (2021), with experimental status removed in GCC 15 (2025), when compiled with -std=c++20. Modules reached stable implementation in GCC 11, eliminating earlier limitations like incomplete private module fragments. Partial support for concepts began in GCC 10, achieving full readiness by GCC 11. Clang provides substantial C++20 support from version 10, with core features approaching full conformance in version 18 (2025) using -std=c++20. Modules stabilized in 15 (2023), coroutines remain partial depending on target. Experimental modules from 8 progressed to stability by 15. Microsoft Visual C++ (MSVC) delivers full C++20 core language support since Visual Studio 2019 version 16.10 (MSVC 19.28, 2021) using /std:c++20. Modules transitioned from experimental in VS 2019 16.8 to stable in 16.10 and later. Three-way comparison available from MSVC 19.22; comprehensive support in 2022 (MSVC 19.30+) and VS 2026 previews as of 2025. EDG-based compilers, including Intel oneAPI DPC++/C++ Compiler (versions 2023.1+), provide substantial C++20 support with -std=c++20, aligning closely with ; modules partial in ICX 2023.2+. For detailed feature status, including HPC, see community tables.
CompilerFull C++20 Support VersionModules Complete VersionPrimary FlagNotes on 2025 Status
11 (2021, non-experimental in 15, 2025)11+-std=c++20Substantial; production-ready, bugs mostly resolved.
Substantial from 10 (near-full in 18, 2025)15+ (2023)-std=c++20Partial coroutines; stable overall.
MSVC19.28+ (VS 2019 16.10, 2021)19.28+/std:c++20Full; integrated in VS 2026 previews.
(EDG/Clang-based)Substantial 2023.1+Partial 2023.2+-std=c++20Aligns with Clang 18; modules ongoing.
Library implementation gaps, such as partial ranges in some libraries, are addressed separately but do not impact core language. As of November 2025, 15 removes C++20 experimental status.

Library Implementation Status

As of November 2025, C++20 standard library features have widespread adoption in major implementations, with libc++, libstdc++, and MSVC STL achieving high completeness for ranges, chrono, and format. Progress tracked via official pages reflects ISO conformance. libc++, used with , supports ranges fully since LLVM 15 (2022, P0896R4). Chrono extensions (P0355R7) remain partial, with core features available; ongoing work in LLVM 18+. fully available since LLVM 14 (2022, P0645R10). char8_t complete since LLVM 16. Modules integration (P1502R1) complete in recent builds. libc++ nears full C++20 conformance by 2025, partials limited to chrono extensions. libstdc++ supports C++20 library features from 11 (2021), including layout-compatibility (P0466R5). Time zones in chrono fully implemented since 11. Ranges full since 12 (2022), format since 11. Full support achieved in 11, with refinements for C++26 in 15. MSVC STL completed core C++20 implementations by 2019 version 16.10 (2022), covering ranges, format, and most chrono like zoned_time. Chrono enhancements finalized in VS 2022 17.x (2023). By VS 2022 17.14 (May 2025), reports complete C++20 support, with conformance bugs resolved.
LibraryRangesChrono (incl. Time Zones)Formatchar8_t SupportNotes
libc++ (/)Full (LLVM 15, 2022)Partial (core LLVM 15+; extensions ongoing 2025)Full (LLVM 14, 2022)Full (LLVM 16, 2023)Modules complete 2025; near-full conformance.
libstdc++ ()Full (GCC 12, 2022)Full (GCC 11, 2021)Full (GCC 11, 2021)Full (GCC 9, 2020)Full from GCC 11; C++26 extensions in GCC 15.
MSVC STLFull (VS 2019 16.10, 2022)Full (VS 2022 17.x, 2023)Full (VS 2019 16.10, 2022)Full (VS 2019 16.7, 2021)Complete by 2025; Windows-focused.
Platform adaptations vary; r26 (2024) offers full C++20 via libc++ with -std=c++20. char8_t compatibility issues persist in mixed builds. Conformance validated by ISO test suites and stdtests, with near-100% pass rates for 15, 18, MSVC 17.14 in 2025.

Historical Context

Development Timeline

Following the publication of the C++17 standard in December 2017, the ISO/IEC JTC1/SC22/WG21 committee initiated planning for its successor at the March 2018 meeting in , where initial goals were established, including prioritization of features like modules and , and the overall schedule for the C++20 cycle was unanimously approved. Working papers such as P0734R0, which proposed extensions for to enable better and , played a key role in feature prioritization during this early phase. Subsequent meetings advanced the draft significantly, with the July 2019 session in Cologne, Germany, focusing on integrating major features into the committee draft (CD). The onset of the shifted the 2020 meetings to virtual formats, allowing continued progress despite logistical challenges. A pivotal milestone occurred at the 2020 meeting in , where the committee declared C++20 feature-complete and advanced working draft N4860 (dated March 31, 2020) for international ballot. This led to the Draft International Standard (DIS) ballot initiation on June 11, 2020, which concluded unanimously in favor on September 4, 2020, clearing the path for final approval. The International Standard was formally published as ISO/IEC 14882:2020 in December 2020. Post-publication, the committee processed defect reports through 2023, with resolutions incorporated into subsequent standards such as . The standard was withdrawn in 2024 upon the publication of its successor, (ISO/IEC 14882:2024). The entire active development phase for C++20 lasted approximately three years, from the 2018 Jacksonville meeting to the 2020 publication.

Key Committee Decisions

The WG21 committee's adoption of modules for C++20 marked a pivotal shift toward modernizing the language's compilation model. After over a decade of discussion originating from the Modules Technical Specification, modules were integrated into the working draft in 2019 at the meeting, replacing the header-based inclusion model with explicit import declarations to enhance encapsulation, reduce compilation times, and mitigate issues like macro pollution and inadvertent name dependencies. This decision resolved long-standing debates on build system compatibility and scalability, prioritizing a design that supports both new modular code and gradual migration from legacy headers without requiring wholesale overhauls. In contrast, the committee deferred the inclusion of contracts—a proposed mechanism for specifying preconditions, postconditions, and assertions at function boundaries—during the 2019 Cologne meeting. The rejection stemmed from persistent disagreements on models, overhead, diagnostics, and integration with existing tools, which risked complicating the standard's timely delivery. Alternatives, such as library-based approaches outlined in related proposals like those exploring contract-like assertions via attributes or macros, were evaluated but deemed insufficient for core language adoption at that stage, leading to their postponement for future standards like or beyond. For the ranges library, a of C++20's functional-style programming enhancements, the committee selected the pipe operator (|) syntax for chaining range adaptors and views, favoring it over competing notations like function call chains or hypothetical rewrite operators for its intuitive left-to-right readability and alignment with paradigms in other languages. This choice, finalized as part of the ranges adoption in June at Rapperswil and refined in subsequent polls, enabled composable, lazy-evaluated expressions such as numbers | std::views::filter(is_odd) | std::views::transform(square), promoting safer and more efficient usage without mutable iterators. Supporting refinements, including conditionally borrowed ranges to handle lifetime issues in pipelines, further solidified this design by ensuring compatibility with both lvalue and rvalue inputs. The spaceship operator (<=>), introducing three-way comparisons, was another landmark decision, adopted in 2018 to unify the fragmented set of relational s (<, >, <=, >=, ==, !=) under a single, synthesizable mechanism. By prioritizing a total ordering model—where the operator returns a std::strong_ordering, partial_ordering, or weak_ordering—the committee avoided the pitfalls of partial orders in user-defined types, reducing and preventing inconsistencies in mixed comparisons, as seen in pre-C++20 libraries. This approach allows defaulted operators to automatically generate all six traditional comparisons, streamlining development for types like strings and containers. These decisions were profoundly influenced by contributions from key committee members. , as WG21 convener, provided overarching guidance through his detailed trip reports and feature overviews, such as those synthesizing progress toward C++20's goals of simplicity and productivity. , the language's creator, emphasized core objectives like high performance, zero-overhead abstraction, and resource safety in his committee inputs, ensuring features like modules and ranges advanced C++'s foundational aims without compromising efficiency.