Introduction
The standard std::for_each operates on one element at a time, but many algorithms need to process pairs or windows of adjacent elements — computing differences, detecting changes, or applying sliding-window operations. C++23 added std::views::adjacent and std::views::slide for this purpose. In C++17 and earlier, use std::adjacent_find with a custom predicate, manual iterator arithmetic, or a custom for_each_adjacent function. For Python and JavaScript, the equivalent patterns use zip, itertools.pairwise, or Array.reduce.
C++23: views::adjacent and views::slide
1#include <ranges>
2#include <vector>
3#include <iostream>
4
5int main() {
6 std::vector<int> v = {1, 4, 2, 8, 5, 7};
7
8 // adjacent<2> gives pairs of consecutive elements
9 for (auto [a, b] : v | std::views::adjacent<2>) {
10 std::cout << a << ", " << b << " -> diff: " << b - a << "\n";
11 }
12 // 1, 4 -> diff: 3
13 // 4, 2 -> diff: -2
14 // 2, 8 -> diff: 6
15 // 8, 5 -> diff: -3
16 // 5, 7 -> diff: 2
17
18 // adjacent<3> gives triples
19 for (auto [a, b, c] : v | std::views::adjacent<3>) {
20 std::cout << a << ", " << b << ", " << c << "\n";
21 }
22 // 1, 4, 2
23 // 4, 2, 8
24 // 2, 8, 5
25 // 8, 5, 7
26
27 return 0;
28}
std::views::adjacent<N> produces a view of tuples containing N consecutive elements from the range. It is a compile-time fixed-size sliding window.
C++23: views::slide (Runtime Window Size)
1#include <ranges>
2#include <vector>
3#include <iostream>
4
5int main() {
6 std::vector<int> v = {1, 2, 3, 4, 5};
7
8 // slide(3) gives windows of size 3 as subranges
9 for (auto window : v | std::views::slide(3)) {
10 for (int x : window) {
11 std::cout << x << " ";
12 }
13 std::cout << "\n";
14 }
15 // 1 2 3
16 // 2 3 4
17 // 3 4 5
18
19 return 0;
20}
std::views::slide(n) produces subranges of size n, where n can be determined at runtime. Each subrange is a view into the original container.
C++17: Custom for_each_adjacent
1#include <vector>
2#include <iostream>
3
4template <typename Iter, typename Func>
5void for_each_adjacent(Iter begin, Iter end, Func func) {
6 if (begin == end) return;
7 auto prev = begin;
8 for (auto it = std::next(begin); it != end; ++it) {
9 func(*prev, *it);
10 prev = it;
11 }
12}
13
14int main() {
15 std::vector<int> v = {10, 20, 15, 30, 25};
16
17 for_each_adjacent(v.begin(), v.end(), [](int a, int b) {
18 std::cout << a << " -> " << b;
19 if (b > a) std::cout << " (increase)";
20 else std::cout << " (decrease)";
21 std::cout << "\n";
22 });
23 // 10 -> 20 (increase)
24 // 20 -> 15 (decrease)
25 // 15 -> 30 (increase)
26 // 30 -> 25 (decrease)
27
28 return 0;
29}
This is the pre-C++23 solution. The template works with any forward iterator and any binary function.
C++17: N-Element Sliding Window
1#include <vector>
2#include <iostream>
3#include <numeric>
4
5template <typename Iter, typename Func>
6void for_each_window(Iter begin, Iter end, int window_size, Func func) {
7 if (std::distance(begin, end) < window_size) return;
8 for (auto it = begin; it + window_size <= end; ++it) {
9 func(it, it + window_size);
10 }
11}
12
13int main() {
14 std::vector<int> v = {1, 3, 5, 7, 9, 11};
15
16 // Moving average with window size 3
17 for_each_window(v.begin(), v.end(), 3,
18 [](auto begin, auto end) {
19 double sum = std::accumulate(begin, end, 0.0);
20 double avg = sum / std::distance(begin, end);
21 std::cout << "avg: " << avg << "\n";
22 });
23 // avg: 3
24 // avg: 5
25 // avg: 7
26 // avg: 9
27
28 return 0;
29}
Using std::adjacent_find as a Workaround
1#include <algorithm>
2#include <vector>
3#include <iostream>
4
5int main() {
6 std::vector<int> v = {1, 3, 2, 5, 4};
7
8 // adjacent_find visits pairs — abuse it to process all pairs
9 // by always returning false
10 std::adjacent_find(v.begin(), v.end(),
11 [](int a, int b) {
12 std::cout << "pair: " << a << ", " << b << "\n";
13 return false; // Never "find" — visits all pairs
14 });
15 // pair: 1, 3
16 // pair: 3, 2
17 // pair: 2, 5
18 // pair: 5, 4
19
20 return 0;
21}
This is a hack — std::adjacent_find is designed to find the first adjacent pair matching a predicate, but returning false forces it to visit every pair. Use the custom function template for production code.
1from itertools import pairwise
2
3data = [10, 20, 15, 30, 25]
4
5# pairwise gives consecutive pairs
6for a, b in pairwise(data):
7 diff = b - a
8 print(f"{a} -> {b}: {'up' if diff > 0 else 'down'} {abs(diff)}")
9# 10 -> 20: up 10
10# 20 -> 15: down 5
11# 15 -> 30: up 15
12# 30 -> 25: down 5
13
14# N-element sliding window
15from itertools import islice
16
17def sliding_window(iterable, n):
18 it = iter(iterable)
19 window = list(islice(it, n))
20 if len(window) == n:
21 yield tuple(window)
22 for x in it:
23 window = window[1:] + [x]
24 yield tuple(window)
25
26for window in sliding_window(data, 3):
27 print(window)
28# (10, 20, 15)
29# (20, 15, 30)
30# (15, 30, 25)
JavaScript: Adjacent Element Patterns
1const data = [10, 20, 15, 30, 25];
2
3// Process adjacent pairs
4data.forEach((val, i) => {
5 if (i === 0) return;
6 const prev = data[i - 1];
7 console.log(`${prev} -> ${val}: diff ${val - prev}`);
8});
9
10// Sliding window of size n using reduce
11function slidingWindow(arr, n) {
12 return arr.slice(0, arr.length - n + 1).map((_, i) => arr.slice(i, i + n));
13}
14
15console.log(slidingWindow(data, 3));
16// [[10,20,15], [20,15,30], [15,30,25]]
17
18// Using Array.reduce for pairwise
19const diffs = data.reduce((acc, val, i) => {
20 if (i > 0) acc.push(val - data[i - 1]);
21 return acc;
22}, []);
23console.log(diffs); // [10, -5, 15, -5]
Common Pitfalls
Off-by-one with window size: A container of N elements has N-1 adjacent pairs, N-2 triples, and so on. Forgetting this leads to out-of-bounds access. Always check that the container size is at least the window size before iterating.
Iterator invalidation: If the callback modifies the container (inserting or erasing elements), iterators become invalid. Never modify the container during a sliding-window traversal. Copy data if mutation is needed.
Using std::adjacent_find for side effects: The standard does not guarantee adjacent_find processes all pairs if it finds a match. The return false hack works for visiting all pairs but is fragile and unclear. Use a custom function instead.
Performance with large windows: A naive sliding window copies elements for each position. For large windows, use a deque-based approach that adds one element and removes one per step, achieving O(1) per window instead of O(window_size).
views::adjacent requires random access: In C++23, std::views::adjacent<N> works best with random-access ranges. For forward-only ranges (like std::forward_list), it may not compile or may have degraded performance. Check the range concept requirements.
Summary
C++23 provides std::views::adjacent<N> (compile-time) and std::views::slide(n) (runtime) for sliding windows
In C++17, write a custom for_each_adjacent template using iterator pairs
std::adjacent_find can visit all pairs as a workaround but is not designed for this purpose
Python 3.10+ has itertools.pairwise() for adjacent pairs; use a generator for arbitrary window sizes
JavaScript uses index-based iteration or Array.slice for sliding windows
Always check that the container has enough elements for the requested window size