Skip to Content
Course content

244: Range Algorithms vs Classic Algorithms

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I've noticed a recurring trend when developers move from C++17 to C++20: they treat the new std::ranges namespace as nothing more than a "convenience wrapper." They assume that std::ranges::sort(vec) is just a shorthand for std::sort(vec.begin(), vec.end()) and that there isn't any real architectural reason to prefer one over the other.

If that were true, Ranges would be a minor quality-of-life update. But it's not. When you treat them as mere syntactic sugar, you miss the most powerful part of the library: projections and lazy evaluation. To see why the "just sugar" mindset is wrong, look at how we used to handle a list of custom objects.

Stop writing boilerplate comparators

Imagine you have a User struct and you want to sort a vector of users by their ID. In the classic world, you're forced to write a lambda that describes how to compare two User objects. It looks like this:

std::sort(users.begin(), users.end(), [](const User& a, const User& b) {
    return a.id < b.id;
});

It's not terrible, but it's repetitive. You're manually extracting the member you care about and then applying the comparison. Now, look at the Ranges version. I can pass the member pointer as a projection:

std::ranges::sort(users, {}, &User::id);

That second argument (the {}) tells C++ to use the default comparator (less-than), and the third argument tells it: "Before you compare these two objects, project them down to their id first." This is a massive win for readability. I'm no longer describing how to compare two users; I'm telling the algorithm what attribute to sort by.

Composition over temporary containers

The second big shift is the move from "eager" algorithms to "lazy" views. In the classic STL, if you wanted to filter a list of numbers and then square the results, you usually had to create a temporary vector to hold the filtered results before passing them to another algorithm. It's a lot of memory allocation for something that should be a simple pipeline.

With std::views, we can compose operations using the pipe operator (|). I personally love this because it reads like a data pipeline in a functional language, but it performs with C++ efficiency.

auto result = numbers 
             | std::views::filter([](int n) { return n % 2 == 0; }) 
             | std::views::transform([](int n) { return n * n; });

Here is the critical distinction: result is not a new vector. It's a view. No filtering or squaring has actually happened yet. The work is deferred until you actually iterate over result in a loop. If you only ever read the first two elements of that result, the computer never bothers to process the rest of the list. You cannot do that with classic algorithms without writing your own custom iterator logic from scratch.

When to stick with the classics

You might be wondering if you should delete all your .begin() and .end() calls. Not quite. Classic algorithms are still the right tool when you are working with legacy codebases or when you specifically need to operate on a sub-range that isn't easily defined by a view. However, for 90% of modern C++ development, if you can use a range, you should. It's safer, more concise, and significantly more flexible.




📋 Practical Task

Build a Product Price Filter Pipeline

You are building a simplified inventory system. You have a Product struct with a name (string) and a price (double). Your goal is to create a pipeline that processes a std::vector<Product> and produces a view of the names of all products that cost more than $50.00.

Requirements:

  • Define a Product struct.
  • Create a vector containing at least five products with varying prices.
  • Use std::views::filter to keep only products with a price > 50.0.
  • Use std::views::transform to extract only the name from the filtered products.
  • Print the resulting names using a for loop.
  • Constraint: You must use the pipe operator (|) and must not create any intermediate vectors to store the filtered results.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.