C++23 Feature Freeze: Summer ISO WG21 Meeting Results

Antony Polukhin
Yandex
Published in
6 min readAug 17, 2022

Hi! This is Antony Polukhin, a member of the ISO WG21 C++ Committee, an organization that controls the development of the C++ programming language. The committee holds a meeting three times a year, each time in a new city worldwide. During the sessions, we consider proposals for changes in the language. I help local C++ developers draw up proposals and promote their ideas: anyone can offer an original solution. Here, in the Yandex blog on Medium, I’ll talk about each new meeting of the committee and the decisions taken regarding the future of the language.

I’ll start with an overview of the meeting that took place in July. It proved to be a major breakthrough, resulting in a number of new features for the C++23 draft:

  • std:mdspan
  • std:flat_map
  • std:flat_set
  • freestanding
  • std:print("Hello {}", "world")
  • formatted ranges output
  • constexpr for bitset, to_chars/from_chars
  • std::string::substr() &&
  • import std;
  • std::start_lifetime_as
  • static operator()
  • [[assume(x > 0)]]
  • 16- and 128-bit floats
  • std::generator
  • and so much more

std::mdspan

Since adopting the multidimensional opertator[] at the last meeting, implementing std::mdspan has gotten much easier. And here’s the result — a non-owning multidimensional array:

It can even work with other programming languages out of the box. For example, in its third template parameter, std::mdspan can take one of several predefined layout classes:

  • std::layout_right: Layout style for C or C++, rows are zero-indexed.
  • std::layout_left: Layout style for Fortran or Matlab, columns are zero-indexed.

You can find all the details in document P0009. The authors promised to provide a lot of examples of the new std:mdspan in the near future.

std::flat_map and std::flat_set

Those wonderful flat_* containers from Boost are now available in the C++ standard. They’re extremely efficient when working with small amounts of data. Under the hood, “flat” containers store data in a sorted array, which significantly reduces dynamic allocations and improves data locality. Despite the O(log N) search complexity and worst-case O(N) insert complexity, flat containers outperform std:unordered_map when working with small amount of elements.

During the standardization process, flat_* containers were actually made adapters; that way, programmers could use their own containers for the underlying implementation:

An interesting point is that the standard, unlike the Boost implementation, stores keys and values in separate containers. That improved key locality makes it faster to search flat container.

The full std::flat_set interface is described in P1222, while the std:flat_map interface description is in P0429.

Freestanding

The C++ standard says it’s possible to implement the standard library as hosted or freestanding. hosted implementation requires operating system support and has to implement all the methods and classes from the standard library. freestanding can work without the OS, the hardware doesn’t matter, and it doesn’t contain some of the classes and functions.

Until recently, there was no description for freestanding, and different hardware manufacturers provided different parts of the standard library. That made porting code harder and undermined the popularity of C++ in embedded environments.

It’s time to change that! P1642 marked which parts of the standard library are mandatory for freestanding.

std::print

Methods from the popular fmt library were added in C++20. The library turned out to be so convenient and fast that programmers started using it almost everywhere in their code, including for formatted output:

std::cout << std::format(“Hello, {}! You have {} mails”, username, email_count);

But code like this isn’t perfect:

  • Superfluous dynamic allocations.
  • std::cout trying to format already formatted lines.
  • No Unicode support.
  • Code that increases the size of the resulting binary file.
  • A less-than-attractive look.

All those problems were resolved by adding std::print methods:

std::print(“Szia, {}! {} új üzeneted”, username, email_count);

You can find details, benchmarks, and options for using std::print with FILE* and streams in P2093.

Formatted output of value ranges

Thanks to P2286, std::format (and std::print) can now output ranges of values regardless of whether they’re in a container or presented by std::ranges::views::*:

constexpr

There’s great parsing news for developers working with different libraries: std::to_chars/std::from_chars can now be used at the compilation stage to convert integer values from text to binary. This should also come in handy when developing DSL. At Yandex Go, we plan to start using it in the userver framework to check SQL queries at the compilation stage.

std::bitset also became constexpr, so working with bits at the compilation stage is now much easier.

Daniil Gocharov worked on std::bitset in P2417 and was joined by Alexander Karaev for std::to_chars/std::from_chars P2291. Many thanks to them for a job well done!

import std;

The first full-fledged module was added to the standard library. The entire library can now be imported with one line: import std;. Builds can be up to 11 times (sometimes even 40 times!) faster if the entire standard library module is imported instead of including the header files. You can view the benchmarks in P2412.

If you’re used to mixing C++ and C as well as using C functions from the global namespace, the std.compat module is for you. Importing it gives you all the functions from C header files, like ::fopen and ::isblank, as well as the contents of the standard library.

Despite all this, P2465, which covers the new modules, actually isn’t that long.

std::start_lifetime_as

Timur Doumler and Richard Smith put together a wonderful gift for all developers working on embedded and high-load applications. Now all you need for everything to work is this:

In other words, you can convert different buffers to structures and work with them without reinterpret_cast, copying data, or the risk of your program acting up. Everything is described and documented in P2590.

16- and 128-bit floats

The C++ standard now includes std::float16_t, std::bfloat16_t, std::float128_t, and aliases for existing numbers with a floating point: std::float32_t, std::float16_t.

16-bit floats help when you’re working with video cards or in machine learning. For example, float16.h could benefit from the new short float types. 128-bit floats are best for scientific calculations involving large numbers.

P1467 describes macros for checking compiler support for new numbers, and there’s even stdfloat.properties, a comparison table with a description of mantissa and exponent sizes in bits.

std::generator

When coroutines were accepted into the C++20 standard, the idea was that they could be used for creating “generators”: functions that would remember their state between calls and return new values based on that state. In C++23, std::generator was added as a new class that lets you easily create your own generators:

You can see in the example how well generators work with ranges. std::generator is efficient and secure. The code that looks like it generates a hanging link is actually absolutely valid and doesn’t cause any issues:

You can find examples and descriptions of how it works and the reasoning behind the interface in P2502.

Pleasant surprises

The standard string class got an overhaul for the substr() method for rvalue references: std::string::substr() &&. Code like this…

…now works without extra dynamic allocations. You can find more information in P2438.

Thanks to P1169, you can now declare operator() as static, which is perfect for creating CPOs for ranges in the standard library:

In addition to std::start_lifetime_as, Timur Doumler came up with an excellent hint for the optimizer [[assume(x > 0)]]. You can now give hints to the compiler about possible values for numbers and other invariants. Some of the examples and benchmarks in P1774 show a fivefold reduction in the number of assembly instructions.

Other

The standard also got a lot of minor edits, bug fixes, and improvements. In some places, move constructors started being used instead of copy constructors (P2266). Luckily for driver developers, some volatile operations are no longer deprecated (P2327 with a bugfix in C++20). operator<=> breaks old code less often (P2468), Unicode characters can now be used by name (P2071), and compilers are all generally required to support Unicode (P2295). New algorithms for ranges (ranges::contains P2302, views::as_rvalue P2446, views::repeat P2474, views::stride P1899, and ranges::fold P2322) and std::format_string were added to do compile-time checks for std::format (P2508) and #warning (P2437). Ranges learned how to work with move-only types (P2494). And finally, std::forward_like was added for forwarding variables based on the type of another variable (P2445).

Summary

For a long time, it seemed like the most significant innovation C++23 brought to the table was adding std::stacktrace from RG21, though the last meeting saw the addition of many long-awaited features. There are innovations for embedded developers, chemists/physicists/mathematicians/…, developers of machine learning libraries, and even developers working on high-load applications.

--

--

Antony Polukhin
Yandex
Writer for

ISO WG21 C++ Committee Member, National Body; Boost C++ libraries developer; author of the “Boost C++ Application Development Cookbook”. Developer of userver