Chapter 11: C++23

If C++20 was another "major" release in the spirit of C++11, C++23 is more of an incremental update focused on consolidation and polish: it completes some features that were rushed into C++20 and adds many long-requested library facilities. This chapter introduces a selection of its more important and practical features.

The examples in this chapter use C++23 features and must be compiled with -std=c++2b (or -std=c++23). In addition, depending on the progress of each standard-library implementation, some library features (such as std::generator, std::move_only_function, <stacktrace>, and import std;) may not yet be available on your toolchain; this is noted where relevant.

11.1 Language features

Deducing this (explicit object parameter)

Before C++23, making a member function serve const/non-const and lvalue/rvalue cases at once usually meant writing several nearly identical overloads. C++23 introduces an explicit object parameter, allowing this to be written as the function's first parameter and its cv-/ref-qualification to be deduced via a template, so a single function template covers every case:

struct Counter {
int value = 0;
template <typename Self>
auto&& get(this Self&& self) { // self is the former *this
return self.value;
}
};

This also makes previously awkward patterns, such as recursive lambdas, natural to write.

if consteval

C++23 introduces if consteval, which distinguishes within a function body whether the current context is constant evaluation, letting you choose different implementations for compile time and run time:

constexpr int compute(int x) {
if consteval {
return x * 2; // taken during constant evaluation
} else {
return x * 3; // taken at run time
}
}

Multidimensional subscript operator

C++23 lets operator[] take multiple subscript arguments, so multidimensional containers can use the intuitive m[i, j] syntax instead of falling back to operator():

struct Matrix2x3 {
int data[6] = {};
int& operator[](std::size_t r, std::size_t c) { return data[r * 3 + c]; }
};

Matrix2x3 m;
m[1, 2] = 7;

auto(x) and static operator()

C++23 provides the auto(x) / auto{x} syntax to explicitly produce a decay-copy — a prvalue copy of the same type as x — which is handy when you specifically want a copy:

std::vector<int> v{1, 2, 3};
auto copy = auto(v); // explicit copy, a prvalue

In addition, the call operator of a function object (including a lambda) can now be declared static, dropping the implicit object parameter, which can improve performance in some scenarios:

struct Add {
static int operator()(int a, int b) { return a + b; }
};

[[assume]]

[[assume(expr)]] is a C++23 standardized attribute that tells the compiler an expression is guaranteed to be true at that point, allowing the optimizer to take advantage of it. Note that if the assumption does not actually hold at run time, the behavior is undefined, so use it with care:

int divide_by(int x) {
[[assume(x > 0)]]; // promise the optimizer x is positive
return 100 / x;
}

Other small language improvements

C++23 also includes several smaller language improvements:

11.2 The standard library

std::expected

std::expected<T, E> represents a result that is "either a value T or an error E", providing a type-safe, expressive way to handle errors without exceptions, similar to the Result type in other languages:

#include <expected>

std::expected<int, std::string> parse_positive(int x) {
if (x > 0) return x;
return std::unexpected("not positive");
}

auto r = parse_positive(42);
if (r) {
// use *r
} else {
// use r.error()
}

std::print and std::println

C++23's <print> provides std::print and std::println, which build on C++20's std::format to produce output via a type-safe format string — more concise and more efficient than the traditional chained iostream <<:

#include <print>

std::println("Hello, {}!", "C++23");
std::println("{} + {} = {}", 1, 2, 1 + 2);

std::mdspan

std::mdspan is a non-owning multidimensional view over contiguous storage. It does not allocate; it merely reinterprets the layout of an underlying one-dimensional array according to the given extents, and is widely used in scientific and high-performance computing:

#include <mdspan>

std::array<int, 6> storage{1, 2, 3, 4, 5, 6};
std::mdspan m(storage.data(), 2, 3); // view it as 2 x 3
int x = m[1, 2]; // together with the multidimensional subscript

std::flat_map and std::flat_set

std::flat_map / std::flat_set are associative containers backed by sorted contiguous storage (by default two vectors). Compared with the red-black-tree-based std::map, insertion is slower, but lookup and iteration are more cache-friendly and the memory overhead is smaller:

#include <flat_map>

std::flat_map<int, const char*> m;
m.insert({3, "three"});
m.insert({1, "one"});
// iterated in sorted key order: 1, 3

Ranges additions

C++23 added many useful range adaptors, such as views::zip (iterates several ranges in lockstep, element by element), views::enumerate (attaches an index to each element), views::chunk, views::slide, and views::join_with, as well as the general-purpose std::ranges::to (materializes a range into a concrete container):

#include <ranges>

std::vector<std::string> names{"a", "b", "c"};
std::vector<int> scores{90, 80, 70};
for (auto&& [name, score] : std::views::zip(names, scores)) {
// name and score correspond one to one
}

Other improvements

C++23 also includes many small but practical improvements, for example:

11.3 A note on library support

Most C++23 language features are already well supported by mainstream compilers, but the availability of some library features varies by implementation. For instance, std::generator (coroutine ranges), std::move_only_function, <stacktrace>, and the standard-library module import std; may not yet be implemented, or may still be experimental, in some standard libraries (especially libc++). Before using these features, check the cppreference compiler support page to confirm the support status of your toolchain.

Conclusion

Although C++23 does not bring the disruptive changes of C++11 or C++20, features such as std::expected, std::print, std::mdspan, and the explicit object parameter genuinely improve the day-to-day experience of writing C++. Together with the upcoming C++26, modern C++ continues to evolve.

Further Readings