Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

I am unable to construct a vector using a iterators over a view that uses take_while in its construction

I ran into this problem while trying to make a powerset function using the std::views library.


auto set = sv::repeat(0) 
    | sv::take_while([&mask](...) { return mask > 0; })
    | sv::transform([s, &mask, idx = 0](...) mutable { 
        return idx = std::countr_zero(mask & ~(mask - 1)), mask &= mask - 1, s[idx]; });
auto vec = std::vector(set.begin(), set.end());
           ^__________ ^______________________
"*cannot deduce types*"   "*no constructor found*" <- these are rough translations of verbose diagnostics

My question is, why doesn't this work? Normally I can construct vectors using views like this perfectly well, and I even do it later in the function. I tested the same thing, but this time I used a captureless and const lambda:

auto arr = std::array {1, 2, 3, 3, 4, 5, 6, 1, 2, 3};
auto arr_view = arr | std::views::take_while([](int i) { return i < 4; });
auto vec = std::vector(arr_view.begin(), arr_view.end());

and got identical error messages. Stranger still, I tried using the std::ranges::move() function to do the same thing, and it worked for some reason! This code compiles and runs as expected (both with the constructed view as an rvalue and stored in a variable):

auto arr_view = arr | std::views::take_while([](int i) { return i < 4; });
auto vec = std::vector<int>{};
std::ranges::move(arr_view, std::back_inserter(vec));

Initially, I thought this was because "arr_view" wasn't a sized range, but the same iterative approach of construction works for std::views::filter(), which is also not a sized range. Can someone explain to me the reasoning behind this not working? Is it just me using (unstable) modern features?

like image 437
SHIPWECK Avatar asked Aug 30 '25 16:08

SHIPWECK


1 Answers

set.begin() and set.end() return iterators of different types, i.e. begin() returns an iterator while end() returns a sentinel. This is necessary for take_while, because one does not know beforehand how long to take. the sentinel returned by end() will compare equal to the iterator, once the predicate of take_while evaluates to false.

To construct a std::vector from a range with a sentinel there are different methods:

  1. views::common makes a view into a common view i.e. with different type iterators:
auto set1 = sv::iota(0)
    | sv::take_while([](auto counter) { return counter < 5; })
    | sv::common;
auto vec1 = std::vector(set.begin(), set1.end());
  1. std::vectors new constructor taking a range. unfortunately, it needs a second argument to allow for the correct constructor to be chosen thus an object of std::from_range_t needs to be passed as the first argument.
auto set2 = sv::iota(0)
    | sv::take_while([](auto counter) { return counter < 5; });
auto vec2 = std::vector(std::from_range, set2);
  1. using std::ranges::to to convert a range to a container:
auto vec3 = sv::iota(0)
    | sv::take_while([](auto counter) { return counter < 5; })
    | std::ranges::to<std::vector>();

See Demo

Note: depending on your different methods might be implemented (e.g. in the demo clang trunk needs -stdlib=libc++ to compile with its own standard library)

like image 57
0xbachmann Avatar answered Sep 02 '25 07:09

0xbachmann