7

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

Is this even a good idea?

Mike Kinghan
  • 55,740
  • 12
  • 153
  • 182
LambdaScientist
  • 435
  • 2
  • 13
  • What are you doing with the elements of the container that you are storing? Do you actually need them or are you just saving them to get a sum of them? – NathanOliver Jan 22 '19 at 15:50
  • Usually we are saving off an ID for later use. – LambdaScientist Jan 22 '19 at 15:53
  • OK. Then the `transform_if` you linked to is probably what you want. – NathanOliver Jan 22 '19 at 15:56
  • when stl algorithms are your hammer then almost every problem actually is a nail ;) – 463035818_is_not_an_ai Jan 22 '19 at 15:56
  • 3
    See also: [Why is there no transform_if in the C++ standard library?](https://stackoverflow.com/questions/23579832/why-is-there-no-transform-if-in-the-c-standard-library) and [`boost::transform_if`](https://www.boost.org/doc/libs/1_62_0/libs/compute/doc/html/boost/compute/transform_if.html) – Igor Tandetnik Jan 22 '19 at 15:57
  • With C++20 it will be possible to compose algorithm on ranges element wise (pipe operator of the range library :)). – Oliv Jan 22 '19 at 15:58
  • It may be possible to create a transforming output iterator to take your input type and extract the output type from it? Then just use `std::copy_if`. – Galik Jan 22 '19 at 16:53

2 Answers2

6

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
6

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.

IceFire
  • 4,016
  • 2
  • 31
  • 51
  • 2
    `std::iterator_traits::value_type` rather than `It::value_type` (`Foo*` has no `::value_type`, but does have a `iterator_traits::value_type`) – Yakk - Adam Nevraumont Jan 22 '19 at 21:08
  • @Yakk-AdamNevraumont great comment, thank you! I have changed this and also used `std::begin` and `std::end`. Now, in fact, it also works with a plain array – IceFire Jan 23 '19 at 09:08
  • The function and one of its parameters are both called `foo` – ugh… how about giving the function a meaningful name such as `copy_member_if`? – Arne Vogel Jan 24 '19 at 12:33
  • 1
    @ArneVogel I changed it a bit. This is only proof of concept, though, not production-ready code. Still, thanks for pointing out. `copy_member_if` would be misleading, though, because the function is more general – IceFire Jan 24 '19 at 12:37