Ranges TS中Proj与IndirectUnaryPredicate模板参数作用及结合用途问询
Alright, let's unpack these two concepts from the Ranges TS—they're key to making the new range-based algorithms far more flexible than the old STL ones, but they can feel abstract at first.
What is Proj (Projection)?
Think of Proj as a data extractor or transformer. It's a callable object (like a function pointer, lambda, or functor) that takes an element from the range (via the iterator's dereference) and converts it into another value.
The whole point of projections is to separate "how to get the data we care about" from "how to operate on that data". Instead of writing a custom predicate that digs into the element's internals, you can pass a projection to pull out the relevant property, and keep your predicate focused on the logic.
For example, if you have a Person struct:
struct Person { std::string name; int age; };
Instead of writing a sort comparator that accesses age directly, you can use a projection:
std::vector<Person> people = {{"Alice", 25}, {"Bob", 35}, {"Charlie", 28}}; // Sort by age using a projection ranges::sort(people, std::less{}, &Person::age);
Here, &Person::age is the projection—it takes a Person and returns their int age. The comparator (std::less{}) only needs to work with ints, not the entire Person type.
What is IndirectUnaryPredicate?
This is a predicate (a callable that returns bool) designed to work with indirectly referenced values—meaning it operates on the value that an iterator points to, not the iterator itself.
Formally, a type Pred satisfies IndirectUnaryPredicate for an iterator type I if calling Pred with *it (where it is an iterator of type I) is valid, and returns a type convertible to bool.
In practice, this just means your predicate can take the element type (or the projected type, as we'll see) and return a boolean. For example, in ranges::find_if, the predicate you pass is an IndirectUnaryPredicate:
// Find the first Person older than 30 (without projection) auto it = ranges::find_if(people, [](const Person& p) { return p.age > 30; });
The lambda here is an IndirectUnaryPredicate because it accepts the dereferenced iterator value (a Person&) and returns bool.
How Proj works with IndirectUnaryPredicate
When you combine a projection with an indirect unary predicate, the algorithm does this in order:
- Dereference the iterator to get the original element.
- Apply the projection to that element to get a transformed value.
- Pass that transformed value to the
IndirectUnaryPredicateto check the condition.
This is where the real power comes in—it lets you reuse predicates across different types, as long as you can project to a type the predicate understands.
Using our Person example again:
// Reusable predicate: checks if an int is greater than 30 auto is_over_30 = [](int age) { return age > 30; }; // Use it with a projection to find the first Person over 30 auto it = ranges::find_if(people, is_over_30, &Person::age);
Here, is_over_30 doesn't know anything about Person—it just works with ints. The projection &Person::age bridges the gap, converting each Person to their age before passing it to the predicate.
If later you have an Employee struct with an age member, you can reuse the same is_over_30 predicate without changing it:
struct Employee { std::string id; int age; }; std::vector<Employee> staff = {{"E1", 22}, {"E2", 32}, {"E3", 27}}; auto it_staff = ranges::find_if(staff, is_over_30, &Employee::age);
Why this matters?
- Separation of concerns: Your predicate handles the business logic (is this value >30?), while the projection handles data extraction (get the age from this object).
- Reusability: Predicates become generic, usable across any type that can project to the right value.
- Readability: The code clearly states what you're checking (
is_over_30) and what data you're checking it against (&Person::age), instead of mixing both in a single lambda.
内容的提问来源于stack exchange,提问作者j6t

