Seven More Examples for C++23 Library
Changes
C++23 gives us plenty of cool features. Below you can find more examples that extend my blog
article - C++23 Library Features and Reference Cards - C++ Stories.
1. std::ranges::to<>
The ranges::to<> feature allows you to easily convert ranges into containers:
#include <ranges>
#include <vector>
#include <iostream>
#include <string>
#include <unordered_map>
#include <map>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
std::unordered_map<int, std::string> number_to_text = {
{1, "one"}, {2, "two"}, {3, "three"}, {4, "four"}, {5, "five"},
{6, "six"}, {7, "seven"}, {8, "eight"}, {9, "nine"}, {10, "ten"}
};
auto even_numbers = numbers
| std::views::filter([](int n) { return n % 2 == 0; })
| std::ranges::to<std::vector>();
auto text_numbers_map = even_numbers
| std::views::transform([&number_to_text](int n) {
return std::pair{n, number_to_text.contains(n) ?
number_to_text[n] : "unknown" };
})
| std::ranges::to<std::map>();
std::cout << "Even numbers: ";
for (int n : even_numbers) std::cout << n << " ";
std::cout << "\nTextual numbers:\n";
for (const auto& [k, v] : text_numbers_map)
std::cout << k << " -> " << v << '\n';
}
Run @Compiler Explorer
As you can see, there's no issue in creating not only vectors but maps as well.
2. std::print and std::println
The new formatted output library simplifies printing. Here's an example that demonstrates
alignment, formatting, and table creation:
#include <print>
#include <unordered_map>
#include <string>
#include <numeric>
int main() {
// Store the data in an unordered_map (country -> size in km²)
std::unordered_map<std::string, double> country_sizes = {
{"USA", 9833517},
{"Canada", 9984670},
{"Australia", 7692024},
{"China", 9596961},
{"Poland", 312696}
};
constexpr double KM_TO_MI = 0.386102; // Conversion factor
const double total_km = std::accumulate(country_sizes.begin(),
country_sizes.end(), 0.0,
[](double sum, const auto& entry) { return
sum + entry.second; });
const double total_mi = total_km * KM_TO_MI;
// Table headers
std::println("{:<15} | {:>15} | {:>15}", "Country", "Size (km²)", "Size
(mi²)");
std::println("{:-<15}-+-{:-<15}-+-{:-<15}", "", "", ""); // Separator line
// Table rows
for (const auto& [country, size_km] : country_sizes) {
double size_mi = size_km * KM_TO_MI; // Compute size in square miles
dynamically
std::println("{:<15} | {:>15.0f} | {:>15.2f}", country, size_km,
size_mi);
}
// Footer
std::println("{:-<15}-+-{:-<15}-+-{:-<15}", "", "", ""); // Separator line
std::println("{:<15} | {:>15.0f} | {:>15.2f}", "Total", total_km, total_mi);
}
Run @Compiler Explorer
Example output:
Country | Size (km²) | Size (mi²)
----------------+-----------------+----------------
Poland | 312696 | 120732.55
China | 9596961 | 3705405.84
Australia | 7692024 | 2969905.85
Canada | 9984670 | 3855101.06
USA | 9833517 | 3796740.58
----------------+-----------------+----------------
Total | 37419868 | 14447885.87
3. std::optional Monadic Operations
The new and_then , transform , and or_else functions make working with std::optional
more expressive. Here's an example that chains operations to process user input.
#include <optional>
#include <iostream>
#include <string>
#include <algorithm>
std::optional<std::string> get_user_input() {
std::string input;
std::cout << "Enter your name: ";
std::getline(std::cin, input);
if (input.empty()) return std::nullopt;
return input;
}
int main() {
auto result = get_user_input()
.transform([](std::string name) {
std::transform(name.begin(), name.end(), name.begin(), ::toupper);
return name;
})
.and_then([](std::string name) {
if (name == "ADMIN") return std::optional<std::string>("Welcome,
Admin!");
return std::optional<std::string>("Hello, " + name + "!");
})
.or_else([] {
return std::optional<std::string>("No input provided.");
});
std::cout << *result << "\n";
}
Experiment at @Compiler Explorer
4. std::generator
The std::generator feature simplifies creating lazy sequences. Here's an example of reading line
by line from a file:
#include <generator>
#include <fstream>
#include <iostream>
#include <string>
// Coroutine to read lines from a file
std::generator<std::string> read_lines(const std::string& filename) {
std::ifstream file(filename);
std::string line;
while (std::getline(file, line)) {
co_yield line;
}
}
int main() {
const std::string temp_filename = "temp_file.txt";
{
std::ofstream temp_file(temp_filename);
temp_file << "Line 1: Hello, World!\n";
temp_file << "Line 2: This is a test.\n";
temp_file << "Line 3: C++ coroutines are cool!\n";
}
for (const auto& line : read_lines(temp_filename)) {
std::cout << line << std::endl;
}
}
See @Compiler Explorer
5. std::mdspan
The std::mdspan feature is useful for multidimensional data. Here's an example of matrix
multiplication.
#include <https://raw.githubusercontent.com/kokkos/mdspan/single-
header/mdspan.hpp>
#include <vector>
#include <print>
void multiply_matrices(std::mdspan<int, std::dextents<size_t, 2>> A,
std::mdspan<int, std::dextents<size_t, 2>> B,
std::mdspan<int, std::dextents<size_t, 2>> C) {
for (size_t i = 0; i < A.extent(0); ++i) {
for (size_t j = 0; j < B.extent(1); ++j) {
C[i, j] = 0;
std::print("{}{}: ", i, j);
for (size_t k = 0; k < A.extent(1); ++k) {
std::print("+= {} x {}, ", A[i, k], B[k, j]);
C[i, j] += A[i, k] * B[k, j];
}
std::println();
}
}
}
int main() {
std::vector<int> A_data = {1, 2, 3, 4, 5, 6};
std::vector<int> B_data = {1, 2, 3, 4, 5, 6};
std::vector<int> C_data(4);
auto A = std::mdspan(A_data.data(), 2, 3);
auto B = std::mdspan(B_data.data(), 3, 2);
auto C = std::mdspan(C_data.data(), 2, 2);
multiply_matrices(A, B, C);
for (size_t i = 0; i < C.extent(0); ++i) {
for (size_t j = 0; j < C.extent(1); ++j) {
std::print("{} ", C[i, j]);
}
std::println();
}
}
Experiment @Compiler Explorer
6. cartesian_product , enumerate , and zip
#include <ranges>
#include <print>
#include <vector>
int main() {
std::vector<int> range1 = {1, 2, 3};
std::vector<char> range2 = {'A', 'B', 'C'};
auto product = std::views::cartesian_product(range1, range2);
std::println("Cartesian Product:");
for (const auto& [a, b] : product)
std::println("({}, {})", a, b);
auto enumerated = std::views::enumerate(range1);
std::println("\nEnumerated Range:");
for (const auto& [index, value] : enumerated)
std::println("Index: {}, Value: {}", index, value);
auto zipped = std::views::zip(range1, range2);
std::println("\nZipped Ranges:");
for (const auto& [a, b] : zipped)
std::println("({}, {})", a, b);
}
Run @Compiler Explorer
7. chunk , slide and stride
#include <ranges>
#include <print>
#include <vector>
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9};
// Chunk: divide the range into groups of 3
auto chunks = std::views::chunk(numbers, 3);
std::println("Chunks of 3:");
for (const auto& chunk : chunks) {
for (int n : chunk)
std::print("{} ", n);
std::println("");
}
// Slide: create overlapping subranges of size 3
auto sliding = std::views::slide(numbers, 3);
std::println("\nSliding Window of 3:");
for (const auto& window : sliding) {
for (int n : window)
std::print("{} ", n);
std::println("");
}
// Stride: skip every 2 elements
auto strided = std::views::stride(numbers, 2);
std::println("\nStrided Range (step 2):");
for (int n : strided)
std::print("{} ", n);
}
Run @Compiler Explorer