C++20
C++20 is the informal name for the sixth edition of the ISO/IEC 14882 standard defining the C++ programming language, published by the International Organization for Standardization (ISO) in December 2020.[1] This revision supersedes the previous C++17 standard and represents a major update to the language, incorporating enhancements developed by the ISO/IEC JTC1/SC22/WG21 committee over several years.[2] The standard spans approximately 1,853 pages and specifies requirements for C++ implementations, building upon the C language (ISO/IEC 9899:2018) while adding advanced features for data abstraction, object-oriented programming, generic programming, and concurrency.[1]
Among the most prominent additions in C++20 are concepts, which allow developers to constrain template 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.[3] These core features address longstanding limitations in template metaprogramming, build systems, and data manipulation, making C++ more expressive and performant for modern applications such as systems software, games, and high-performance computing.[3]
Further notable language improvements include the three-way comparison operator (<=>), which simplifies defining ordering for custom types; designated initializers for aggregates, akin to those in C; and enhancements to lambda expressions, such as templated parameters and explicit capture lists.[3] 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.[3] Additionally, the standard deprecates or removes several outdated facilities from prior versions, promoting cleaner code while maintaining backward compatibility through gradual adoption options in compilers like GCC, Clang, and MSVC.[4] 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 international working group tasked with maintaining and evolving the C++ programming language under the auspices of the International Organization for Standardization (ISO) and the International Electrotechnical Commission (IEC).[5]
The committee's technical work on C++20 culminated at its in-person meeting in Prague, Czech Republic, 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.[6]
In response to the COVID-19 pandemic, WG21 shifted to virtual meetings for the remainder of 2020, including subgroup teleconferences and a virtual plenary in June 2020 to conduct final editorial reviews, incorporate feedback, and prepare the DIS document N4860 dated June 7, 2020.[7]
This DIS was submitted to ISO national member bodies for formal balloting, a key ratification 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 international standard.[8]
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 C++ standard.[1][2]
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.[7]
Goals and Motivations
C++20 aimed to evolve the language by enhancing its expressiveness, performance, and usability, particularly in the domains of generic programming and concurrency, while preserving the core principles that have defined C++ since its inception. Building on the foundations laid by C++11 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 high-performance computing, embedded systems, and large-scale software development, where developers require tools that balance power with simplicity.[9][10]
A key motivation was to rectify limitations inherited from C++17, notably the issues of header pollution in template-heavy code and the absence of robust compile-time constraints, which often led to cumbersome metaprogramming 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 order of magnitude—and to enforce constraints at compile time, 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.[9]
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, type safety, and compatibility with existing codebases, C++20 addressed the needs of sectors like systems programming, finance, and scientific computing, where scalability and maintainability are paramount. Stability was underscored as a deliberate feature, allowing gradual adoption while supporting diverse hardware architectures. These motivations reflect a pragmatic evolution, informed by decades of committee deliberations and real-world feedback, to sustain C++'s role as a versatile, high-performance language.[9][10]
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 generic code. A concept is declared as a template that evaluates to a boolean constant expression based on its constraints, such as template<typename T> [concept](/page/Concept) Integral = std::is_integral_v<T>;.[11]
This feature allows developers to replace complex Substitution Failure Is Not An Error (SFINAE) techniques with explicit requires clauses, resulting in clearer and more maintainable template definitions. For instance, a function can be constrained directly as template<typename T> requires Integral<T> void process(T value) { /* ... */ }.[11] 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 template.[11]
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.[11]
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.[7]
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 standard library 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.[7]
For scalability in large projects, C++20 supports module partitioning and a global module fragment. Partitions allow splitting a module into submodules, declared as export module ModuleName.PartitionName;, which can import the primary module interface and other partitions using import :PartitionName;. The global module fragment, delimited by module; before the module declaration, provides a space for code that applies globally, such as preprocessor directives or imports that affect the entire program. This structure facilitates incremental builds in complex codebases.[7]
One of the primary advantages of modules is significantly reduced compilation times, as the compiler processes each module interface only once and caches the BMI, 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 macro pollution, as macros defined outside a module do not automatically propagate into it, reducing namespace conflicts and improving reliability.[12][7]
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 C++23 and later standards. While concepts can constrain templates within modular interfaces, their integration primarily aids generic programming rather than module structure itself.[7]
Coroutines
C++20 introduces native support for coroutines, enabling stackless cooperative multitasking for asynchronous programming and generator functions without relying on threads or external libraries.[13] 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.[14] 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.[13]
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 coroutine 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.[13] The co_yield keyword produces a value to the caller and suspends the coroutine, allowing resumption on the next invocation, commonly used in generator patterns.[13] Finally, co_return terminates the coroutine, optionally providing a return value that is forwarded to the promise type for handling, after which the coroutine enters a final suspension state unless specified otherwise.[13]
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.[13] 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.[13] These methods allow tailoring coroutines to specific needs, such as integrating with execution contexts or allocators.[14]
For manual management, C++20 provides std::coroutine_handle<Promise>, a non-owning handle to a coroutine's frame, enabling explicit resumption, destruction, and state queries outside the automatic mechanism.[13] Key operations include resume(), which continues execution from the suspension point (undefined if the coroutine is not suspended or has completed); destroy(), which deallocates the frame; and done(), which checks completion status.[13] 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 thread safety.[13] The <coroutine> header also includes std::coroutine_traits for deducing promise types and std::noop_coroutine_handle for a null handle.[13]
Coroutines excel in use cases like implementing generators for lazy evaluation 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.[13] 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.[14] Such patterns reduce context-switching costs compared to traditional threading models.[13]
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 equality operations for user-defined types. This operator performs a three-way comparison between two operands, returning one of several comparison category types defined in the <compare> header, such as std::strong_ordering for total orders (where equality 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.[15]
The spaceship operator can be explicitly defined for a class 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 compiler synthesizes a memberwise or lexicographical comparison of the object's subobjects (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 subobject 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 operand and that the class has no virtual bases or user-provided constructors that interfere.[16][15]
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.[16][15]
Equality comparisons also see improvements, particularly for standard library types like arrays and tuples. For std::array<T, N>, the equality operator == (present since C++11) 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 spaceship. These changes ensure consistent, short-circuiting behavior using std::strong_equality for bitwise or value-based equivalence where applicable.[17][18][15]
Overall, these features simplify the implementation of comparison operators for user-defined types by allowing a single <=> definition to generate the full set via rewriting, while supporting heterogeneous comparisons between compatible types without explicit overloads. This reduces code duplication, improves maintainability, and aligns with modern practices for generic programming, such as in the Ranges library where three-way comparisons facilitate sorting and searching.[15][19]
Designated Initializers
Designated initializers provide a syntax for explicitly initializing specific members of aggregate 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 type safety and adherence to aggregate initialization rules.[20][7]
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};
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).[20][21]
Designated initializers apply exclusively to aggregate classes, including non-union structs and unions that lack user-provided constructors, private or protected non-static data members, virtual functions, or virtual/private/protected base classes. They do not extend to non-aggregate 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 prefix 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 comma and an additional initializer-list for the remaining members after the designated prefix.[20][7][21]
This feature extends C99 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 C. These limitations enhance predictability in member destruction order and side-effect sequencing, aligning with C++'s aggregate initialization semantics. C++ adds compile-time checks for narrowing conversions and type mismatches, providing greater robustness than C's more permissive model.[20][7]
Common use cases include initializing complex structs for configuration options or parameters without relying on default constructors or verbose setters, such as hardware registers or API request objects. For instance:
cpp
struct Config {
int timeout;
bool enabled;
std::string name;
};
Config cfg{.timeout = 30, .enabled = true, .name = "default"};
struct Config {
int timeout;
bool enabled;
std::string name;
};
Config cfg{.timeout = 30, .enabled = true, .name = "default"};
This approach facilitates partial updates and self-documenting code, particularly beneficial in embedded systems or performance-critical applications where aggregates avoid constructor overhead.[22][20]
Other Language Features
C++20 introduced several enhancements to lambda expressions, improving their flexibility and usability in generic programming. One key improvement allows capturing the enclosing class's *this by value, enabling the creation of independent copies of the object within the lambda without modifying the original; this is specified using [=*this] in the capture list.[23] Additionally, template lambdas can now use explicit template parameter lists, such as []<typename T>(T x) { return x; }, which facilitates more precise control over type deduction in lambda bodies.[23] Stateless lambdas, those without captures, can now be implicitly converted to function pointers, allowing them to be passed to C-style APIs expecting function pointers, as in int (*fp)() = [] { return 42; };.[23] Pack expansions in lambda captures further enable variadic handling, for example [...xs = std::move(xs)] to capture a parameter pack into the lambda.[23]
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.[24] 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.[24] 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.[24]
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.[25] This syntax supports concepts as well, such as void g(Integral auto y) { }, which constrains the parameter to integral types.[25] 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.[26] Refinements to existing attributes include extensions for [[nodiscard]] and [[maybe_unused]], which now apply more broadly to suppress warnings in generic contexts, such as unused structured bindings or discarded values in template 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.[27] This feature is particularly useful in class or namespace scopes for cleaner code integration.[27]
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 range as any type that supports begin() and end() operations to access a sequence, formalized in namespace 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 lazy evaluation without data duplication. Views act as composable sequences that reference underlying data indirectly, allowing operations to be chained without immediate execution or allocation of intermediates. The library also defines refinements like input_range, a range whose iterator category models std::input_or_output_iterator, ensuring compatibility with forward iteration semantics.[28]
Central to the library's usability is the pipe operator |, which facilitates fluent composition of views and adaptors, improving readability over traditional nested function calls or iterator 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 integer sequence, [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 range in place or std::ranges::for_each(rng, [](auto&& elem){ /* process */ }) for applying a function to each element, accept ranges directly and integrate seamlessly with piped expressions, reducing the boilerplate of iterator pairs.[29][30][31]
Range adaptors further enhance composability by transforming views lazily, deferring computation until consumption. Examples include std::views::take(n) to limit a range 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 range 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.[29][31]
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.[31]
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, time zones, 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 framework for applications requiring temporal computations, such as scheduling, logging, and internationalization.[32]
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 (January) to 12 (December), 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 (Sunday) to 6 (Saturday), with circular arithmetic to handle week boundaries. These primitives combine into higher-level types such as year_month_day, which represents a full civil date and can be constructed using the / operator, for example, sys_days{2020y / December / 25} to denote December 25, 2020, as a system-time day point since the Unix epoch. This notation facilitates intuitive date literals while ensuring validation for invalid dates, like February 30, which results in an uninitialized state.[32]
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.[32]
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.[32]
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.[32]
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 time zone. Conversions between sys_time, local_time, and zoned_time handle DST folds and gaps automatically, ensuring that operations like addition of durations preserve civil time 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.[32]
Atomic Operations
C++20 introduces several enhancements to the atomic operations library in <atomic>, improving support for lock-free concurrency and thread synchronization. These changes build on the C++11 memory model by providing more efficient primitives for shared memory access, reducing reliance on traditional mutex-based synchronization in certain scenarios. Key additions include atomic specializations for smart pointers, wait/notify mechanisms on atomic variables, refined bit-level operations, hardware-aware sizing constants, and clarifications to synchronization 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.[33][34]
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.
}
#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.[33]
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 spurious wakeup. 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 ABA problem due to bitwise comparison.[35][36]
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 synchronization. This operation atomically sets the flag to true and returns the prior value, ensuring visibility 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 test-and-set 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.[36]
Another key addition is std::atomic_ref<T>, which provides atomic operations on a reference 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, store, exchange, 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
}
#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.[37]
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 false sharing on cache lines, typically 64 bytes on modern hardware. Programmers can use it with alignas to pad atomic 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;
};
#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.[38]
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 sequential consistency for memory_order_seq_cst in a total order (S) consistent with modification orders. These changes address prior ambiguities in release sequences, allowing acquire operations to synchronize with releases via any member of the sequence, enhancing portability across architectures.[39][40]
The Format Library in C++20 provides std::format as a modern, type-safe facility for generating formatted strings, drawing from the design of Python's str.format and the {fmt} library to offer an extensible replacement for legacy functions like printf and sprintf.[41] This library resides in the <format> header and supports positional or automatic argument binding through curly brace placeholders {} in the format string, enabling concise and readable code without manual type conversions or buffer overflows.[41] For instance, the expression std::string greeting = std::format("Hello, {}!", "world"); produces the string "Hello, world!", with automatic deduction of the argument type for insertion.[41]
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 hexadecimal (x) or fixed-point notation (f).[41] 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.[41] For user-defined types, customization occurs via specialization of the std::formatter<T> template, allowing parse and format functions to handle type-specific logic while leveraging the library's core machinery.[41] Invalid format strings or mismatched arguments trigger a std::format_error exception at runtime, ensuring robustness without the undefined behavior common in C-style formatting.[41]
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.[42] 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).[42] Alignment, width, and fill apply to chrono outputs as well, such as {0:>20%Y-%m-%d} for padded dates.[42]
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.[41] Its compile-time type checking prevents mismatches that could lead to runtime errors in variadic functions like printf, promoting safer code without sacrificing expressiveness.[41]
Other Library Additions
C++20 introduces several enhancements to the standard library beyond the major updates to ranges, chrono, atomics, and formatting, focusing on concurrency primitives, mathematical utilities, bit manipulation, 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.[43] 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.[43] 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
}
#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.[44] 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();
}
}
#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();
}
#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.[45][46]
The mathematical utilities in <cmath> are expanded with std::midpoint(a, b), which computes the arithmetic mean while avoiding overflow for integers and pointers, and std::lerp(a, b, t), a linear interpolation function that ensures monotonicity for floating-point types even when t is exactly 0 or 1.[47] These functions address common numerical issues in graphics, simulations, and algorithms requiring precise interpolation, 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
#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.[48] 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
#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 debugging and logging, std::source_location offers a lightweight way to capture source code details like file name, line number, and function without macros or __FILE__/__LINE__, populated at compile-time or via a current() function at runtime.[49] 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";
}
#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 lightweight array 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 arrays, 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)
}
#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 APIs handling buffers or collections.[50]
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 concepts, 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.[13]
The keyword [concept](/page/Concept) is used to define a named constraint on template parameters, enabling more expressive and readable template programming by specifying requirements on types. For example, a concept 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>;
};
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) { /* ... */ }
template<typename T>
requires std::[integral](/page/Integral)<T>
void process(T value) { /* ... */ }
This keyword is integral to the concepts facility, promoting safer and more maintainable generic programming.[13]
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; }
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 UTF-8 character representation, providing explicit support for 8-bit Unicode characters and string literals prefixed with u8. This addresses previous ambiguities in handling UTF-8 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 compile time, enhancing metaprogramming 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)
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 runtime, 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
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 global one. This extends C++11's inline namespaces for better library versioning:
cpp
namespace A::inline B::C { /* declarations */ }
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 namespace visibility.[51]
The using keyword is extended with using enum declarations, which import all enumerators from a scoped or unscoped enumeration 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
}
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 namespace.[52]
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.[53]
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.[4][13]
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 exception handling. The changes aim to streamline the language by eliminating rarely used or problematic elements while maintaining backward compatibility during a transition period.[54]
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-threaded programming. Specifically, volatile-qualified arithmetic operations on atomic 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 deprecated. This addresses longstanding issues where volatile was misused for thread synchronization, 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 runtime bugs in concurrent code by flagging such patterns at compile time.[55]
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.[54]
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 draft 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 library 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.[56] These headers, introduced for backward compatibility, 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.[56] 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 standard library, 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.[56] 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.[56] 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 exception safety via noexcept and resource management 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.[56]
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 Microsoft, 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.[57]
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 Toronto in July 2017.[57] Initial attempts to merge it directly into the C++20 working draft occurred at meetings in Rapperswil (June 2018) and San Diego (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 Kona meeting in February 2019, merging the TS (as N4775) into the working draft N4800 with targeted refinements.[14]
At its core, the Coroutines TS defines a stackless coroutine model, where execution suspends by returning control to the caller without preserving a separate call stack, instead transforming the coroutine body into a state machine that manages local state in heap-allocated frames.[57] Key elements include the promise type, which encapsulates the coroutine's state and behavior—such as handling returns, yields, and exceptions via methods like return_value() and unhandled_exception(); awaitables, objects returned by co_await expressions that define suspension and resumption logic through await_ready(), await_suspend(), and await_resume(); and keywords (co_await, co_yield, co_return) that trigger these operations within coroutine functions.[57] 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 handle) from the promise type, allowing greater customization via template specialization.[14] Unlike the TS, where the return type directly served as the promise type, C++20's traits-based approach supports more flexible library designs, such as generators or tasks, while maintaining backward compatibility for TS implementations.[14]
Prior to standardization, the TS enabled experimental adoption through vendor implementations, including Microsoft Visual C++ since version 14.0 (2015 SP2) and Clang since version 5 (2017), allowing developers to test coroutine patterns in real-world scenarios like asynchronous I/O and parallel simulations.[14] These early supports, based on evolving drafts, facilitated feedback that refined the final C++20 feature set without introducing breaking changes.[14]
Concepts TS
The Concepts Technical Specification (TS), formally known as ISO/IEC TS 19217:2015, introduced extensions to the C++ programming language for constraining template parameters using named Boolean predicates, known as concepts, which specify requirements on types and other template arguments.[58] These concepts served as compile-time checks to ensure that template instantiations met specified syntactic and semantic criteria before substitution, improving error diagnostics and enabling better generic programming. 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 concept definitions.[59] The TS also proposed advanced features like concept maps, which allowed implicit adaptations of types to satisfy concept requirements (e.g., mapping a pointer type to an iterator concept 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.[60]
The evolution of concepts from the TS to C++20 involved significant simplification to prioritize usability and compiler feasibility, resulting in the adoption of "lightweight" concepts that omitted axioms and concept maps in favor of more straightforward requires clauses for expressing constraints directly on templates.[60] 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 Concepts TS directly influenced C++20's standardized concepts by providing the foundational design and terminology, enabling their integration into the core language and library without the full overhead of the original proposal.
Prior to C++20, concepts from the TS were experimentally implemented in libraries such as Boost.ConceptCheck, which emulated concept verification using macros and templates to enforce requirements at compile time in pre-standard C++ codebases. These tools demonstrated practical benefits in generic libraries, paving the way for formal adoption, and the Concepts TS itself was referenced in other specifications like the Ranges TS to define iterator and range abstractions.[61]
Ranges TS
The Ranges Technical Specification, designated ISO/IEC TS 21425:2017, was published in November 2017 by the International Organization for Standardization (ISO) and the International Electrotechnical Commission (IEC).[62] This document outlined extensions to the C++ standard library to support operations on ranges of data, serving as the precursor to the full integration of the ranges library into C++20.[63] 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 data processing in C++.[62]
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.[64] 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 iterators.[64] These elements, including new components like counted_iterator, common_iterator, and the unreachable sentinel, facilitate more efficient and expressive iteration patterns.[64]
For inclusion in C++20, the specification underwent refinements, such as finalizing the pipe syntax with the | operator to chain views and adaptors—e.g., transforming a range 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.[64]
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.[64]
Other Relevant TS
The Library Fundamentals Technical Specification Version 3 (drafted in 2019) introduced several foundational library components prototyped for potential integration into the C++ standard, influencing C++20 by providing refined utilities for safer and more expressive code. Notably, it specified std::source_location, a lightweight structure for capturing source code details such as file name, line number, and function name at compile time, 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.[65][66]
The Parallelism Technical Specification Version 2 (ISO/IEC TS 19570:2018) extended the parallel execution model from earlier versions, focusing on advanced policies for standard algorithms to leverage vectorization 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 library by validating execution policy mechanisms for future enhancements in threading and GPU offloading.[67]
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.[68][69]
The mathematical constants in the <numbers> header (e.g., std::numbers::pi_v<float>) were added in C++20 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 special functions (which were addressed separately in ISO/IEC 29124:2010 for C++17).[70]
Collectively, these Technical Specifications functioned as iterative prototypes, enabling the C++ committee to refine library features through community feedback and implementation 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 timeline 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 reflection but defers full pattern matching to future standards.[71][72]
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.[73] These contracts would enable runtime or compile-time verification of function behaviors, enhancing code reliability by enforcing expected conditions at entry and exit points.[73]
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 contracts seamlessly with C++'s existing exception system.[74] These issues stemmed from a lack of implementation experience and consensus on how violations should propagate without introducing undefined behavior or complicating standard library adoption.[74]
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 evaluation semantics—ignore (no runtime check), observe (check without termination), enforce (check and terminate), and quick_enforce (fast termination)—where runtime checks are not enabled by default and can be controlled via implementation-defined modes.[75] This iteration removes complexities like support for virtual functions and procedural assertions from the original design, prioritizing compile-time documentation and optional runtime enforcement.[75]
In the absence of contracts in C++20, developers can approximate some benefits using static_assert for compile-time checks or concepts for template constraints, though these lack the full runtime verification and function-level assertions of contracts. The feature's intended impact is to promote self-documenting code that is easier to verify and maintain, reducing bugs through explicit behavioral guarantees without relying solely on external testing.[73]
Reflection
The reflection proposal for C++20, detailed in P1240R2, aimed to introduce compile-time introspection 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 compile time, such as retrieving member names and types via metafunctions like name_of and members_of.[76] The proposal also included splicing mechanisms with syntax like [:refl:] to inject reflected information directly into source code, facilitating code generation without runtime overhead.[76]
Key planned features encompassed generating metaprogramming code from reflections, such as iterating over class members to produce serialization functions or template specializations. For instance, a struct's members could be enumerated at compile time to auto-generate equality operators or JSON converters. Use cases included serialization libraries that avoid manual boilerplate or macro-based solutions, and GUI builders that dynamically create interface code from type definitions.[76] These capabilities were envisioned to integrate with modules for scoping reflections to specific translation units, enhancing modularity in large projects.[76]
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.[77] By November 2025, the feature has been adopted for C++26 with a limited implementation via P2996R12, introducing partial constexpr reflection features like basic type and member queries but omitting full splicing and expression reflection for future iterations.[78][79] This scaled-back approach allows initial adoption for metaprogramming tasks while addressing earlier concerns about complexity.[78]
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, variants, and user-defined structures, building on existing facilities like structured bindings.[80] 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 visitor patterns for handling variant types.[80] 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.[80]
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";
}
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: ....[80] 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.[80] The proposal emphasized interoperability with emerging features like std::variant, allowing patterns to match held types and extract values directly.[80]
Despite positive feedback from the Evolution Working Group, pattern matching was deferred from C++20 due to semantic ambiguities in pattern resolution, particularly overlaps with variant handling and the need for clearer integration with structured bindings, as discussed in committee meetings leading to the standard's finalization.[81] The decision, made late in the C++20 cycle, prioritized completing core features like modules and coroutines over this more experimental addition.[81] Ongoing work has evolved the design, with later proposals like P2688R5 exploring a match expression for single-pattern boolean results, but it was not included in C++26 and is now targeting a future standard such as C++29.[82]
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 deconstruction that hints at pattern matching'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.[80] Overall, pattern matching promises to make C++ code more concise and less error-prone for algebraic data handling, akin to features in languages like Rust or Swift, once standardized.[80]
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 GCC 15 and Clang 18. Developers can utilize the standard with flags like -std=c++20 in GCC and Clang, 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.[83][4][84][85]
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.[4][83][86]
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 Clang 15 (2023), coroutines remain partial depending on target. Experimental modules from Clang 8 progressed to stability by Clang 15.[84][83]
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 Visual Studio 2022 (MSVC 19.30+) and VS 2026 previews as of 2025.[85][83]
EDG-based compilers, including Intel oneAPI DPC++/C++ Compiler (versions 2023.1+), provide substantial C++20 support with -std=c++20, aligning closely with Clang; modules partial in ICX 2023.2+. For detailed feature status, including NVIDIA HPC, see community tables.[83][87]
| Compiler | Full C++20 Support Version | Modules Complete Version | Primary Flag | Notes on 2025 Status |
|---|
| GCC | 11 (2021, non-experimental in 15, 2025) | 11+ | -std=c++20 | Substantial; production-ready, bugs mostly resolved.[4] |
| Clang | Substantial from 10 (near-full in 18, 2025) | 15+ (2023) | -std=c++20 | Partial coroutines; stable overall.[84] |
| MSVC | 19.28+ (VS 2019 16.10, 2021) | 19.28+ | /std:c++20 | Full; integrated in VS 2026 previews.[85] |
| Intel (EDG/Clang-based) | Substantial 2023.1+ | Partial 2023.2+ | -std=c++20 | Aligns with Clang 18; modules ongoing.[87] |
Library implementation gaps, such as partial ranges in some libraries, are addressed separately but do not impact core language. As of November 2025, GCC 15 removes C++20 experimental status.[83]
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 Clang, supports ranges fully since LLVM 15 (2022, P0896R4). Chrono extensions (P0355R7) remain partial, with core features available; ongoing work in LLVM 18+. Format 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.[88][83]
libstdc++ supports C++20 library features from GCC 11 (2021), including layout-compatibility (P0466R5). Time zones in chrono fully implemented since GCC 11. Ranges full since GCC 12 (2022), format since GCC 11. Full support achieved in GCC 11, with refinements for C++26 in GCC 15.[4][89][83]
MSVC STL completed core C++20 implementations by Visual Studio 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.[90][83][85]
| Library | Ranges | Chrono (incl. Time Zones) | Format | char8_t Support | Notes |
|---|
| libc++ (Clang/LLVM) | 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.[83][88] |
| libstdc++ (GCC) | 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.[83][4][89] |
| MSVC STL | Full (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.[83][90] |
Platform adaptations vary; Android NDK 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 GCC 15, Clang 18, MSVC 17.14 in 2025.[91][92][83][93]
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 Jacksonville, Florida, where initial goals were established, including prioritization of features like modules and concepts, and the overall schedule for the C++20 cycle was unanimously approved.[94] Working papers such as P0734R0, which proposed extensions for concepts to enable better template metaprogramming and generic programming, 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).[95] The onset of the COVID-19 pandemic shifted the 2020 meetings to virtual formats, allowing continued progress despite logistical challenges.[96]
A pivotal milestone occurred at the February 2020 meeting in Prague, where the committee declared C++20 feature-complete and advanced working draft N4860 (dated March 31, 2020) for international ballot.[7] 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.[97]
The International Standard was formally published as ISO/IEC 14882:2020 in December 2020.[2] Post-publication, the committee processed defect reports through 2023, with resolutions incorporated into subsequent standards such as C++23. The standard was withdrawn in 2024 upon the publication of its successor, C++23 (ISO/IEC 14882:2024).[1] The entire active development phase for C++20 lasted approximately three years, from the 2018 Jacksonville meeting to the 2020 publication.[8]
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 February 2019 at the Kona 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.[77] 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 toolchain overhauls.[77]
In contrast, the committee deferred the inclusion of contracts—a proposed mechanism for specifying preconditions, postconditions, and assertions at function boundaries—during the July 2019 Cologne meeting. The rejection stemmed from persistent disagreements on enforcement models, runtime overhead, diagnostics, and integration with existing debugging tools, which risked complicating the standard's timely delivery.[74] 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 C++23 or beyond.[74]
For the ranges library, a cornerstone 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 pipeline paradigms in other languages.[98] This choice, finalized as part of the ranges adoption in June 2018 at Rapperswil and refined in subsequent polls, enabled composable, lazy-evaluated data processing expressions such as numbers | std::views::filter(is_odd) | std::views::transform(square), promoting safer and more efficient algorithm usage without mutable iterators.[98] 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 operators (<, >, <=, >=, ==, !=) under a single, synthesizable mechanism.[15] 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 boilerplate code and preventing inconsistencies in mixed comparisons, as seen in pre-C++20 libraries.[15] 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. Herb Sutter, 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.[77] Bjarne Stroustrup, 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.[10]