7

I know algorithms (e.g. sort) in ranges support projection, but it seems to me that there is no way to get that functionality for views... Am I right?

As an example consider following working code:

#include <algorithm>
#include <ranges>
#include <vector>
#include <iostream>

enum Color {
   Red,
   Green,
   Blue
}; 
struct Cat {
   int age;
   Color color;
};

int main() {
    std::vector<Cat> cats{{.age = 10,.color=Color::Red}, {.age = 20,.color=Color::Blue}, {.age = 30,.color=Color::Green}};
    auto is_red = [](const auto& cat) {return cat.color == Color::Red;};
    for (const auto& cat: cats | std::views::filter(is_red)) {
        std::cout << cat.age << std::endl; 
    }
}

Is there a way to remove the lambda and do something like:

for (const auto& cat: cats | std::views::filter(&Cat::color, Color::Red) {

note: my question is for member variable projection, but obviously in real code member function calls would be also needed.

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277

2 Answers2

6

Do C++ ranges support projections in views?

No (although range-v3 did).

Is there a way to remove the lambda and do something like:

std::views::filter(&Cat::color, Color::Red)

That wouldn't really be how this would work with projections anyway. It would have been:

filter([](Color c){ return c == Color::Red; }, &Cat::color)

Which you can reduce this if you have an equals that returns a predicate:

filter(equals(Color::Red), &Cat::color)

But adding projections to algorithms is kind of unnecessary. You can always provide a projection manually. Using Boost.Hof's appropriately-named proj function adapter, which satisfies proj(p, f)(xs...) == f(p(xs)...) (i.e. we're applying p on each argument before passing them into f):

filter(proj(&Cat::color, [](Color c){ return c == Color::Red; }))

or, shorter:

filter(proj(&Cat::color, _ == Color::Red))

Demo.


Even in the range-v3 implementation, it's not that remove_if_view had explicit support for projections. It's that the overload that took a projection manually composed the predicate for you as compose(pred, proj). In range-v3, compose(f, g)(xs...) can mean either f(g(xs...)) or f(g(xs)...) depending on how g is invocable. So in this case, it's a projection rather than function composition. In Boost.Hof, there is are distinct compose and proj adaptors for the two cases.

Barry
  • 286,269
  • 29
  • 621
  • 977
  • Yes, my use of projection was wrong, I was looking for something like projection + nonexisting filter (in another universe where current filter is named filter_if and filter is checking values, not predicates). – NoSenseEtAl May 20 '21 at 19:26
  • BTW just noticed the _ in answer, it is nice, but I am a bit concerned that so many libs(e.g. gmock) use it... But actually that or lambda2 are great for simple cases. – NoSenseEtAl May 21 '21 at 10:05
0

Other answer is correct since my use of projection was wrong, I wanted a projection and also equivalent of (assuming std::filter in C++ is named std::filter_if) imaginary std::filter that uses == for comparison, like we have std::count/std::count_if std::find/std::find_if pairs in STL.

If somebody needs something like this this seems to work, but it has a potential to be slow since perfect forwarding in lambdas is tricky to do, so I did not bother.

( I use keep since it is nicer name for me, but it can also be named filter_memb).

template<typename Member, typename Val>
auto keep_memb(const Member& memb, const Val& val){
    return std::views::filter([=](auto& object){return std::invoke(memb, object) == val; } );
}

godbolt

NoSenseEtAl
  • 28,205
  • 28
  • 128
  • 277