Home > Net >  How to combine views::enumerate and views::filter?
How to combine views::enumerate and views::filter?

Time:09-12

I have a container that I want to filter.
I also need the index of the items later.
For that I want to use something like this:

auto items_to_remove = items | ranges::view::enumerate | ranges::view::filter(/*???*/);

What type do I have to use for the filter function?

CodePudding user response:

Use std::pair<int, T>. .first is the index. .second is the element.

int main() {
    auto v = std::vector{0, 1, 23, 3, 42, 50, 81, 4, -1};
    auto f = [](std::pair<int, int> p) { return p.first % 2 == 0 && p.second >= 10; };
    for (auto [i, e] : v | views::enumerate | views::filter(f)) {
        std::cout << i << " -> " << e << "\n";
    }
}

( " << e << "\n"; } } '),l:'5',n:'0',o:'C++ source #1',t:'0')),k:55.75156902803084,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((g:!((h:compiler,i:(compiler:g122,filters:(b:'0',binary:'1',commentOnly:'0',demangle:'0',directives:'0',execute:'0',intel:'0',libraryCode:'0',trim:'1'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:1,lang:c++,libs:!((name:rangesv3,ver:'0120')),options:'--std=c++20',selection:(endColumn:1,endLineNumber:1,positionColumn:1,positionLineNumber:1,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1,tree:'1'),l:'5',n:'0',o:'x86-64 gcc 12.2 (C++, Editor #1, Compiler #1)',t:'0')),k:50,l:'4',m:64.14556842336175,n:'0',o:'',s:0,t:'0'),(g:!((h:output,i:(compilerName:'x86-64 gcc 12.1',editorid:1,fontScale:14,fontUsePx:'0',j:1,wrap:'1'),l:'5',n:'0',o:'Output of x86-64 gcc 12.2 (Compiler #1)',t:'0')),header:(),l:'4',m:35.85443157663825,n:'0',o:'',s:0,t:'0')),k:44.24843097196916,l:'3',n:'0',o:'',t:'0')),l:'2',n:'0',o:'',t:'0')),version:4" rel="nofollow noreferrer">Godbolt Demo)

CodePudding user response:

Adding an enumerate::view makes the element that is passed to the filter a

ranges::common_pair<size_t, int&>&

So, just take the common_pair by const&.

auto f = [](const ranges::common_pair<size_t, int&>& pair) {
    // filter on pair.first to filter on the index
    // filter on pair.second to filter on the actual value in the vector
};

or simply:

auto f = [](auto&& pair) { ... }

Then

for (auto&&[idx, element] : v | views::enumerate | views::filter(f)) {
    element  = 100;                            // elements are mutable here
    std::cout << idx << ' ' << element << '\n';
}

Demo

CodePudding user response:

You should use auto lambdas with ranges, adding it static asserts if necessary.

If you don't want a copy happening you can simply have a predicate that takes its argument by value.

auto pred = [](auto pair) -> bool { return /*do something with T&*/ pair.second; };

The instantiated type of pair will be ranges::common_pair<size_t, T&> which you can copy around without copying the underlying T. You can call pair.second to get a T& in you predicate.

Shower around the following static assert just to be sure or delete the copy constructor for T because it is a very annoying when this copy turns out to be your bug.

std::vector<std::optional<T>> elements;
for (auto [index, opt] : elements
    | ranges::views::filter([](auto pair) -> bool { return pair.second; })
    )
{
    static_assert(std::is_reference_v<decltype(opt)>);
    opt->MethodOfT();
}

To understand why opt is a reference here see my question

  • Related