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
What Exactly Is a Namespace and Why Is It Necessary
Are Data Members Allocated in the Same Memory Space as Their Objects in C++
Understanding Rvalue References
Does Gcc Inline C++ Functions Without the 'Inline' Keyword
Convert Std::Variant to Another Std::Variant with Super-Set of Types
Why Does Glgetstring(Gl_Version) Return Null/Zero Instead of the Opengl Version
Opencv Fisheye Calibration Cuts Too Much of the Resulting Image
What Does C4250 Vc++ Warning Mean
How to Generate Type Library from Unmanaged Com Dll
How to Assign a Value to the Pointer 'This' in C++
How Does the Friend Keyword (Class/Function) Break Encapsulation in C++
Throwing C++ Exceptions Across Dll Boundaries
Add Elements to a Vector During Range-Based Loop C++11