C++ Views | Lazy Range Views in C++20
이 글의 핵심
Practical guide to C++ views: concepts, lazy pipelines, and production use with examples.
What are views?
Lazy-evaluated ranges (C++20).
#include <ranges>
std::vector<int> v = {1, 2, 3, 4, 5};
// View: no copy
auto filtered = v | std::views::filter([](int x) { return x > 2; });
// Evaluated on iteration
for (int x : filtered) {
std::cout << x << " "; // 3 4 5
}
Basic views
namespace vws = std::views;
// filter: predicate
vws::filter([](int x) { return x % 2 == 0; })
// transform: map
vws::transform([](int x) { return x * 2; })
// take: first n elements
vws::take(5)
// drop: skip first n
vws::drop(3)
// reverse
vws::reverse
Practical examples
Example 1: filter
#include <ranges>
#include <vector>
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Evens only
auto evens = numbers | std::views::filter([](int x) {
return x % 2 == 0;
});
for (int x : evens) {
std::cout << x << " "; // 2 4 6 8 10
}
Example 2: transform
std::vector<int> numbers = {1, 2, 3, 4, 5};
// Squares
auto squares = numbers | std::views::transform([](int x) {
return x * x;
});
for (int x : squares) {
std::cout << x << " "; // 1 4 9 16 25
}
Example 3: take & drop
std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// First 5
auto first5 = numbers | std::views::take(5);
// 1 2 3 4 5
// Skip first 3
auto skip3 = numbers | std::views::drop(3);
// 4 5 6 7 8 9 10
// Combined: skip 3, then take 5
auto middle = numbers | std::views::drop(3) | std::views::take(5);
// 4 5 6 7 8
Example 4: composite pipeline
#include <ranges>
#include <vector>
#include <string>
struct Person {
std::string name;
int age;
};
std::vector<Person> people = {
{"Alice", 25},
{"Bob", 30},
{"Charlie", 35},
{"David", 40}
};
// Age >= 30 -> names -> uppercase -> first 2
auto result = people
| std::views::filter([](const Person& p) { return p.age >= 30; })
| std::views::transform([](const Person& p) { return p.name; })
| std::views::transform([](std::string name) {
std::string upper = name;
for (char& c : upper) c = std::toupper(c);
return upper;
})
| std::views::take(2);
for (const auto& name : result) {
std::cout << name << std::endl; // BOB, CHARLIE
}
Conditional views
namespace vws = std::views;
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// take_while: while predicate holds
auto lessThan5 = v | vws::take_while([](int x) { return x < 5; });
// 1 2 3 4
// drop_while: skip while predicate holds
auto from5 = v | vws::drop_while([](int x) { return x < 5; });
// 5 6 7 8 9 10
Common problems
Problem 1: Lifetime
// ❌ Dangling view
auto getView() {
std::vector<int> v = {1, 2, 3};
return v | std::views::filter([](int x) { return x > 1; });
// v destroyed
}
// ✅ Own the result
auto getFiltered() {
std::vector<int> v = {1, 2, 3};
std::vector<int> result;
auto view = v | std::views::filter([](int x) { return x > 1; });
std::ranges::copy(view, std::back_inserter(result));
return result;
}
Problem 2: Aliasing / mutation
// Views do not copy the sequence
std::vector<int> v = {1, 2, 3};
auto view = v | std::views::transform([](int x) { return x * 2; });
v[0] = 10;
// view still refers to v
for (int x : view) {
std::cout << x << " "; // 20 4 6
}
Problem 3: Re-evaluation
auto view = v | std::views::filter(pred) | std::views::transform(func);
// Recomputed each pass
for (int x : view) { /* ... */ } // compute
for (int x : view) { /* ... */ } // compute again
// ✅ Cache if needed
std::vector<int> cached(view.begin(), view.end());
Problem 4: Pipeline order
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// ❌ Less efficient
auto r1 = v
| std::views::transform([](int x) { return x * x; }) // 10 squares
| std::views::take(3); // use only 3
// ✅ Better order
auto r2 = v
| std::views::take(3) // 3 elements only
| std::views::transform([](int x) { return x * x; }); // 3 squares
Combining views
namespace vws = std::views;
std::vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Multiple adapters
auto result = v
| vws::filter([](int x) { return x % 2 == 0; }) // evens
| vws::transform([](int x) { return x * x; }) // squares
| vws::reverse // reverse
| vws::take(3); // first 3
// 100 64 36
FAQ
Q1: What are views?
A: Lazily evaluated range adapters.
Q2: Lazy evaluation?
A: Computation happens when you iterate.
Q2: Copying?
A: Views do not copy elements; they refer to the source.
Q4: Pipelines?
A: Combine with the | operator.
Q5: Performance?
A:
- No element copy by default
- Lazy evaluation
- Order of adapters matters
Q6: Learning resources?
A:
- “C++20 Ranges”
- “C++20 The Complete Guide”
- cppreference.com
Related reading (internal links)
Other posts that connect to this topic.
- C++ Ranges | range library guide
- C++ Ranges | functional-style C++20 guide
- C++ subrange | partial range 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++, views, ranges, lazy, C++20 to find this post.
Related posts
- C++ Ranges pipeline & views
- C++ Ranges basics
- C++ Generator guide | co_yield, lazy sequences
- C++20 Ranges basics series
- C++ Ranges views and pipelines series