Search code examples
c++rvalue-referencestd-rangesc++23

std::set<std::unique_ptr<int>>and std::views::as_rvalue


I have the following code where for some reason only last block does not work.

I originally thought it may be related to unique_ptr being move only or std::set having const keys, but then it is unclear why other blocks work.



namespace sr = std::ranges;
namespace sv = std::views;
int main() {
    {
        std::set<int> up_s{10,20};
        const auto up_vec = sv::as_rvalue(up_s) | sr::to<std::vector>();
        assert(up_vec.size() == 2);
    }
    {
        std::vector<std::unique_ptr<int>> up_d;
        up_d.emplace_back(std::make_unique<int>(10));
        up_d.emplace_back(std::make_unique<int>(20));
        const auto up_vec =  sv::as_rvalue(up_d) | sr::to<std::vector>();
        assert(up_vec.size() == 2);
    }    
    {
        std::set<std::unique_ptr<int>> up_d;
        up_d.emplace(std::make_unique<int>(10));
        up_d.emplace(std::make_unique<int>(20));
        //const auto up_vec =  sv::as_rvalue(up_d) | sr::to<std::vector>();
        //assert(up_vec.size() == 2);
    }        
}

Error message seems useless, as it looks like that for some reason code wants to treat unique_ptr as range

/opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/ranges:9354:25: error: static assertion failed 9354 | static_assert(input_range<range_reference_t<_Rg>>); | ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/ranges:9354:25: note: constraints not satisfied

...

/opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/bits/ranges_base.h:499:13: required for the satisfaction of 'range<_Tp>' [with _Tp = const std::unique_ptr<int, std::default_delete >&&] /opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/bits/ranges_base.h:499:21: in requirements with '_Tp& __t' [with _Tp = const std::unique_ptr<int, std::default_delete >&&] /opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/bits/ranges_base.h:501:22: note: the required expression 'std::ranges::_Cpo::begin(__t)' is invalid 501 | ranges::begin(__t); | ~~~~~~~~~~~~~^~~~~ /opt/compiler-explorer/gcc-trunk-20240513/include/c++/15.0.0/bits/ranges_base.h:502:20: note: the required expression 'std::ranges::_Cpo::end(__t)' is invalid 502 | ranges::end(__t);


Solution

  • I originally thought it may be related to unique_ptr being move only or std::set having const keys, but then it is unclear why other blocks work.

    You've hit the nail on the head. It's very difficult to tell from the errors and I wasn't able to produce some error message that would make it obvious, but in short, the issue is that std::set only gives us access to const keys and std::unique_ptr would require something non-const to make moving possible.

    Piping this range through as_rvalue doesn't fix this problem; it just means that we're working with a range of xvalues of type const std::unique_ptr, but that's no good. The following code is ill-formed, and so is your attempt at making a vector:

    const std::unique_ptr<int> p;
    auto q = std::move(p); // call to deleted copy constructor, ill-formed
    

    To get more into specifics, ranges::to<std::vector> would require std::vector(std::from_range, sv::as_rvalue(up_d)) to be valid. However, the constraint container-compatible-range is not satisfied because const std::unique_ptr<int>&& is not convertible to std::unique_ptr<int>.

    Other cases

    In the case of std::set<int>, the constness doesn't matter. std::set<int> is range of const int lvalues, and piping it through as_rvalue makes it a range of const int xvalues. The following code is fine, and so is construction of a std::vector<int> from such a range:

    const int x = 0;
    int y = std::move(x); // initialization of an int from an xvalue of type const int
    

    In the case of std::vector<std::unique_ptr<int>>, constness isn't a problem. A std::vector gives us non-const access to the elements, so piping it through as_rvalue gives us a range of non-const std::unique_ptr xvalues. Therefore, the move constructor will be called for each element.