Last Update:
8 More C++23 Examples
Table of Contents
In this article, you’ll see eight larger examples that illustrate the changes in C++23.
C++23 brings a ton of cool features, as you can see in my two previous articles (here and here). So far, we explored each new addition one by one, but I’d like to share more examples that combine multiple features.
Here we go:
1. std::ranges::to<>
The ranges::to<>
feature allows you to convert ranges into containers easily:
#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';
}
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;
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);
}
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 of chain operations that 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:
std::string to_uppercase(std::string str) {
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
return str;
}
struct ProcessedLine {
std::string original;
std::string uppercase;
};
std::generator<ProcessedLine> read_and_process_lines(const std::string& filename) {
std::ifstream file(filename);
std::string line;
while (std::getline(file, line)) {
ProcessedLine processed{
line,
to_uppercase(line)
};
co_yield processed;
}
}
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& processed : read_and_process_lines(temp_filename)) {
std::cout << "Original : " << processed.original << '\n';
std::cout << "Uppercase: " << processed.uppercase << '\n';
}
}
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);
}
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);
}
8. chunk
, enumerate
and fold_left
Here’s an example where we process simple strings into words and sentences and provide simple stats:
Core part:
std::vector<std::string> split_into_words(const std::string& text) {
return text
| std::views::split(' ')
| std::views::transform([](auto&& word_range) {
return std::string(word_range.begin(), word_range.end());
})
| std::ranges::to<std::vector>();
}
void analyze_sentence(size_t index, const std::vector<std::string>& words) {
const size_t word_count = words.size();
const double total_word_length = std::ranges::fold_left(words, 0.0,
[](double sum, const std::string& word) {
return sum + word.size();
});
const double avg_word_length
= word_count > 0 ? total_word_length / word_count : 0.0;
const auto longest_word = std::ranges::max(words, {}, &std::string::size);
std::cout << "Sentence " << index + 1 << ": ";
for (const auto& w : words)
std::cout << w << ' ';
std::cout << "\n Word count: " << word_count << "\n";
std::cout << " Average word length: " << avg_word_length << "\n";
std::cout << " Longest word: " << longest_word << "\n";
}
int main() {
std::string text = "This is the first sentence. Here is another one! "
"And yet another sentence. It is fun to analyze text.";
auto all_words = split_into_words(text);
auto sentences = all_words | std::views::chunk_by(
[](const std::string& a, const std::string& b) {
return !a.ends_with('.') && !a.ends_with('!');
});
for (const auto& [index, sentence] : std::views::enumerate(sentences)) {
auto words = sentence | std::ranges::to<std::vector>();
if (!words.empty() && (words.back().ends_with('.') || words.back().ends_with('!'))) {
words.back().pop_back(); // Remove the sentence-ending punctuation for analysis
}
analyze_sentence(index, words);
}
}
Back to you
- Do you have some other cool examples with C++23 features?
- Have you played with C++23 library features?
- What are the most important features for you in this release?
Share your comments below
I've prepared a valuable bonus for you!
Learn all major features of recent C++ Standards on my Reference Cards!
Check it out here: