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

Time:02-10

I've reduced the code failing to compile to the one below:

[Demo]

#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.

  • Related