Why There Is No Std::Copy_If Algorithm

Why there is no std::copy_if algorithm?

According to Stroustrup's "The C++ Programming Language" it was just an over-sight.

(as a citation, the same question answered in boost mail-lists: copy_if)

Why is there no std::move_if algorithm?

If move_if existed as an algorithm, it would have to be specified as:

template <class InputIt, class OutputIt, class UnaryPredicate>
OutputIt move_if(InputIt first, InputIt last, OutputIt d_first, UnaryPredicate pred)
{
return std::copy_if(std::make_move_iterator(first), std::make_move_iterator(last),
d_first, pred);
}

We are very used to thinking of the difference between copying and moving as simply a matter of whether or not we care about the source object. We still do? Copy. We don't? Move. Having move_if(f, l, d, pred) do something semantically different than copy_if(f, l, d, pred) seems inherently confusing and error-prone - since the inevitable usage of it would be to do a "copy where we don't care about the source anymore."

But then - you're more or less describing the problems with this algorithm in your question. When would I use this? I would end up with a source range where some of the elements are moved-from but others aren't. What could I do with such a range? I couldn't coalesce them somehow since I don't know which ones they are or anything. Moving those elements to the back is useful - and we do have that algorithm: remove_if.

Basically the only thing I could do with the source range at this point is destroy it. Maybe that's useful. Maybe that's useful enough to even merit this algorithm in std::. I don't know. But move_if definitely needs to do the same thing as copy_if.

copy_if with map whose value is also a std::pair

The iterators of a std::map are not suitable for use with copy_if, as that algorithm is simply going to attempt to assign the entire value. However, the iterator of a std::map has a value type of std::pair<const K, V>, which means it is not copy assignable.

However, you can use std::inserter to accomplish what you want

std::copy_if(inMap.begin(), inMap.end(), std::inserter(outMap, outMap.end()), Predicate);

Why is there no transform_if in the C++ standard library?

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"));
}

Can't make my example using std::ranges::copy_if(R&& r, O result, Pred pred, Proj proj = {}) compile

The projection applies only to the predicate, not to the result. There's already a way to transform the data entirely—std::views::transform:

std::ranges::copy_if(
v | std::views::transform(&A::p),
std::back_inserter(o),
[](const std::string& p){ return (p.size() > 10); });

(This compiles in GCC and MSVC, but not Clang. I'm not 100% confident saying it's correct, but it should be close if nothing else, and it illustrates the point.)

Projections are for when you want to test a transformation (projection) of the data while carrying the originals through. In this example, that would mean you'd output to std::vector<A>, yet test a std::string in the predicate. Not super useful here since you could just return (a.p.size() > 10);, but useful if passing a pre-written function instead of using a lambda. Also useful for some other cases such as easily sorting by a member—simply use the default comparator and pass &A::p as the projection.

In-place std::copy_if

copy_if is primarily for copying a range to another range/container I.e. by design, the nature of the algorithm is to copy the elements satisfying some condition to another (non-overlapping) range or to a new container.

remove_if is more appropriate for what you need; it is exactly for filtering out as you expect. However, it only removes the elements by overwriting; the remnants between the old and new ends would be unspecified elements after the function's completion and needs to be manually erased using erase, like so:

std::vector<int> vec { 1, 2, 3, 4 };
vec.erase(std::remove_if(std::begin(vec),
std::end(vec),
[](int i) { return i <= 2; }),
std::end(vec));

This is a C++ idiom that goes by the name erase-remove.


Instead of copy_if, if copy was what you wanted, then you've an alternative for overlapping ranges, namely copy_backward; from the documentation

If d_first is within [first, last), std::copy_backward must be used
instead of std::copy.

Why does the std::copy_if signature not constrain the predicate type

Because the predicate does not have to be a function, but it can be a functor too. And restricting functor type is close to impossible since it can be anything at all as long as it has operator() defined.

Actually I suggest you convert the overloaded function to a polymorphic functor here:

struct predicate {
bool operator()( const A& a) const
{
return a.i > 123;
}

bool operator()( const B& b) const
{
return operator()(b.a);
}
}

and call the functor with an instance, i.e.

std::copy_if(a_source.begin(), a_source.end(), std::back_inserter( a_target ), predicate());
std::copy_if(b_source.begin(), b_source.end(), std::back_inserter( b_target ), predicate());
// ^^ here, see the ()

Then the correct overload will be selected inside the algorithm.

Providing a correct std::copy_if predicate

Unfortunately, sometimes cplusplus has wrong/misleading information. They write:

result

Output iterator to the initial position of the range where the resulting sequence is stored. The range includes as many elements as [first,last).

And that is wrong. The output range has as many elements as the predicate returns true. Others are not copied and the target iterator is not incremented in that case. copy_if works just like expected in your example.

I suggest this reference: https://en.cppreference.com/w/cpp/algorithm/copy

It also does not mention explicitly that the output iterator is only advanced when actually an element was copied. But it also does not state otherwise. Looking at the possible implementation should make things more clear:

template<class InputIt, class OutputIt, class UnaryPredicate>
OutputIt copy_if(InputIt first, InputIt last,
OutputIt d_first, UnaryPredicate pred)
{
while (first != last) {
if (pred(*first))
*d_first++ = *first;
first++;
}
return d_first;
}

You can see that d_first is only incremented when pred(*first) returns true.



Related Topics



Leave a reply



Submit