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
- Lazy Evaluation:
std::views::adjacentdoes not copy or allocate elements ahead of time. Iterators compute the tuple of references on demand as the range is traversed. - 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.
- Range Category Preservation: If the underlying
range satisfies
std::ranges::random_access_rangeorstd::ranges::bidirectional_range, the resultingadjacent_viewpreserves those traversal capabilities, allowing random-access operations where applicable. - Safety Over Indexing: Traditional sliding windows
often require manual index arithmetic (such as
vec[i]andvec[i+1]), which risks off-by-one errors or out-of-bounds access.std::views::adjacentabstracts boundary checks away entirely, ensuring traversal stops safely at the end of the collection.