C++ std::views::adjacent for Sliding Window Views

This article explains the functionality of std::views::adjacent, introduced in C++23, as a mechanism for processing fixed-size sliding windows across sequential collections. You will learn how this range adaptor works under the hood, how it produces tuples of element references without manual index management, and how to effectively utilize it alongside structured bindings to write safer, more readable range-based algorithms.

Understanding std::views::adjacent

Introduced in the <ranges> header in C++23, std::views::adjacent is a range adaptor closure object that transforms an existing range into a sequence of sliding windows of a fixed, compile-time size N. When applied to a sequence, it returns an adjacent_view, where each step yields an std::tuple containing N references to contiguous elements of the underlying collection.

The template signature requires specifying the window size at compile time:

std::views::adjacent<N>

For the common case of comparing consecutive pairs (\(N = 2\)), the standard library provides the convenient alias std::views::pairwise, which is functionally identical to std::views::adjacent<2>.

Mechanics and Sliding Behavior

Given a sequence \([e_0, e_1, e_2, \dots, e_{m-1}]\) and a window size \(N\), std::views::adjacent<N> produces a sequence of tuples:

  • Index 0: std::tuple(e_0, e_1, ..., e_{N-1})
  • Index 1: std::tuple(e_1, e_2, ..., e_N)
  • ...
  • Index \(m-N\): std::tuple(e_{m-N}, e_{m-N+1}, ..., e_{m-1})

If the source range contains fewer than \(N\) elements, the resulting view is valid but empty.

Code Implementation

Because each element emitted by the view is a tuple, it integrates naturally with C++17 structured bindings.

#include <iostream>
#include <vector>
#include <ranges>

int main() {
    std::vector<int> numbers = {10, 20, 35, 55, 80};

    // Inspect pairs of adjacent items
    for (auto [prev, curr] : numbers | std::views::pairwise) {
        std::cout << "Delta: " << (curr - prev) << '\n';
    }

    // Inspect 3-element sliding windows
    for (auto [a, b, c] : numbers | std::views::adjacent<3>) {
        std::cout << "Window: [" << a << ", " << b << ", " << c << "]\n";
    }
}

Key Properties and Benefits

  1. Lazy Evaluation: std::views::adjacent does not copy or allocate elements ahead of time. Iterators compute the tuple of references on demand as the range is traversed.
  2. Reference Semantics: The tuples contain value references to the original data rather than copies. If the underlying range is non-const and provides mutable references, elements can be updated in-place via the unpacked tuple references.
  3. Range Category Preservation: If the underlying range satisfies std::ranges::random_access_range or std::ranges::bidirectional_range, the resulting adjacent_view preserves those traversal capabilities, allowing random-access operations where applicable.
  4. Safety Over Indexing: Traditional sliding windows often require manual index arithmetic (such as vec[i] and vec[i+1]), which risks off-by-one errors or out-of-bounds access. std::views::adjacent abstracts boundary checks away entirely, ensuring traversal stops safely at the end of the collection.