106

A use case emerged when wanting to do a contitional copy (1. doable with copy_if) but from a container of values to a container of pointers to those values (2. doable with transform).

With the available tools I can't do it in less than two steps :

#include <vector>
#include <algorithm>

using namespace std;

struct ha { 
    int i;
    explicit ha(int a) : i(a) {}
};

int main() 
{
    vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector
    // GOAL : make a vector of pointers to elements with i < 2
    vector<ha*> ph; // target vector
    vector<ha*> pv; // temporary vector
    // 1. 
    transform(v.begin(), v.end(), back_inserter(pv), 
        [](ha &arg) { return &arg; }); 
    // 2. 
    copy_if(pv.begin(), pv.end(), back_inserter(ph),
        [](ha *parg) { return parg->i < 2;  }); // 2. 

    return 0;
}

Ofcourse we could call remove_if on pv and eliminate the need for a temporary, better yet though, it's not difficult to implement (for unary operations) something like this :

template <
    class InputIterator, class OutputIterator, 
    class UnaryOperator, class Pred
>
OutputIterator transform_if(InputIterator first1, InputIterator last1,
                            OutputIterator result, UnaryOperator op, Pred pred)
{
    while (first1 != last1) 
    {
        if (pred(*first1)) {
            *result = op(*first1);
            ++result;
        }
        ++first1;
    }
    return result;
}

// example call 
transform_if(v.begin(), v.end(), back_inserter(ph), 
[](ha &arg) { return &arg;      }, // 1. 
[](ha &arg) { return arg.i < 2; });// 2.
  1. Is there a more elegant workaround with the available C++ standard library tools ?
  2. Is there a reason why transform_if does not exist in the library? Is the combination of the existing tools a sufficient workaround and/or considered performance wise well behaved ?
einpoklum
  • 118,144
  • 57
  • 340
  • 684
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • (IMO) The name `transform_if` implies "only transform if a certain predicate is satisfied". A more descriptive name for what you want would be `copy_if_and_transform`! – Oliver Charlesworth May 10 '14 at 10:23
  • 1
    @OliCharlesworth, actually `copy_if` also implies "only copy if a certain predicate is satisfied". It's equally ambiguous. – Shahbaz May 10 '14 at 10:25
  • @Shahbaz: But that's what `copy_if` does, right? – Oliver Charlesworth May 10 '14 at 10:27
  • 2
    I wouldn't be suprised if disputes about the name of such a thing were the actuall reason for not implementing it !! – Nikos Athanasiou May 10 '14 at 10:27
  • @Shahbaz no. Because, if the predicate is satisfied, it is clear that `copy_if` will **not** copy, but `transform_if` might (it would just _not transform_) – sehe May 10 '14 at 10:27
  • 6
    Maybe I'm missing something in these comments, but how could `transform_if` possibly copy those elements it doesn't transform, if the transformation can be to a different incompatible type? The implementation in the question is exactly what I would expect such a function to do. –  May 10 '14 at 10:33
  • @hvd that's an excellent point, there – sehe May 10 '14 at 10:37
  • @olicharleworth, perhaps I misunderstood, but anyway the ambiguity is this: `verb_if` could mean "either verb all or none" based on predicate, or "verb only those that satisfy the predicate". I thought you were mentioning this ambiguity, although I notice now that the first meaning is quite useless. – Shahbaz May 11 '14 at 12:04
  • does the predicate apply before or after the transform? In any case with the new for notation there is less need for algorithms that traverse every element of the collectioin. – CashCow Apr 08 '15 at 09:57

11 Answers11

39

The standard library favours elementary algorithms.

Containers and algorithms should be independent of each other if possible.

Likewise, algorithms that can be composed of existing algorithms are only rarely included, as shorthand.

If you require a transform if, you can trivially write it. If you want it /today/, composing of ready-mades and not incur overhead, you can use a range library that has lazy ranges, such as Boost.Range, e.g.:

v | filtered(arg1 % 2) | transformed(arg1 * arg1 / 7.0)

As @hvd points out in a comment, transform_if double result in a different type (double, in this case). Composition order matters, and with Boost Range you could also write:

 v | transformed(arg1 * arg1 / 7.0) | filtered(arg1 < 2.0)

resulting in different semantics. This drives home the point:

it makes very little sense to include std::filter_and_transform, std::transform_and_filter, std::filter_transform_and_filter etc. etc. into the standard library.

See a sample Live On Coliru

#include <boost/range/algorithm.hpp>
#include <boost/range/adaptors.hpp>

using namespace boost::adaptors;

// only for succinct predicates without lambdas
#include <boost/phoenix.hpp>
using namespace boost::phoenix::arg_names;

// for demo
#include <iostream>

int main()
{
    std::vector<int> const v { 1,2,3,4,5 };

    boost::copy(
            v | filtered(arg1 % 2) | transformed(arg1 * arg1 / 7.0),
            std::ostream_iterator<double>(std::cout, "\n"));
}
sehe
  • 374,641
  • 47
  • 450
  • 633
  • 39
    Well, the problem is that the standard algorithms can't be easily composed, because they are not lazy. – Jan Hudec May 10 '14 at 10:32
  • 2
    @JanHudec Indeed. (sorry about that? :)). Which is why you use a library (much like you use AMP/TBB for concurrency, or Reactive Extensions in C#). Many people are working on a range proposition + implementation for inclusion into the standard. – sehe May 10 '14 at 10:38
  • @sehe Do you have any links to any of does propsitions? – Viktor Sehr May 10 '14 at 10:46
  • @hvd I acknowledged your point made in the comments, and used it to make it very clear how it's not feasible to cater for all useful compositions of algorithms in the standard library. Instead, we should hope for more composable concepts in the standard library! – sehe May 10 '14 at 10:47
  • 1
    @ViktorSehr [This page](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3371.html) lists [N1871](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2005/n1871.html), [N2068](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2006/n2068.html) and maybe [N3350](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3350.html). Besides that there are Boost Range, Eric Niebler's **[range-v3](https://github.com/ericniebler/range-v3)** and some other significant efforts – sehe May 10 '14 at 10:51
  • 2
    @sehe +1 Very impressive, I have learned something new today! Would you be so kind as to tell us who are not familiar with Boost.Range and Phoenix where we can find the documentation/examples that explains how to use `boost::phoenix` to make such nice predicates without lambdas? A quick google search returned nothing relevant. Thanks! – Ali May 10 '14 at 12:33
  • @Ali Boost Phoenix is sort of like Boost Lambda on steroids. It originated as a subproject of Boost Spirit, but has been spun off as it's own library for years now. This heritage might explain the lack of compelling examples in the docs. You might want to look at Spirit's semantic actions (which are Phoenix _actors_) and [there are some things that lambdas can't quite do the way Phoenix does them](http://stackoverflow.com/questions/6160931) – sehe May 10 '14 at 13:44
  • @sehe Thanks. It is very disappointing that we have such a great tool but not examples showing what we can do with it / how to use it... :( – Ali May 10 '14 at 14:14
  • 2
    I disagree regarding the "it makes very little sense to include std::filter_and_transform" part. Other programming languages also provide this combination in their "standard library". It totally makes sense to iterate over a list of elements once, transforming them on the fly, while skipping those that cannot be transformed. Other approaches require more than one pass. Yes, you can use BOOST, but the question actually was "Why is there no transform_if in the C++ standard library?". And IMHO, he is right to question this. There should be such a function in the standard library. – Jonny Dee May 28 '18 at 07:49
  • @JonnyDee Lots of things "totally make sense" to do. That's the point: there is no end to the list of equally useful compositions. The current, dated, STL design couldn't possibly cover them all, and they would end up getting arcane and confusing names (that was what the sample was intended for). As you can see, I was _literally_ answering why `transform_if` wouldn't be in the standard library. About the normative question ("should it be in the standard library") I agree with your opinion. But that's it: an opinion. – sehe May 28 '18 at 09:23
  • @JonnyDee You're welcome to post your own answer. I'm also curious about how other language do supply `transform_if`. In my experience they *all* use composable abstractions instead (of the filter+map variation). – sehe May 28 '18 at 09:24
  • 2
    @sehe Regarding "they all use composable abstractions": that's not true. Rust, for instance, has exactly such a `transform_if`. It's called [`filter_map`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map). However, I must admit it's there to simplify code but, on the other hand, one could apply the same argument in the C++ case. – Jonny Dee May 28 '18 at 13:21
  • @JonnyDee That's an interesting example. It's a bit different in that it mandates the combination of predicate and projection (or you could look at it the other way and say the predicate is hardcoded), but I suppose you could shoe-horn it: `filter_map(range, make_optional_transform(predicate, projection))` and get equivalent code generation. I do wonder why `filter_map` was added there. (On the other hand of the spectrum we have Python which, in its quest to have One Way To Do It dropped things like `reduce()`: https://stackoverflow.com/questions/181543/what-is-the-problem-with-reduce) – sehe May 28 '18 at 13:50
  • It appears their docs suggest that it's ["because it's much nicer to hide the `Option`"](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.filter_map). And although the _standard_ implementation seemingly is not in terms of `map` and `filter`, the point still stands: Rust, too, opted to supply the composable primitives, instead of hardcoding a limited set of compositions. (`filter_map` seems to be the exception to confirm the rule then). // @JonnyDee – sehe May 28 '18 at 13:55
  • @sehe "`filter_map` seems to be the exception to confirm the rule then": you're right, but this is exactly the function this question is about. The question was not: "Why aren't there all arbitrary combinations of composable primitives already implemented in the standard library?" – Jonny Dee May 28 '18 at 14:30
  • @JonnyDee Fair point. Perhaps if `optional` existed in 20[03,11,14] it would have been proposed. The whole functional programming renaissance came after the (groundbreaking) STL revolution. I for one welcome a new era, new standard library to rule another 2 decades. – sehe May 28 '18 at 17:15
8

After just finding this question again after some time, and devising a whole slew of potentially useful generic iterator adaptors I realized that the original question required NOTHING more than std::reference_wrapper.

Use it instead of a pointer, and you're good:

Live On Coliru

#include <algorithm>
#include <functional> // std::reference_wrapper
#include <iostream>
#include <vector>

struct ha {
    int i;
};

int main() {
    std::vector<ha> v { {1}, {7}, {1}, };

    std::vector<std::reference_wrapper<ha const> > ph; // target vector
    copy_if(v.begin(), v.end(), back_inserter(ph), [](const ha &parg) { return parg.i < 2; });

    for (ha const& el : ph)
        std::cout << el.i << " ";
}

Prints

1 1 
sehe
  • 374,641
  • 47
  • 450
  • 633
7

The new for loop notation in many ways reduces the need for algorithms that access every element of the collection where it is now cleaner to just write a loop and put the logic inplace.

std::vector< decltype( op( begin(coll) ) > output;
for( auto const& elem : coll )
{
   if( pred( elem ) )
   {
        output.push_back( op( elem ) );
   }
}

Does it really provide a lot of value now to put in an algorithm? Whilst yes, the algorithm would have been useful for C++03 and indeed I had one for it, we don't need one now so no real advantage in adding it.

Note that in practical use your code won't always look exactly like that either: you don't necessarily have functions "op" and "pred" and may have to create lambdas to make them "fit" into algorithms. Whilst it is nice to separate out concerns if the logic is complex, if it is just a matter of extracting a member from the input type and checking its value or adding it to the collection, it's a lot simpler once again than using an algorithm.

In addition, once you are adding some kind of transform_if, you have to decide whether to apply the predicate before or after the transform, or even have 2 predicates and apply it in both places.

So what are we going to do? Add 3 algorithms? (And in the case that the compiler could apply the predicate on either end of the convert, a user could easily pick the wrong algorithm by mistake and the code still compile but produce wrong results).

Also, if the collections are large, does the user want to loop with iterators or map/reduce? With the introduction of map/reduce you get even more complexities in the equation.

Essentially, the library provides the tools, and the user is left here to use them to fit what they want to do, not the other way round as was often the case with algorithms. (See how the user above tried to twist things using accumulate to fit what they really wanted to do).

For a simple example, a map. For each element I will output the value if the key is even.

std::vector< std::string > valuesOfEvenKeys
    ( std::map< int, std::string > const& keyValues )
{
    std::vector< std::string > res;
    for( auto const& elem: keyValues )
    {
        if( elem.first % 2 == 0 )
        {
            res.push_back( elem.second );
        }
    }
    return res;
}         

Nice and simple. Fancy fitting that into a transform_if algorithm?

CashCow
  • 30,981
  • 5
  • 61
  • 92
  • *we don't need one now so no real advantage in adding it*. Let me answer to that with an analogy. We can write in assembly, so there's no real advantage in writing in C. The fact that a low-level fallback of a for-loop is now more convenient than before doesn't change the fact it's a low-level construct that leaves much more space for errors, is lengthier and harder to understand than algorithm functions. – Bartek Banachewicz Apr 09 '15 at 10:32
  • 4
    If you think my code above has more room for errors than a transform_if with 2 lambdas, one for the predicate and one for the transform, then please explain it. Assembly, C and C++ are different languages and have different places. The only place the algorithm may be at an advantage over a loop is the ability to "map/reduce" so run concurrently over large collections. However this way the user can control whether to loop in sequence or map-reduce. – CashCow Apr 09 '15 at 10:41
  • 4
    In a proper functional approach functions for predicate and mutator are well defined blocks which make the construct properly structured. For loop body can have arbitrary things in it, and every loop you see has to be carefully analyzed to understand its behavior. – Bartek Banachewicz Apr 09 '15 at 10:48
  • 3
    Leave the proper functional approach for proper functional languages. This is C++. – CashCow Apr 09 '15 at 10:49
  • CashCow as I think that functional programming is perfectly sane, doable and appropriate in C++, I'm keeping my downvote. It's not by any means personal, though. – Bartek Banachewicz Apr 09 '15 at 11:10
  • 1
    @CashCow Agreed. However, since you asked: http://coliru.stacked-crooked.com/a/9020224d60d18169 – sehe Apr 09 '15 at 12:08
  • 3
    "Fancy fitting that into a transform_if algorithm?" That *is* a "transform_if algorithm", except it has everything hardcoded. – R. Martinho Fernandes Apr 09 '15 at 12:13
  • 2
    It performs the equivalent of a transform_if. Just that algorithms are supposed to simplify your code or improve it in some way, not make it more complicated. – CashCow Apr 09 '15 at 12:21
  • @sehe yeah, the lengths to which some people will go. I think a big issue of transform_if, copy_if, find etc is it encourages users to use linear search too much. Sometimes it's useful: small collections, one-off data sweeps etc. For general use you should try to partition the data better. remove_if is good though because you want to actually remove the items. – CashCow Apr 09 '15 at 12:26
  • 1
    @CashCow It's really really funny that you worry about encouraging linear searches and countering that by encouraging loops. I'm not in favour of any of these, but that argument sinks pretty fast. Actually ranged for doesn't support anything _but_ linear iteration. (One notable advantage of my range example was genericity, in case you missed this minor benefit. Again, I was just responding to the explicit challenge. Not saying you should write it in that particular way) – sehe Apr 09 '15 at 12:32
  • I encourage loops when it makes the code easier to read / follow. A handwritten loop puts responsibility into the hands of the person who wrote it. An algorithm provided and you tend to trust it.. Yes, the standard says it's O(N). And its genericity means someone might use it with an associative container for a ranged key which can be done with lower_bound on the first and upper_bound on the second. – CashCow Apr 09 '15 at 12:43
  • 1
    I don't get it. You trust the programmer to do the right thing, but if they use algorithms they suddenly gets ---more stupid--- it wrong? Also, didn't you just imply that the "non-loop" approaches are more complicated? Doesn't it sort of make sense, then, that the programmers lacking understanding of what they do will resort to loops? Doesn't that make it slightly more likely that programmers who don't use algorithms might not know what they are doing? (This is certainly what I tend to see). Again, I write loops liberally, and ranged-for is a huge blessing, but I know what I see around me. – sehe Apr 09 '15 at 13:09
  • Don't programs without loops terminate rather quickly? – Martin James Apr 09 '15 at 13:12
  • 1
    Well that is not expressive + the code doesn't look linear. ranges (boost::range or range-v3) would be the best solution to that problem... – AlexTheo Aug 01 '19 at 10:36
7

Sorry to resurrect this question after so long. I had a similar requirement recently. I solved it by writing a version of back_insert_iterator that takes a boost::optional:

template<class Container>
struct optional_back_insert_iterator
: public std::iterator< std::output_iterator_tag,
void, void, void, void >
{
    explicit optional_back_insert_iterator( Container& c )
    : container(std::addressof(c))
    {}

    using value_type = typename Container::value_type;

    optional_back_insert_iterator<Container>&
    operator=( const boost::optional<value_type> opt )
    {
        if (opt) {
            container->push_back(std::move(opt.value()));
        }
        return *this;
    }

    optional_back_insert_iterator<Container>&
    operator*() {
        return *this;
    }

    optional_back_insert_iterator<Container>&
    operator++() {
        return *this;
    }

    optional_back_insert_iterator<Container>&
    operator++(int) {
        return *this;
    }

protected:
    Container* container;
};

template<class Container>
optional_back_insert_iterator<Container> optional_back_inserter(Container& container)
{
    return optional_back_insert_iterator<Container>(container);
}

used like this:

transform(begin(s), end(s),
          optional_back_inserter(d),
          [](const auto& s) -> boost::optional<size_t> {
              if (s.length() > 1)
                  return { s.length() * 2 };
              else
                  return { boost::none };
          });
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • 1
    Not measured - until users complain that their experience is CPU-bound (i.e. never) I am more concerned with correctness than nanoseconds. However I can't see it being poor. Optionals are very cheap since there is no memory allocation and Ts constructor is only called if the optional is actually populated. I would expect the optimiser to eliminate almost all dead code since all code paths are visible at compile time. – Richard Hodges Dec 08 '15 at 00:48
  • Yeah. I'd agree if it weren't exactly about a general purpose algorithm (actually, generic building block inside those). This is the place where I'm not usually enthused unless something is as simple as it gets. Further, I'd love for the optional handling to be a decorator on any output iterator (so at least we get composability of output iterators, while we're trying to plug the lack of composability of algorithms). – sehe Dec 08 '15 at 01:00
  • There's logically no difference whether you handle the optional insert via a decorator on the iteratior or in the transform function. It's ultimately just a test of a flag. I think you'll find that the optimised code would be the same either way. The only thing standing in the way of full optimisation would be exception handling. Marking T as having noexcept constructors would cure this. – Richard Hodges Dec 08 '15 at 08:08
  • what form would you like the call to transform() to take? I'm sure we could build a composable iterator suite. – Richard Hodges Dec 08 '15 at 11:35
  • Me too :) I was commenting on your suggestion. I was not proposing something else (I had that long ago. Let's have ranges and composable algorithms instead :)) – sehe Dec 08 '15 at 13:33
  • :) agreed. so far, boost::range is probably the most succinct way I've seen. – Richard Hodges Dec 08 '15 at 14:43
  • 1
    This is pretty handy! It also works with c++17 if you replace boost with std::optional – wrestang Jul 29 '21 at 17:59
5

C++20 brought ranges and with them a new set of algorithms to operate on them. One of the most powerful tools in this addition is that of views:

  • They support lazy evaluation, which means elements are generated upon request and not upon construction. So performance considerations are put to rest (the original question mentions how creating temporary vectors with intermediate results is sub-optimal).
  • They are composable, which means that operations can easily chained together without loss of performance or expressiveness.

Armed with those new tools, a transform if operation to:

  • "transform a vector v using function A
  • only if an element satisfies condition B

becomes as simple as:

v | std::views::filter(B) | std::views::transform(A)

It's now fair to say that there is a pretty straight-forward way to do "transform if" using the Standard library.

What was originally asked can be written as:

struct ha { 
    int i;
    explicit ha(int a) : i(a) {}
};

int main() 
{
    std::vector<ha> v{ ha{1}, ha{7}, ha{1}, ha{4}, ha{3}, ha{0} };

    auto less4 =  [](ha const& h) { return h.i < 4; };
    auto pnter =  [](ha const& h) { return std::addressof(h); };
 
    for (auto vp : v | std::views::filter(less4) 
                     | std::views::transform(pnter)) 
    {
        std::cout << vp->i << ' ';
    }    
}

Demo

Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
3

The standard is designed in such a way as to minimise duplication.

In this particular case you can achieve the algoritm's aims in a more readable and succinct way with a simple range-for loop.

// another way

vector<ha*> newVec;
for(auto& item : v) {
    if (item.i < 2) {
        newVec.push_back(&item);
    }
}

I have modified the example so that it compiles, added some diagnostics and presented both the OP's algorithm and mine side by side.

#include <vector>
#include <algorithm>
#include <iostream>
#include <iterator>

using namespace std;

struct ha { 
    explicit ha(int a) : i(a) {}
    int i;   // added this to solve compile error
};

// added diagnostic helpers
ostream& operator<<(ostream& os, const ha& t) {
    os << "{ " << t.i << " }";
    return os;
}

ostream& operator<<(ostream& os, const ha* t) {
    os << "&" << *t;
    return os;
}

int main() 
{
    vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector
    // GOAL : make a vector of pointers to elements with i < 2
    vector<ha*> ph; // target vector
    vector<ha*> pv; // temporary vector
    // 1. 
    transform(v.begin(), v.end(), back_inserter(pv), 
        [](ha &arg) { return &arg; }); 
    // 2. 
    copy_if(pv.begin(), pv.end(), back_inserter(ph),
        [](ha *parg) { return parg->i < 2;  }); // 2. 

    // output diagnostics
    copy(begin(v), end(v), ostream_iterator<ha>(cout));
    cout << endl;
    copy(begin(ph), end(ph), ostream_iterator<ha*>(cout));
    cout << endl;


    // another way

    vector<ha*> newVec;
    for(auto& item : v) {
        if (item.i < 2) {
            newVec.push_back(&item);
        }
    }

    // diagnostics
    copy(begin(newVec), end(newVec), ostream_iterator<ha*>(cout));
    cout << endl;
    return 0;
}
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
1

You may use copy_if along. Why not? Define OutputIt (see copy):

struct my_inserter: back_insert_iterator<vector<ha *>>
{
  my_inserter(vector<ha *> &dst)
    : back_insert_iterator<vector<ha *>>(back_inserter<vector<ha *>>(dst))
  {
  }
  my_inserter &operator *()
  {
    return *this;
  }
  my_inserter &operator =(ha &arg)
  {
    *static_cast< back_insert_iterator<vector<ha *>> &>(*this) = &arg;
    return *this;
  }
};

and rewrite your code:

int main() 
{
    vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector
    // GOAL : make a vector of pointers to elements with i < 2
    vector<ha*> ph; // target vector

    my_inserter yes(ph);
    copy_if(v.begin(), v.end(), yes,
        [](const ha &parg) { return parg.i < 2;  });

    return 0;
}
dyomas
  • 700
  • 5
  • 13
  • 4
    "Why not?" - Because code is for humans. To me the friction is actually worse than going back to writing function objects instead of lambdas. `*static_cast< back_insert_iterator> &>(*this) = &arg;` is both unreadable and needlessly concrete. See this [c++17](http://coliru.stacked-crooked.com/a/1dc18bcf2967f9cb) take with more generic usages. – sehe Dec 09 '17 at 10:07
  • Here's a version doesn't hardcode the base-iterator (so you can use it with `std::insert_iterator<>` or `std::ostream_iterator<>` e.g.) and also let's you supply a transformation (e.g. as a lambda). [c++17, Starting to look useful](http://coliru.stacked-crooked.com/a/abdb9e0a04b6dd97)/[Same in c++11](http://coliru.stacked-crooked.com/a/ea781d749ead52f7) – sehe Dec 09 '17 at 10:39
  • Note, at this point, there is little reason to keep the base-iterators, and you can simply: [use any function](http://coliru.stacked-crooked.com/a/4a57c6db857cc789), noting that Boost contains a better implementation: [boost::function_output_iterator](http://www.boost.org/doc/libs/1_65_1/libs/iterator/doc/function_output_iterator.html). Now all that's left is re-inventing `for_each_if` :) – sehe Dec 09 '17 at 10:40
  • Actually, re-reading the original question, let's add a [voice of reason](https://stackoverflow.com/a/47727874/85371) - using just c++11 standard library. – sehe Dec 09 '17 at 11:04
0
template <class InputIt, class OutputIt, class BinaryOp>
OutputIt
transform_if(InputIt it, InputIt end, OutputIt oit, BinaryOp op)
{
    for(; it != end; ++it, (void) ++oit)
        op(oit, *it);
    return oit;
}

Usage: (Note that CONDITION and TRANSFORM are not macros, they are placeholders for whatever condition and transformation you want to apply)

std::vector a{1, 2, 3, 4};
std::vector b;

return transform_if(a.begin(), a.end(), b.begin(),
    [](auto oit, auto item)             // Note the use of 'auto' to make life easier
    {
        if(CONDITION(item))             // Here's the 'if' part
            *oit++ = TRANSFORM(item);   // Here's the 'transform' part
    }
);
user5406764
  • 1,627
  • 2
  • 16
  • 23
  • would you rate this implementation production ready? Would it work well with non-copyable elements? Or move-iterators? – sehe Mar 22 '16 at 08:02
0

This is just an answer to question 1 "Is there a more elegant workaround with the available C++ standard library tools ?".

If you can use c++17 then you can use std::optional for a simpler solution using only C++ standard library functionality. The idea is to return std::nullopt in case there is no mapping:

See live on Coliru

#include <iostream>
#include <optional>
#include <vector>

template <
    class InputIterator, class OutputIterator, 
    class UnaryOperator
>
OutputIterator filter_transform(InputIterator first1, InputIterator last1,
                            OutputIterator result, UnaryOperator op)
{
    while (first1 != last1) 
    {
        if (auto mapped = op(*first1)) {
            *result = std::move(mapped.value());
            ++result;
        }
        ++first1;
    }
    return result;
}

struct ha { 
    int i;
    explicit ha(int a) : i(a) {}
};

int main()
{
    std::vector<ha> v{ ha{1}, ha{7}, ha{1} }; // initial vector

    // GOAL : make a vector of pointers to elements with i < 2
    std::vector<ha*> ph; // target vector
    filter_transform(v.begin(), v.end(), back_inserter(ph), 
        [](ha &arg) { return arg.i < 2 ? std::make_optional(&arg) : std::nullopt; });

    for (auto p : ph)
        std::cout << p->i << std::endl;

    return 0;
}

Note that I just implemented Rust's approach in C++ here.

Jonny Dee
  • 837
  • 4
  • 12
0

You can use std::accumulate which operates on a pointer to the destination container:

Live On Coliru

#include <numeric>
#include <iostream>
#include <vector>

struct ha
{
    int i;
};

// filter and transform is here
std::vector<int> * fx(std::vector<int> *a, struct ha const & v)
{
    if (v.i < 2)
    {
        a->push_back(v.i);
    }

    return a;
}

int main()
{
    std::vector<ha> v { {1}, {7}, {1}, };

    std::vector<int> ph; // target vector

    std::accumulate(v.begin(), v.end(), &ph, fx);
    
    for (int el : ph)
    {
        std::cout << el << " ";
    }
}

Prints

1 1 
Wojciech Migda
  • 738
  • 7
  • 16
0

The standard std::copy_if & std::transform support execution policies (e.g., std::execution::par_unseq), so a standard std::copy_if_and_transform would also do so and allow one to filter & transform in parallel, without having to create an intermediate sequence of elements (copy_if) and then transform that.

None of the "do it yourself" suggestions above seem to be able to do so.

So I too wonder why the standard didn't include a copy_if_and_transform algorithm. Nikos' answer above (https://stackoverflow.com/a/70523558/20396957) (which I like a lot, as it introduced me to ranges!) uses ranges to do this lazily. But "lazily" doesn't necessarily guarantee an execution policy - they could all be computed sequentially for all I know.

So, do we still need the copy_if_and_transform?

And is the newer standard (C++23) going to provide it?

(and same question for remove_if_and_transform while I'm at it, since one may want to do the filter/transform in place instead of constructing a new container)

EDIT: Here's code I've written to implement the (policy taking) copy_if_and_transform using the standard copy_if - hope it helps! I'd love to hear comments about it and how one can improve it (my generic programming skills are not very good).

Solution - What's the idea:

The copy_if uses *first1 twice - once to call pred() on it and the second time to assign it to *d_first. I want to be able to hijack the 2nd call, so as to call the transform operation. So I proxy the input iterator so that it returns a proxy_val instead. Then I wrap the pred so it can take a proxy_val and apply itself to the actual value. While proxy_val also offers a way to get the output iterator's element type, upon which it calls the transform operation.

#include <iostream>
#include <string>
#include <vector>
#include <functional>
#include <algorithm>
#include <execution>
#include <iterator>
#include <utility>

// Get the container element type from an iterator
template<class It, class Itvaluetype>
struct get_value_type {
    using value_type = std::iter_value_t<It>;
};

// Get the container element type from an inserter
template<class It> 
struct get_value_type<It, void> {
    using value_type = typename It::container_type::value_type ;
};

template< class ExecutionPolicy, class InputIt, class OutputIt,
        class UnaryPredicate, class UnaryOperation>
OutputIt copy_if_and_transform(ExecutionPolicy&& policy,
            InputIt first1, InputIt last1,
                    OutputIt d_first,
            UnaryPredicate pred,
            UnaryOperation unary_op) {
    if (first1 != last1) {

        using InputElementType
            = std::iterator_traits<InputIt>::value_type;
        using OutputElementType
            = get_value_type< OutputIt, typename std::iterator_traits< OutputIt > ::value_type >::value_type ;

    class proxy_val {
            UnaryOperation op;
        public:
            InputElementType val;
            proxy_val(const InputElementType &vl
                , UnaryOperation o
                ) : op(o) , val(vl) {}
            operator OutputElementType() const {return op(val);}
    };

    class proxy_InputIt {
        InputIt ii;
        UnaryOperation op;
        public:
        proxy_InputIt(const InputIt &an_in_it, UnaryOperation o)
            : ii(an_in_it) , op(o) {}
        proxy_InputIt &operator++() { ++ii; return *this; }
        proxy_InputIt operator++(int) { proxy_InputIt prev=*this; ++ii; return prev; }
        proxy_val operator*() {return {*ii, op};}
        bool operator==(const proxy_InputIt &o) const {return ii == o.ii;}
    };
    auto pr = [ &pred ]( const proxy_val &p ) {return pred(p.val);};
    d_first =
    std::copy_if(policy
            , proxy_InputIt(first1, unary_op)
            , proxy_InputIt(last1, unary_op)
            , d_first
            , pr
            );
    }
    return d_first;
}

// Test with iterator & inserter
int main() {
    std::vector<int> vi = {1, 2, 3, 4};
    std::vector<std::string> squares_of_odds(vi.size());

    auto e = 
        copy_if_and_transform(std::execution::par_unseq, 
            std::begin(vi), std::end(vi)
            , std::begin(squares_of_odds)
            , [](auto x) {return x%2;}
            , [](auto x) {return '|'+std::to_string(x*x)+'|';});

    std::cout << "Squares of odd\n";
    for ( auto f = begin(squares_of_odds); f != e; ++f )
        std::cout << (*f) << std::endl;

    std::vector<int> vib = {1, 2, 3, 4};
    std::vector<std::string> squares_of_even;

    copy_if_and_transform(std::execution::par_unseq, 
        std::begin(vib), std::end(vib)
        , std::back_inserter(squares_of_even)
        , [](auto x) {return 0==(x%2);}
        , [](auto x) {return '|' + std::to_string(x*x) + '|';} );

    std::cout << "Squares of even\n";
    for ( auto n : squares_of_even)
        std::cout << n << std::endl;

    return 0;
}