Table of Contents

In this blog post, we’ll continue looking at ranges and this time explore ways to split them into sub-ranges. So we’ll take a look at views::split, views::chunk, and views::chunk_by.

We’ll walk through two examples for each adaptor: one simple and one slightly more advanced, to highlight their practical uses.

Let’s go.

Splitting Ranges with views::split, C++20  

If you want to split a range using some “delimeter,” then views::split (or ranges::split_view) will do the job.

template< ranges::forward_range V, ranges::forward_range Pattern >
// .. requires...
class split_vie  : public ranges::view_interface<split_view<V, Pattern>>

Both of the ranges V and Pattern have to be forward_range.

Notice that you need to use a pattern; it cannot be a condition.

Example 1: Splitting a Sentence into Words  

#include <print>
#include <ranges>
#include <string_view>

int main() {
    using namespace std::string_view_literals;

    constexpr auto text = "C++ is powerful and elegant"sv;
    
    for (auto part : std::views::split(text, ' '))
        std::print("'{}' ", std::string_view(part));
}

Play @Compiler Explorer

The output:

'C++' 'is' 'powerful' 'and' 'elegant'

A classic example that splits a sentence into words. We convert each subrange to std::string_view for easy printing.

We can also extend this and use not just one character:

constexpr auto text = "C++breakisbreakpowerfulbreakandbreakelegant"sv;

for (auto part : std::views::split(text, "break"sv))
	std::print("'{}' ", std::string_view(part));

Example 2: Splitting Not Just Text  

split can be applied to any forward range, so not just text:

#include <print>
#include <ranges>
#include <vector>
#include <utility>

int main() {
    using Point = std::pair<int, int>;

    std::vector<Point> path = {
        {0, 0}, {1, 1}, {-1, -1}, // marker: {-1, -1}
        {2, 2}, {3, 3}, {-1, -1},
        {4, 4}, {5, 5}
    };

    for (auto segment : std::views::split(path, Point{-1, -1}))
        std::print("Segment: {}\n", segment);
}

See @Compiler Explorer

And the output:

Segment: [(0, 0), (1, 1)]
Segment: [(2, 2), (3, 3)]
Segment: [(4, 4), (5, 5)]

In the example, we run through the point list and break at markers that are (-1, -1). This code also leveraged std::format support for ranges (C++23), so we don’t have to print individual points.

What About lazy_split?  

You might also encounter views::lazy_split, introduced in C++20.

This view is specialized for input-only ranges, such as reading from streams or generators. It avoids buffering or requiring multiple passes, but its subranges aren’t contiguous and don’t expose .data() or .size().

Unless you’re processing streams in a single pass, prefer views::split for simplicity.

Here’s a question from Stack Overflow that I recently asked to clarify this view: c++ - What is the use of std::ranges::views::lazy_split when we have std::ranges::views::split? - Stack Overflow

Grouping with views::chunk, C++23  

When you need to process data in fixed-size batches, chunk is the perfect tool.

Example 1: Fixed-Size Batches  

#include <print>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> data{1, 2, 3, 4, 5, 6, 7, 8};

    for (auto chunk : data | std::views::chunk(3))
        std::print("{}\n", chunk);
}

See @Compiler Explorer

And the output:

[ 1 2 3 ]
[ 4 5 6 ]
[ 7 8 ]

views::chunk splits the sequence into groups of three elements. If the number of elements isn’t divisible by 3, the last chunk will contain fewer elements.

Example 2: Processing Network Packets in Chunks  

chunk can work not just with regular containers, but with just input ranges:

#include <print>
#include <ranges>
#include <sstream>
#include <vector>

int main() {
    std::istringstream stream{"AB CD EF 12 34 56 78 95 FF"};

    auto bytes = std::ranges::istream_view<std::string>(stream);

    for (auto packet : bytes | std::views::chunk(4))
        std::print("Packet: {}\n", packet);
}

See @Compiler Explorer

Output:

Packet: ["AB", "CD", "EF", "12"]
Packet: ["34", "56", "78", "95"]
Packet: ["FF"]

This example simulates processing a byte stream in fixed 2-byte packets.

Dynamic Grouping with views::chunk_by, C++26  

When the group size isn’t fixed but defined by a condition, chunk_by comes into play, which is a recent addition from C++26. This time, the range has to model forward range at least.

Example 1: Grouping by Parity (Even/Odd)  

#include <print>
#include <ranges>
#include <vector>

int main() {
    std::vector<int> values{1, 3, 5, 2, 4, 6, 7, 9, 8};

    for (auto group : values | std::views::chunk_by([](int a, int b) {
        return (a % 2) == (b % 2); // Same parity
    })) {
        std::print("size {}, {}\n", group.size(), group);
    }
}

Run @Compiler Explorer

Output:

size 3, [1, 3, 5]
size 3, [2, 4, 6]
size 2, [7, 9]
size 1, [8]

This code dynamically groups consecutive numbers based on their parity. Each group contains either only odd or only even numbers.

Example 2: Extracting Sentences from Text  

We can use chunk_by and split at places where a sentence ends.

First try:

#include <print>
#include <ranges>
#include <string_view>

int main() {
    using namespace std::string_view_literals;

    constexpr auto text = "C++ is powerful. Ranges are elegant. This is fun!"sv;

    for (auto sentence : text | std::views::chunk_by([](char a, char b) {
        // Group until a dot is found; start a new group after '.'
        return a != '.' && b != '.';
    })) {
        std::print("Sentence: {}\n", sentence);
    }
}

And we get:

Sentence: ['C', '+', '+', ' ', 'i', 's', ' ', ...]
Sentence: ['.']
Sentence: [' ', 'R', 'a', 'n', 'g', 'e', 's', ...]
Sentence: ['.']
Sentence: [' ', 'T', 'h', 'i', 's', ' ', 'i', ...]

Probably not the best… but we can try cleaning the result:

int main() {
    using namespace std::string_view_literals;

    constexpr auto text = "C++ is powerful. Ranges are elegant. This is fun!"sv;

    for (auto sentence : text | std::views::chunk_by([](char a, char b) {
        // Group until a dot is found; start a new group after '.'
        return a != '.' && b != '.';
    })) {
        // Remove leading spaces if any, and skip dots-only groups
        auto view = std::string_view(&*sentence.begin(), std::ranges::distance(sentence));
        view.remove_prefix(std::min(view.find_first_not_of(' '), view.size()));

        if (!view.empty() && view != ".")
            std::print("Sentence: [{}]\n", view);
    }
}

And now we get:

Sentence: [C++ is powerful]
Sentence: [Ranges are elegant]
Sentence: [This is fun!]

Experiment @Compiler Explorer

Summary  

Feature split chunk chunk_by
C++ Standard C++20 C++23 C++26
Fixed-size groups
Custom grouping
Text splitting

In this text, we explored various ways to split ranges using split, chunk or chunk_by, we also touched a little bit about lazy_split.

Back to you

  • Which of these adaptors have you already tried?
  • Which one surprised you the most?

Let me know your thoughts in the comments!