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

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.



Solution 1:[1]

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.


For more on this topic, I wrote a whole post about it.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1