ericniebler / range-v3

Range library for C++14/17/20, basis for C++20's std::ranges
Other
4.11k stars 440 forks source link

filter_(equal|greater|etc..) view? #1702

Open Talkless opened 2 years ago

Talkless commented 2 years ago

I have some code like this:

|  r::views::filter([](const auto &rect) { return rect.childCount() == 3; })

It kinda feels too verbose for such simple task...

Alternatives I can think of:

const auto isValidChildCount = [](const auto &rect) { return rect.childCount() == 3; };
// ...
| r::views::filter(isValidChildCount)

Or even worse case:

| r::views::filter(std::bind_front(std::equal_to<>{}, 3), &ParentRectangle::childCount)

But wouldn't it be nice to have:

| r::views::filter_equal(3,  &ParentRectangle::childCount);

// or this in case we don't need projection:
// | r::views::filter_equal(someValue);

This would be more universal but not really less verbose than original lambda implementation:

| r::views::filter_equal(std::equal_to<>{}, &ParentRectangle::childCount, 3);

Or there's already something to simplify filtering? I'm very new to ranges... although their expressiveness is rather addictive, hence this issue :P .

brevzin commented 2 years ago

If this is something you need, you can write it yourself easily enough:

template <class V, class Proj = identity>
constexpr auto filter_equal(V value, Proj proj = {}) {
    return ranges::views::filter([=](auto&& elem){
        return ranges::invoke(proj, elem) == value;
    });
};

Which allows:

r | filter_equal(3);
r | filter_equal(3, &ParentRectangle::childCount);

But generally this is something that people use function composition for. Like Boost.Lambda2 (example):

r | views::filter(_1 == 3);
r | views::filter(_1->*&ParentRectangle::childCount > 3);
Talkless commented 2 years ago

Thanks @brevzin for great examples!

Though using Boost for that if Boost is not already used in project feels like overkill, and need to implement helper for rather common operation feels "dissapointing".

Meaning, it would be "cool" to have these helpers already in ranges & standard (and would helpful for simpler teaching, etc), though I understand that there's no much need per se if you can implement that yourself in few lines of code... But these few lines of code will be needed to be repeated in lot's of in-house codebases again and again.. :/ .

Talkless commented 2 years ago

Maybe instead of creating _equal|greater|.. better to make:

rv::filter_value(std::equal_to<>(), valueToCompare, &Foo::getName);

Or even better, maybe same-name overload against unary/binary predicates would work?

rv::filter(myCustomUnaryPredicate); // as before, default projection
rv::filter(myCustomUnaryPredicate, &Foo::getName); // as before, just with non-default projection
rv::filter(std::equal_to<>(), valueToFind); // new overload, no problem because first argument is **binary** predicate?
rv::filter(std::equal_to<>(), valueToFind, &Foo::getName); // new overload, no problem because unique three-arguments overload?

It's a little less verbose that using bind_front as in original post... though maybe for most common cases, _equa|_greater|... would be even more readable..?