Last Update:
C++23 Library Features and Reference Cards
Table of Contents
In this blog post, you’ll see all C++23 library features! Each with a short description and additional code example.
Prepare for a ride!
For language features please see the previous article: C++23 Language Features and Reference Cards - C++ Stories
This article is “in progress”, most topics are completed, but few sections are in the “to-do” state.
- 10th Dec: Explicit Lifetime Management
- 11th Dec:
aligned_storage
deprecating
Want your own copy to print?
If you like, I prepared PDF I packed both language and the Standard Library features. Each one has a short description and an example if possible.
All of the existing subscribers of my mailing list have already got the new document, so If you want to download it just subscribe here:
Please notice that along with the new ref card you’ll also get C++20 and C++17 language reference card that I initially published three years ago. With this “package” you’ll quickly learn about all of the latest parts that Modern C++ acquired over the last few years.
New Headers
expected
flat_map
,flat_set
generator
mdspan
print
spanstream
stacktrace
stdfloat
Stacktrace Library
Based on Boost.Stacktrace allows for more context when debugging code. The library defines components to store the stacktrace of the current thread of execution and query information about the stored stacktrace at runtime.
#include <iostream>
#include <stacktrace>
void foo() {
std::cout << std::stacktrace::current();
}
int main() {
[] {
foo();
}();
}
Experiment @Compiler Explorer, note that GCC 14 requires to be linked with -lstdc++exp
.
Possible output on GCC:
0# foo() at /app/example.cpp:5
1# operator() at /app/example.cpp:10
2# main at /app/example.cpp:11
3# at :0
4# __libc_start_main at :0
5# _start at :0
is_scoped_enum
& to_underlying
P1048R1 (is_scoped_enum
), P1682R3 (to_underlying
)
#include <print>
#include <utility> // for std::to_underlying
enum class Color: uint8_t { Red = 1, Green = 2, Blue = 3 };
enum Col { Red, Green, Blue };
int main() {
Color c = Color::Blue;
// Pre-C++23: verbose casting
auto old_way = static_cast<std::underlying_type_t<Color>>(c);
// C++23: clean and simple
auto new_way = std::to_underlying(c);
std::println("Color value: {}", new_way);
std::println("is_scoped_enum Color {}", std::is_scoped_enum_v<Color>);
std::println("is_scoped_enum Col {}", std::is_scoped_enum_v<Col>);
}
The trait (underlying_type
) has been available since C++11.
Bonus: First notes and ideas about to_underlying
appeared in Scott Meyers’ book: Effective Modern C++: 42 Specific Ways to Improve Your Use of C++11 and C++14 @Amazon.
std::string
/std::string_view
Improvements
contains(char/string_view/const char*)
- member function- Prohibiting
std::basic_string
andstd::basic_string_view
construction fromnullptr
- Range constructor for
std::basic_string_view
- P1989 string::resize_and_overwrite()
- P1072R10 - Allows us to initialize/resize strings without clearing the buffer but filling bytes with some user operation.
See the example below:
#include <string>
#include <string_view>
#include <vector>
#include <print>
int main() {
// 1. contains() example
std::string str = "Hello C++23 World";
std::println("{}", str.contains("C++23"));
std::println("{}", str.contains('X'));
// 2. resize_and_overwrite() example
std::string numbers {"xyz"};
numbers.resize_and_overwrite(5, [](char* buf, std::size_t n) {
for (std::size_t i = 1; i < n; ++i) {
buf[i] = '0' + i;
}
return n;
});
std::println("Numbers: {}", numbers);
// 3. string_view range constructor
std::vector<char> chars = {'H', 'e', 'l', 'l', 'o'};
std::string_view sv(chars.begin(), chars.end());
std::println("View: {}", sv);
}
Play @Compiler Explorer
std::out_ptr()
, std::inout_ptr()
,
Functions that wrap a smart pointer into a special type allowing to pass to low-level functions that require pointer-to-pointer parameters.
void lowLevel(int** pp) { if (pp) *pp = new int{42}; }
auto ptr = std::make_unique<int>(10);
lowLevel(std::inout_ptr(ptr));
Handy for interaction with C-style API like WindowsAPI, DirectX, Media libraries. inout_ptr
additionally calls .release()
on the pointer.
#include <memory>
#include <utility> // for out_ptr/inout_ptr
#include <print>
// Simulate C-style API functions
void AllocateResource(int** pp) {
*pp = new int{42};
}
void ModifyResource(int** pp) {
if (*pp) {
**pp = 100;
}
}
void CleanupResource(int* p) {
delete p;
}
int main() {
// Example 1: out_ptr for new allocation
std::unique_ptr<int, decltype(&CleanupResource)> resource1(nullptr, CleanupResource);
AllocateResource(std::out_ptr(resource1));
std::println("Resource1 value: {}", *resource1);
// Example 2: inout_ptr for modifying existing resource
std::unique_ptr<int> resource2 = std::make_unique<int>(42);
std::println("Resource2 before: {}", *resource2);
ModifyResource(std::inout_ptr(resource2));
std::println("Resource2 after: {}", *resource2);
}
Play @Compiler Explorer
See another example: https://godbolt.org/z/KerKM7fM8
ranges::to<>
A way to build containers from a view:
auto v = iota('a') | take(10);
auto vec = v | std::ranges::to<std::vector>();
auto str = v | std::ranges::to<std::string>();
When a container has a reserve()
function, ranges:to
will also try to use it to make creation more optimal.
#include <algorithm>
#include <concepts>
#include <iostream>
#include <ranges>
#include <vector>
int main()
{
using namespace std::views;
auto v = iota('a') | take(10);
// new in C++23
auto vec = v | std::ranges::to<std::vector>();
for (auto x : vec)
std::cout << x;
auto vec2 = vec | std::views::reverse | std::ranges::to<std::vector>();
for (auto x : vec2)
std::cout << x;
}
Ranges Algorithms
ranges::starts_with()
andranges::ends_with()
ranges::iota()
,ranges::shift_left/right()
ranges::find_last()
,find_last_if()
,find_last_if_not()
ranges::contains()
andranges::contains_subrange()
- Ranges fold algorithms:
ranges::fold_*()
Views Additions
cartesian_product
repeat
enumerate
adjacent
,adjacent_transform
stride
slide
chunk
,chunk_by
join_with
zip
,zip_transform
as_rvalue
,as_const
For zip
you can see my other article: Combining Collections with Zip in C++23 for Efficient Data Processing - C++ Stories
Heterogeneous Erasure for Associative Containers
Continuation of the work for heterogeneous operations. This time you can use transparent comparators for erase()
and extract()
member functions. To be backward compatible, the comparators cannot be convertible to iterator
or const_iterator
of a given container.
Read more about heterogeneous access in my other article: C++20: Heterogeneous Lookup in (Un)ordered Containers - C++ Stories
Monadic Operations for std::optional
New member functions for optional: and_then
, transform
, and or_else
.
auto ret = userName.transform(toUpper)
.and_then([](string x) { return make_optional(x + "OK"); })
.or_else([] { return make_optional(string{"no user"}); });
For example:
#include <optional>
#include <print>
#include <algorithm>
#include <ranges>
void test(const std::optional<std::string>& userName) {
auto up = [](std::string x) {
std::ranges::transform(x, x.begin(), ::toupper);
return x;
};
auto ret = userName.transform(up)
.and_then([](std::string x) {
std::println("x: {}", x);
return std::optional<int>(x.size());
})
.or_else([]{
std::println("empty...");
return std::optional<int>{0};
});
std::println("ret is {}", *ret);
}
int main() {
test(std::nullopt);
test("john");
}
output:
empty...
ret is 0
x: JOHN
ret is 4
See my full article about this extension here: How to Use Monadic Operations for `std::optional` in C++23 - C++ Stories
<expected>
and Its Monadic Operations
A vocabulary type that allows storing either of two values: T
or unexpected
(in a form of some error type). It’s something between std::optional
and std::variant
.
enum class FuelErr { DistLarge, Neg };
std::expected<double, FuelErr> calcFuel(int dst) {
if (dst < 0) return std::unexpected(FuelErr::Neg);
return distance * 1.333;
}
C++23 also adds monadic operations for this type, so it’s consistent with operations for std::optional
.
See my mini-series about this type:
- Using std::expected from C++23 - C++ Stories
- Understand internals of std::expected - C++ Stories
- std::expected - Monadic Extensions - C++ Stories
- Function Composition and the Pipe Operator in C++23 – With std::expected - C++ Stories (guest post)
constexpr std::unique_ptr
The new()
operator can be used in constexpr
context since C++20, and now you can wrap it also in a unique_ptr
.
#include <numeric>
#include <memory>
constexpr int naiveSum(unsigned int n) {
auto p = std::make_unique<int[]>(n);
std::iota(p.get(), p.get()+n, 1);
auto tmp = std::accumulate(p.get(), p.get()+n, 0);
return tmp;
}
constexpr int smartSum(unsigned int n) {
return (n*(n+1))/2;
}
int main() {
static_assert(naiveSum(10) == smartSum(10));
static_assert(naiveSum(11) == smartSum(11));
}
std::mdspan
Multidimensional Span, P0009
A generalization over std::span
for multiple dimensions. Supports dynamic as well as static extents (compile-time constants). It also supports various mappings like column-major order, row-major, or even stride access.
#include <vector>
#include <https://raw.githubusercontent.com/kokkos/mdspan/single-header/mdspan.hpp>
#include <algorithm>
#include <iostream>
bool isSymmetric(std::mdspan<int, std::dextents<size_t, 2>> matrix) {
const auto rows = matrix.extent(0);
const auto cols = matrix.extent(1);
if (rows != cols) return false;
for (size_t i = 0uz; i < rows; ++i) {
for (size_t j = i + 1; j < cols; ++j) {
if (matrix[i, j] != matrix[j, i]) return false;
}
}
return true;
}
int main() {
std::vector<int> matrix_data = {1, 2, 3, 2, 4, 5, 3, 5, 6};
auto matrix = std::mdspan(matrix_data.data(), 3, 3);
std::cout << isSymmetric(matrix) << std::endl;
}
See at Compiler Explorer
And see my two bonus articles, available for Patreons, about this type:
std::mdspan
in C++23std::mdspan
in C++23 - 2 - ops and customizationstd::mdspan
in C++23 - 3 - Dynamic Programming
<flat_map>
and <flat_set>
Drop-in replacement for maps and sets with better performance characteristics. It gives faster lookup, faster iteration, random access iteration, less memory, and better cache efficiency. But iterators might be invalidated, and insertion is slower than the tree approach. The container is actually a container adaptor with proxy iterators.
Here’s a reference implementation: https://github.com/tzlaine/flat_map/blob/master/implementation/flat_map
Formatted Output Library <print>
New Hello World Style for C++23!
#include <print>
#include <string>
int main() {
std::string name = "C++23";
int year = 2024;
double version = 23.0;
// Basic printing
std::println("Hello from {}!", name);
// Named arguments
std::print("Language: {0}, Version: {0}\n", name);
// Multiple arguments with formatting
std::println("Release year: {:d}, Version: {:.1f}", year, version);
// Alignment and width
std::println("{:>10}: {:>5}", "Status", "OK"); // right-aligned
std::println("{:<10}: {:<5}", "Error", "None"); // left-aligned
// Print without newline and then with newline
std::print("Loading");
std::println("... done!");
}
New functions in the <print>
header: std::print
, std::println
(adds a new line) that uses std::format
to output text to stdout. Plus lower-level routines like vprint_unicode
with more parameters for output.
constexpr
to_chars()
, from_chars()
Integral versions available in constexpr
context:
#include <charconv>
#include <optional>
#include <string_view>
constexpr std::optional<int> to_int(std::string_view sv)
{
int value {};
const auto ret = std::from_chars(sv.begin(), sv.end(), value);
if (ret.ec == std::errc{})
return value;
return {};
};
int main() {
static_assert(to_int("hello") == std::nullopt);
static_assert(to_int("10") == 10);
}
See my article about this new feature and more example: C++ String Conversion: Exploring std::from_chars in C++17 to C++26 - C++ Stories
Standard Library Modules
C++23 introduces two standard library modules that significantly improve compilation efficiency and code organization:
import std;
- Imports all C++ standard library components in namespace
std
- Includes C++ headers and C wrapper headers
- Provides
::operator new
and related operators - Keeps the global namespace clean
- Ideal for new projects and modern C++ code
import std.compat;
- Provides everything from
import std;
- Additionally, imports C functions into global namespace
- Helps transition legacy code that uses unqualified C functions
- Useful when working with platforms where C functions are traditionally global
- Recommended for compatibility with existing codebases
Here are two examples:
import std;
int main() {
std::string str = "Hello";
std::size_t len = std::strlen(str.c_str()); // must use std::
std::println("Length: {}", len);
}
and the compat
version:
import std.compat;
int main() {
string str = "Hello"; // works: string is in std
size_t len = strlen(str.c_str()); // works: strlen is global
printf("Length: %zu\n", len); // works: printf is global
}
Unfortunately as of December 2024, no major compiler supports the above examples.
std::generator
Coroutine Generator
C++20 introduced coroutines, but the standard library support was minimal, leaving us to implement our own coroutine types or rely on third-party libraries. Synchronous generators are a crucial use case for coroutines, enabling efficient and lazy evaluation of sequences. Writing an efficient recursive generator is non-trivial, and the standard should provide one to simplify this task for us.
std::generator
fills this gap by providing a ready-to-use, standardized coroutine type for generating sequences. This makes it significantly easier for us to adopt coroutines in our projects without the overhead of implementing custom coroutine logic.
std::generator
is a coroutine-based feature that uses the co_yield
keyword to define a sequence of values. Each co_yield
call produces a value and suspends execution, allowing us to retrieve the value. When resumed, the coroutine continues from where it left off.
Here’s a simple generator:
std::generator<int> gen(int n) {
for (int a = 0; a < n; ++a)
co_yield a;
}
int main() {
auto g = gen(5);
auto it = g.begin();
while (it != g.end())
{
std::cout << *it << " ";
++it;
}
}
For more examples have a look at my two bonus articles for Patreons:
Explicit Lifetime Management
From the proposal:
Since C++20, certain functions in the C++ standard library such as
malloc
,bit_cast
, andmemcpy
are defined to implicitly create objects.
But when you use non standard techniques to obtain a memory for the object you might end up with UB:
C++23 introduces std::start_lifetime_as/start_lifetime_as_array
to explicitly start the lifetime of objects in raw memory, enabling well-defined behavior without invoking constructors. This is useful for low-level tasks like custom allocators or deserialization.
For example:
#include <memory>
struct X { int a, b; };
void example() {
void* rawMemory = My_Malloc(sizeof(X)); // non standard way...
X* obj = std::start_lifetime_as<X>(rawMemory); // Start lifetime
obj->a = 42; obj->b = 84; // Use the object
std::free(rawMemory); // Free memory
}
(as of December no major compiler implements this functionality)
<spanstream>
String-Stream with Span Buffers
New classes: basic_spanbuf
, basic_ispanstream
, basic_ospanstream
, basic_spanstream
analogous to existing stream classes but using std::span
as the buffer. They allow explicit buffer management and improved performance.
char buffer[64] { 0 };
std::span<char> spanBuffer(buffer);
std::basic_ospanstream<char> outputStream(spanBuffer);
outputStream << "Hello, " << "C++23!";
std::format
Improvements
- Compile-time string parsing
- Formatting Ranges
- Improve default container formatting
std::visit()
for classes derived from std::variant
/* todo */
std::unreachable()
/* todo */
Deprecating std::aligned_storage
and aligned_union
The accepted proposal deprecates C++11’s std::aligned_storage
and std::aligned_union
in the C++ standard due to their poor API design, undefined behavior risks, and limited utility. These helper types require error-prone usage patterns like reinterpret_cast
, lack proper size guarantees, and have inconsistent or confusing APIs, making them unsuitable for modern C++ practices.
The authors analysed libraries like Boost, Folly, and Abseil and most use cases for std::aligned_
involve repetitive patterns to deduce size and alignment manually. This can be replaced with simpler alternatives like alignas
and std::byte
arrays.
// deprecated:
template <typename T>
class MyContainer {
private:
std::aligned_storage_t<sizeof(T), alignof(T)> buffer;
};
// better:
template <typename T>
class MyContainer {
private:
alignas(T) std::byte buffer[sizeof(T)];
};
These utilities are still available in the standard but are marked as discouraged for use. They may be removed in a future version of the standard (e.g., C++26 or later).
For example clang reports warning: 'aligned_storage_t' is deprecated
when compiling in C++23 mode.
Pipe support for user-defined range adaptors, P2387
/* todo */
Examples
Books on C++23
Although the standard is fresh, there are several good books focusing on C++23 worth reading… and probably more to come :)
Title | Author(s) | Description |
---|---|---|
Modern C++ Programming Cookbook (3rd Edition) | Marius Bancila | Master Modern C++ with comprehensive solutions for C++23 and all previous standards |
The C++ Programming Language (4th Edition) | Bjarne Stroustrup | The definitive guide from the creator of C++ |
Beginning C++23: From Beginner to Pro (7th Edition) | Ivor Horton, Peter Van Weert | A comprehensive guide for learning modern C++ from the ground up |
Modern C++ for Absolute Beginners (2nd Edition) | Slobodan Dmitrović | A friendly introduction to C++ programming language and C++11 to C++23 standards |
C++23 Best Practices | Jason Turner | Simple rules with specific action items for better C++ |
Learn C++ by Example | Frances Buontempo | A practical approach to learning C++ versions 11 to 23 |
Note: Links are affiliate links and may provide the site with a small commission at no extra cost to you.
Summary
I hope we covered most if not all C++23 library features!
You can check their implementation status at C++ Reference: https://en.cppreference.com/w/cpp/compiler_support#cpp23
For language features please see the previous article: C++23 Language Features and Reference Cards - C++ Stories
Back to you
- Have you played with C++23 library features?
- What are the most important features for you in this release?
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here:
Similar Articles:
- C++23 Language Features and Reference Cards
- What is the current time around the world? Utilizing std::chrono with time zones in C++23
- std::initializer_list in C++ 2/2 - Caveats and Improvements
- Fun with printing tables with std::format and C++20
- std::initializer_list in C++ 1/2 - Internals and Use Cases