Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement something like std::copy_if but apply a function before inserting into a different container

Tags:

c++

stl

c++14

Full disclosure, this may be a hammer and nail situation trying to use STL algorithms when none are needed. I have seen a reappearing pattern in some C++14 code I am working with. We have a container that we iterate through, and if the current element matches some condition, then we copy one of the elements fields to another container.

The pattern is something like:

 for (auto it = std::begin(foo); it!=std::end(foo); ++it){
    auto x = it->Some_member;
    // Note, the check usually uses the field would add to the new container. 
    if(f(x) && g(x)){ 
      bar.emplace_back(x);
    }
  }

The idea is almost an accumulate where the function being applied does not always return a value. I can only think of a solutions that either

  • Require a function for accessing the member your want to accumulate and another function for checking the condition. i.e How to combine std::copy_if and std::transform?
  • Are worse then the thing I want to replace.

Is this even a good idea?

like image 831
LambdaScientist Avatar asked Jan 22 '19 15:01

LambdaScientist


2 Answers

A quite general solution to your issue would be the following (working example):

#include <iostream>
#include <vector>
using namespace std;

template<typename It, typename MemberType, typename Cond, typename Do>
void process_filtered(It begin, It end, MemberType iterator_traits<It>::value_type::*ptr, Cond condition, Do process)
{
    for(It it = begin; it != end; ++it)
    {
        if(condition((*it).*ptr))
        {
            process((*it).*ptr);
        }
    }
}

struct Data
{
    int x;
    int y;
};

int main()
{
    // thanks to iterator_traits, vector could also be an array;
    // kudos to @Yakk-AdamNevraumont
    vector<Data> lines{{1,2},{4,3},{5,6}};

    // filter even numbers from Data::x and output them
    process_filtered(std::begin(lines), std::end(lines), &Data::x, [](int n){return n % 2 == 0;}, [](int n){cout << n;});

    // output is 4, the only x value that is even

    return 0;
}

It does not use STL, that is right, but you merely pass an iterator pair, the member to lookup and two lambdas/functions to it that will first filter and second use the filtered output, respectively.

I like your general solutions but here you do not need to have a lambda that extracts the corresponding attribute.

Clearly, the code can be refined to work with const_iterator but for a general idea, I think, it should be helpful. You could also extend it to have a member function that returns a member attribute instead of a direct member attribute pointer, if you'd like to use this method for encapsulated classes.

like image 141
IceFire Avatar answered Oct 02 '22 20:10

IceFire


Sure. There are a bunch of approaches.

  1. Find a library with transform_if, like boost.

  2. Find a library with transform_range, which takes a transformation and range or container and returns a range with the value transformed. Compose this with copy_if.

  3. Find a library with filter_range like the above. Now, use std::transform with your filtered range.

  4. Find one with both, and compose filtering and transforming in the appropriate order. Now your problem is just copying (std::copy or whatever).

  5. Write your own back-inserter wrapper that transforms while inserting. Use that with std::copy_if.

  6. Write your own range adapters, like 2 3 and/or 4.

  7. Write transform_if.

like image 25
Yakk - Adam Nevraumont Avatar answered Oct 02 '22 19:10

Yakk - Adam Nevraumont