无法使用 std::ranges::copy_if(R&& r, O result, Pred pred, Proj proj = {}) 编译我的示例

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

我已经将无法编译的代码减少为以下代码:

[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 的错误输出很短:error C7602: 'std::ranges::_Copy_if_fn::operator ()': the associated constraints are not satisfied. 它基本上指示您在 algorithm header 处检查 copy_if 的约束。

gcc和clang更详细。看一下两个输出:

既然我们使用的是投影(从 A&std::string&),为什么它试图复制一个 A& 而不是 std::string&o?

完整的错误输出可通过上面的演示 link 访问。

投影仅适用于谓词,不适用于结果。已经有一种方法可以完全转换数据—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); });

(这可以在 GCC 和 MSVC 中编译,但不能在 Clang 中编译。我不是 100% 有信心说它是正确的,但如果不出意外的话应该很接近,它说明了这一点。)

投影适用于您想要测试数据的转换(投影),同时携带原件。在此示例中,这意味着您将输出到 std::vector<A>,但在谓词中测试 std::string。在这里不是很有用,因为你可以 return (a.p.size() > 10);,但如果传递 pre-written 函数而不是使用 lambda,则很有用。对于其他一些情况也很有用,例如按成员轻松排序——只需使用默认比较器并传递 &A::p 作为投影。

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?

因为算法就是这么做的。 copy_if 对于每个满足谓词的元素,总是将源范围复制到提供的迭代器中。

投影只影响谓词 - 它基本上只是在这里进行函数组合 - 但它不会影响整个算法的作用。

所有投影都是如此。

如果您想要复制的只是 p 个子对象,您想要做的是

ranges::copy_if(v | views::transform(&A::p), /* ... */);

现在更改了您要从中复制的源范围。


有关此主题的更多信息,我写了一个 whole post 关于它。