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.
- Is there a more elegant workaround with the available C++ standard library tools ?
- 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 ?
You may use
copy_if
along. Why not? DefineOutputIt
(see copy):and rewrite your code:
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.:
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:resulting in different semantics. This drives home the point:
See a sample Live On Coliru
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.
I have modified the example so that it compiles, added some diagnostics and presented both the OP's algorithm and mine side by side.
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:
used like this:
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.
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.
Nice and simple. Fancy fitting that into a transform_if algorithm?
Usage: (Note that CONDITION and TRANSFORM are not macros, they are placeholders for whatever condition and transformation you want to apply)