Home > OS >  Using '|' (pipe) operator with std::views does not compile
Using '|' (pipe) operator with std::views does not compile

Time:12-02

After a career diversion, I am trying to get up to speed with std::views (and functional programming in general). I am using the '|' (pipe) operator with std::views::filter on a vector, and I am puzzled why some code structures compile and others don't.

This code creates a vector of vectors of int, then filters them by sum. I've commented the three statements that are confusing me, the first two of which compile and the third doesn't.

Compilation error is:

'|': no operator found which takes a left-hand operand of type 'std::vector<std::vector<int,std::allocator<int>>,std::allocator<std::vector<int,std::allocator<int>>>>' (or there is no acceptable conversion)  

(Using MSVC19, compiled with /std:c latest)

I am puzzled as to why this doesn't compile while (2) especially does?

#include <vector>
#include <numeric>
#include <ranges>

template<typename T>
auto buildMultiples(const std::vector<T>& base)
{
    std::vector<std::vector<T>> vRet;
    for(T n= 1; n <= 5; n  )
    {
        auto v = base;
        for (auto& m : v) m *= n;
        vRet.push_back(v);
    }
    return vRet;
}

template<typename T>
struct sumGreaterThan
{
    T _limit{ 0 };

    auto operator()(const std::vector<T>& v) {return std::accumulate(v.cbegin(), v.cend(), 0) > _limit;}
};
   
int main()
{
    using namespace std;
    vector<int> nums{1,2,3,4,5,6,7,8,9};

    auto mults = buildMultiples(nums);

    for (auto& m : buildMultiples(nums)) {} //1. Compiles

    sumGreaterThan sumFilter{ 10 };
    
    auto vecs = buildMultiples(nums);
    for (auto& m : vecs | views::filter(sumFilter)) {} //2. Compiles

    for (auto& m : buildMultiples(nums) | views::filter(sumFilter)) {} //3. Compilation Error!!

    for (auto vecs = buildMultiples(nums); auto & m : vecs | views::filter(sumFilter)) {} // 4. Compiles. Thanks @Aryter
}

CodePudding user response:

This is passing an lvalue vector into filter:

vecs | views::filter(sumFilter)

whereas this is passing an rvalue vector into filter:

buildMultiples(nums) | views::filter(sumFilter)

The current rule, which compilers implement, is that range adaptor pipelines cannot take rvalue non-view ranges (like vector, string, etc.). This is because the pipeline itself is non-owning (views were non-owning), and exists as a safety mechanism to prevent dangling.

The new rule, recently adopted as a defect, would allow this could and would cause filter to own the result of buildMultiples (this is P2415), but compilers don't implement it quite yet. With this change, your other version would also have compiled.

So for now, you will have to keep writing it this way (as you are already doing):

auto vecs = buildMultiples(nums);
for (auto& m : vecs | views::filter(sumFilter)) { ... }
  • Related