The C++ standards committee (ISO/IEC JTC1/SC22/WG21, or WG21 to its friends) met in February to approve the final set of changes to the upcoming C++23 standard. The meeting was in Issaquah, in Washington, USA, and was the second time the committee had met in person since the pandemic. Like the meeting last November, this was a "hybrid" meeting, with in-person attendance but a large number of people (including me) attending virtually via video conference.
Now that the contents of the C++23 are finished, we're waiting for the new standard to be published sometime later this year. The committee has successfully delivered a new revision of the C++ standard on time every three years since 2011, but this one has some special significance for me because I've been chairing WG21's Library Working Group (LWG) since we finished C++20 three years ago. I'm proud to have been able to help coordinate some of the great work that has gone into the new C++ standard, especially when we suddenly had to stop holding our regular meetings in 2020 and adopt new working practices.
There are lots of new features in the C++23 standard library, but in this post, I will show a few examples of ones I think are particularly interesting. You might notice a theme: many of the new features are building on new features of C++20. Several of the features described below are smoothing away rough edges or filling in missing pieces of the features added to C++20.
New feature highlights
Building on the modules support added to C++20, there is now a std
module for the standard library. This allows the entire standard library to be imported by using 'import std;
' -- this not only means you don't need to remember which header files to include for each component you use but importing the entire library should compile much faster than including and preprocessing individual headers.
Working with ranges
Building on the <ranges>
library introduced in C++20, there are many other new views and new algorithms for working with ranges; for example, views::zip
and views::enumerate
. The zip view of those takes any number of separate views and combines them into a single view yielding elements of type tuple<T&...>
, i.e., a tuple of references to the elements of the individual views. The enumerate view takes a range of elements and produces a view that yields elements of type tuple<integer-type, T>
such that each element of type T
from the input range is paired with its index in the range. There are several more new views that yield different permutations of ranges, such as zip_transform
, adjacent
, adjacent_transform
, join_with
, chunk
, chunk_by
, slide
, and cartesian_product
.
New <print> header
Building on the <format>
library introduced in C++20, the new <print>
header provides convenient and type-safe printing to files and iostreams. In C++20 the classic "Hello, world!" program could be written in C++ as std::cout << std::format("Hello, {}!\n", "world");
. But with C++23, you can simply write std::println("Hello, {}!", "world")
, which will both format the string and write it to standard output. The std::format
and std::print
functions also support more types than in C++20, including ranges (such as std::map
, std::vector
, and views such as the result of views::zip
) and thread IDs and stack traces (which are another new addition in C++23).
Error handling
We also added discriminated union type, std::expected<T, U>
, which holds either a result value or an error value. This allows a systematic approach to error handling that combines the best aspects of error codes and exceptions. You can think of an expected<T, U>
object as being similar to optional<T>
except instead of being empty when the expected T
value, it contains an "unexpected" value of type U
. This can be an error code (like the value of errno
), an std::error_code
, or anything representing the error that meant a T
result could not be produced.
Here is a quick demo showing some of these new features:
import std; std::expected<std::vector<int>, std::error_code> get_data(); int main() { try { auto indexed_vals = get_data().value() | std::views::enumerate; std::println("{}", indexed_vals); } catch (const std::bad_expected_access<std::error_code>& e) { std::println(std::cerr, "Failed to get data: {}: {}", e.error().category().name(), e.error().message()); } }
This will either print the vector with the index of each value, like "[(0, v0), (1, v1), (2, v2), ...]"
or, if the get_data()
function failed to produce a vector, calling the value()
member function will throw an exception and then an error will be printed.