If you’ve ever written a for-loop in C++ that filters, transforms, or just pokes around a container, you’ve probably thought: “This feels like more work than it should be.” C++20 heard you. Enter the ranges library—a major facelift for the Standard Template Library (STL) that brings modern, expressive data processing right to your fingertips.
Let’s walk through what the ranges library actually is, what problems it solves, and how you can start using it to write more readable, functional-style C++ code.
At its core, the ranges library is an extension of the STL that lets you chain operations—like filtering or transforming a list—without writing boilerplate loops or creating intermediate containers.
The best part? It works with your existing STL containers like std::vector, std::list, or even std::map. It’s not a brand-new thing; it’s a better way to use the STL.
Let’s look at a classic example. Say you want to take a list of numbers, filter out the odd ones, double the even ones, and then sum the result.
In pre-C++20 STL, you’d be writing something like this:
#include <vector>
#include <numeric>
#include <iostream>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5, 6};
std::vector<int> evens;
for (int n : data) {
if (n % 2 == 0) {
evens.push_back(n * 2);
}
}
int sum = std::accumulate(evens.begin(), evens.end(), 0);
std::cout << "Sum: " << sum << "\n";
return 0;
}
Not horrible, but it’s a lot of plumbing. Now, here’s the same logic with ranges:
#include <ranges>
#include <numeric>
#include <vector>
#include <iostream>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5, 6};
auto result = data
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * 2; });
int sum = std::accumulate(result.begin(), result.end(), 0);
std::cout << "Sum: " << sum << "\n";
return 0;
}
Looks clean, right? That’s the power of ranges. No intermediate containers, no extra loops, just focused, declarative logic.
At the heart of the ranges library is the concept of views. A view is like a lightweight, composable adapter over a range (such as a std::vector or std::map) that applies transformations without copying the data. Views are lazy—they don’t actually do anything until you iterate over them. Think of them like blueprints that describe how to transform data, but don’t actually build anything until you need the result.
When you write something like:
auto result = vec | std::views::filter(...);
You’re not running any filtering logic yet—you’re just setting up the pipeline. The magic only happens when you start iterating, like with a range-based for loop or an algorithm like std::ranges::for_each.
Another cool part: views can be chained together cleanly. That’s why you can filter, then transform, then reverse all in one go—without creating intermediate containers or cluttering your logic with boilerplate.
And even better: views don’t allocate memory or own the data. They just operate on top of your containers. That means better performance and fewer chances for bugs involving dangling references or unnecessary copies.
Now we get to see a concrete example of how to chain views. You can stack up views like Lego bricks:
#include <vector>
#include <ranges>
#include <iostream>
int main() {
std::vector<int> myVector = {10, 20, 30, 40, 50, 60, 70};
auto processed = myVector
| std::views::drop(2)
| std::views::take(3)
| std::views::reverse;
for (int n : processed) {
std::cout << n << " ";
}
std::cout << "\n";
return 0;
}
That’s: “skip the first two elements, take the next three, and reverse them.” All without writing a single loop or begin()/end() combo.
Here’s something neat: ranges also modernize STL algorithms by bundling the container with the range directly.
Old way:
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {4, 2, 5, 1, 3};
std::sort(vec.begin(), vec.end());
return 0;
}
New hotness:
#include <vector>
#include <algorithm>
#include <ranges>
int main() {
std::vector<int> vec = {4, 2, 5, 1, 3};
std::ranges::sort(vec);
return 0;
}
We don’t have to mention the beginning and end iterator. We just point out the container and C++ figures out the rest. Cleaner, and fewer chances for mismatched iterators or typos.
Want to pull all entries from a map with a value over 100?
#include <map>
#include <string>
#include <ranges>
#include <iostream>
int main() {
std::map<std::string, int> scores = {
{"Alice", 150}, {"Bob", 90}, {"Charlie", 200}
};
for (const auto& [name, score] : scores
| std::views::filter([](const auto& pair) { return pair.second > 100; })) {
std::cout << name << ": " << score << "\n";
}
return 0;
}
This should only print the entries that have score greater than 100. No need to write a verbose iterator loop or build a filtered copy. You just say what you mean.
Let’s take a moment to appreciate what’s happening under the hood.
Imagine you’re standing in front of a long conveyor belt. Each item that comes down the line is a number from a container. With classic loops or algorithms, you’d often stop the belt, take the item off, put it in a bin (maybe even several bins if you’re transforming it multiple times), then finally use it.
But with ranges, that conveyor belt keeps moving. The items are processed as they pass by, not before, not in bulk. That’s what we mean by lazy evaluation—operations aren’t performed until you actually need the results, like when you’re iterating over the view.
Even better, views are incredibly lightweight. They don’t store the data, they don’t allocate memory, and they don’t duplicate anything. They’re more like lenses through which you view your data differently. Want only even numbers? Put on the filter lens. Want each number doubled? Add the transform lens. But you’re always looking at the same underlying container.
And since you’re chaining views together, you also skip a whole bunch of unnecessary copies. Combine this with algorithms like std::ranges::sort or std::ranges::for_each, and you’re operating with elegance and efficiency, all in a single pass.
Performance meets readability. That’s the story of ranges.
Even with all the elegance and efficiency, ranges aren’t totally free of sharp edges.
First up: views are non-owning. They’re just lenses onto existing data, which means if the data goes away—like if you create a view over a temporary container—that view is left pointing at nothing. It’s like looking through a magnifying glass at an object that vanishes mid-glance. So always make sure the underlying container outlives the view.
Next, not all containers are created equal in the eyes of ranges. For instance, std::list doesn’t play nicely with some views like reverse, because it lacks random access. Trying to reverse a std::list view might compile, but it won’t be efficient—and in some cases, it might not work at all. Stick to containers like std::vector when chaining multiple views for the best results.
And finally, let’s talk about error messages. If you mess up your pipeline—say, mismatch types or forget a capture—it’s not always gentle. Template-heavy machinery behind the scenes can throw out compiler errors that look more like a stack trace than a helpful hint. Don’t be discouraged. Start small, build up your pipeline, and lean on your IDE for auto-completion and type hints.
If you’re feeling spicy, you can even write your own views. But that’s a whole blog post on its own.
For now, just know that ranges are extensible, composable, and designed with modern C++ idioms in mind.
The C++20 ranges library is all about writing less code that does more. It borrows ideas from functional programming, embraces composition, and makes everyday operations on containers a whole lot cleaner.
If you’re working with data, it’s one of the most empowering features C++20 has to offer. Once you get the hang of it, you’ll never want to go back to hand-rolled loops again.
So go on—ditch the boilerplate and let your containers flow. If you’ve already started exploring ranges, I’d love to hear about your favorite tricks or biggest surprises. Drop a comment below and share your experience!
Want to master ranges and other powerful features of C++20? Check out my C++20 Masterclass and take your skills to the next level. With hands-on examples, expert guidance, and tons of assignments and exercises. If you’re rusty on modern C++ this course is your ultimate guide to stepping up your game. Don’t miss out!
Fluid and dynamic user interfaces for desktop, mobile and embedded devices.
Learn and Master Qt, QML and C++ with Minimal Effort, Time and Cost
Clear, up front courses on Qt. We have made it our business to provide the best online learning resources for Qt Development. We put in the required effort to make sure the code resources coming with the courses are up to date and use the latest tools to reduce the hustle for students as much as possible. We already have the basic courses on Qt C++ and QML out and there are more in the pipeline.