I've reduced the code failing to compile to the one below:
#include <algorithm> // copy_if
#include <iostream> // cout
#include <iterator> // back_inserter
#include <ranges>
#include <string>
#include <vector>
struct A
{
std::string p{};
std::string d{};
};
int main()
{
std::vector<A> v{{"/usr/bin/cat", "blah"}, {"/usr/lib", "foo"}};
std::vector<std::string> o{};
std::ranges::copy_if(
v,
std::back_inserter(o),
[](std::string& p){ return (p.size() > 10); },
&A::p);
}
Visual Studio's error output is quite short: error C7602: 'std::ranges::_Copy_if_fn::operator ()': the associated constraints are not satisfied.
It basically points you to check the constraints of copy_if
at the algorithm
header.
gcc and clang are more detailed. Having a look at a bit of both outputs:
- First, the
copy_if
overload with a range, an output iterator, a predicate, and a projection, is considered. - But then, the expression
*__o = (std::forward<_Tp>(__t)
is evaluated as invalid; where:'_Out&& __o', '_Tp&& __t' [with _Tp = A&; _Out = std::back_insert_iterator<std::vector<std::string>>]
Since we are using a projection (from A&
to std::string&
), why is it trying to copy an A&
instead of a std::string&
to o
?
The full error output is accessible through the Demo link above.
CodePudding user response:
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.
CodePudding user response:
Since we are using a projection (from A& to std::string&), why is it trying to copy an A& instead of a std::string& to o?
Because that's what the algorithm does. copy_if
always copies the source range into the provided iterator, for each element that satisfies the predicate.
The projection only affects the predicate - it's basically just doing function composition here - but it doesn't affect what the overall algorithm does.
This is true for all projections.
If what you want to copy are just the p
subobjects, what you want to do instead is
ranges::copy_if(v | views::transform(&A::p), /* ... */);
Which now changes the source range that you're copying from.