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';
}

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;
        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 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';
    }
}

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

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);
    }
}

Run @Compiler Explorer

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