Can I use C++20 ranges to break when matched count is greater than some threshold?

Viewed 133

Consider the following pre ranges code:

std::vector<int> v(1000*1000);
bool count_gt_5_v1(int val){
    return std::count(v.begin(), v.end(), val)>5;
}

It looks nicer than the raw loop, but it can be very inefficient if val is very common in v.

Is there any way to use C++20 ranges so that iteration will stop after I encounter val 6 times. In other words I am looking for a way to introduce a break when my condition is satisfied. I have this abomination, that seems to work, but it is much much uglier than raw for loop.

bool count_gt_5_v2(int val){
    int cnt=0;
    auto span = std::ranges::views::take_while(v,[&cnt, &val]
    (const auto elem)
    {
        cnt+=elem==val; 
        return cnt<6;
    });
    std::ranges::distance(span);
    return cnt==6;
}

Link to full code: https://godbolt.org/z/86djdK

1 Answers

You could do this:

auto matches = v | rv::filter([=](int i){ return i == val; })
                 | rv::take(6);
return ranges::distance(matches) == 6;

Or, better:

auto matches = v | rv::filter([=](int i){ return i == val; });
return not ranges::empty(matches | rv::drop(5));

This attempt:

std::ranges::views::take_while(v, [&cnt, &val](const auto elem){
    cnt+=elem==val; 
    return cnt<6;
});

doesn't meet the requirements of take_while. All of the predicates in ranges have to be equality-preserving - same inputs, same output. Here, that's not the case - if we call the predicate twice on a single element, we'd get different output. So that's undefined behavior.

Related