C++ Range Adaptors | Pipeline Composition in C++20
이 글의 핵심
Practical guide to C++ range adaptors: concepts, pipelines, and production patterns with examples.
What are range adaptors?
Function objects that transform a range into a view (C++20).
#include <ranges>
std::vector<int> v = {1, 2, 3, 4, 5};
// Adaptor: range -> view
auto filtered = v | std::views::filter([](int x) { return x > 2; });
Basic usage
namespace vws = std::views;
std::vector<int> v = {1, 2, 3, 4, 5};
// Apply adaptor (functional style)
auto view1 = vws::filter(v, [](int x) { return x > 2; });
// Pipeline
auto view2 = v | vws::filter([](int x) { return x > 2; });
Practical examples
Example 1: Pipeline composition
#include <ranges>
#include <vector>
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Several adaptors
auto pipeline = numbers
| std::views::filter([](int x) { return x % 2 == 0; }) // evens
| std::views::transform([](int x) { return x * x; }) // squares
| std::views::take(3); // first 3
for (int x : pipeline) {
std::cout << x << " "; // 4 16 36
}
Example 2: Reusable adaptors
#include <ranges>
// Store adaptors
auto evenFilter = std::views::filter([](int x) { return x % 2 == 0; });
auto square = std::views::transform([](int x) { return x * x; });
std::vector<int> v1 = {1, 2, 3, 4, 5};
std::vector<int> v2 = {6, 7, 8, 9, 10};
// Reuse
auto result1 = v1 | evenFilter | square;
auto result2 = v2 | evenFilter | square;
Example 3: Custom-style adaptors
#include <ranges>
// Odds only
auto odds = std::views::filter([](int x) { return x % 2 != 0; });
// Multiples of 3
auto multiplesOf3 = std::views::filter([](int x) { return x % 3 == 0; });
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto result = v | odds; // 1 3 5 7 9
Example 4: Conditional adaptor
#include <ranges>
template<typename Range>
auto conditionalFilter(Range&& r, bool applyFilter) {
if (applyFilter) {
return std::forward<Range>(r)
| std::views::filter([](int x) { return x > 5; });
} else {
return std::forward<Range>(r) | std::views::all;
}
}
int main() {
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto result = conditionalFilter(v, true);
for (int x : result) {
std::cout << x << " "; // 6 7 8 9 10
}
}
Main adaptors
namespace vws = std::views;
// Filtering
vws::filter(pred)
vws::take(n)
vws::drop(n)
vws::take_while(pred)
vws::drop_while(pred)
// Transform
vws::transform(func)
vws::reverse
// Split/join
vws::split(delimiter)
vws::join
// Generation
vws::iota(start)
vws::iota(start, end)
// Other
vws::all // full range as view
vws::counted(it, n)
vws::common
Common problems
Problem 1: Type inference
// ❌ Verbose type
std::vector<int> v = {1, 2, 3};
std::ranges::filter_view<std::ranges::ref_view<std::vector<int>>, /* ... */> filtered =
v | std::views::filter([](int x) { return x > 1; });
// ✅ auto
auto filtered = v | std::views::filter([](int x) { return x > 1; });
Problem 2: Adaptor order
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Order changes the result
auto r1 = v | std::views::reverse | std::views::take(3);
// 10 9 8
auto r2 = v | std::views::take(3) | std::views::reverse;
// 3 2 1
Problem 3: Lifetime
// ❌ Temporary destroyed
auto getView() {
std::vector<int> v = {1, 2, 3};
return v | std::views::filter([](int x) { return x > 1; });
// v destroyed
}
// ✅ Clear ownership or reference
auto getView(const std::vector<int>& v) {
return v | std::views::filter([](int x) { return x > 1; });
}
Problem 4: Performance
// Lazy: recomputed each pass
auto view = v | std::views::filter(pred) | std::views::transform(func);
for (int x : view) { /* ... */ } // compute
for (int x : view) { /* ... */ } // compute again
// ✅ Cache when you need one pass worth of work repeatedly
std::vector<int> cached(view.begin(), view.end());
for (int x : cached) { /* ... */ } // use cache
for (int x : cached) { /* ... */ } // use cache
Combining adaptors
namespace vws = std::views;
// Pipeline of adaptor objects
auto pipeline = vws::filter(pred)
| vws::transform(func)
| vws::take(n);
// Apply to data
std::vector<int> v = {1, 2, 3, 4, 5};
auto result = v | pipeline;
FAQ
Q1: What is a range adaptor?
A: It turns a range into a view.
Q2: Pipelines?
A: Combine with |.
Q3: Lazy evaluation?
A: Computed when you iterate.
Q4: Reuse?
A: You can store adaptor objects and reuse them.
Q5: Order?
A: It matters for correctness and efficiency.
Q6: Learning resources?
A:
- “C++20 Ranges”
- “C++20 The Complete Guide”
- cppreference.com
Related reading (internal links)
- C++ range algorithms
- C++ Ranges library guide
- C++ Views guide
Practical tips
Tips you can apply at work.
Debugging
- When something breaks, check compiler warnings first
- Reproduce with a small test case
Performance
- Do not optimize without profiling
- Define measurable targets first
Code review
- Pre-check areas that often get flagged in review
- Follow team conventions
Production checklist
Things to verify when applying this idea in practice.
Before coding
- Is this technique the best fit for the problem?
- Can teammates understand and maintain it?
- Does it meet performance requirements?
While coding
- Are all compiler warnings addressed?
- Are edge cases considered?
- Is error handling appropriate?
At review
- Is intent clear?
- Are tests sufficient?
- Is it documented?
Use this checklist to reduce mistakes and improve quality.
Keywords covered
Search for C++, range, adaptor, pipeline, C++20 to find this post.
Related posts
- C++20 Ranges basics series
- C++ range algorithms
- C++ Ranges basics
- C++ barrier & latch
- C++ branch prediction