I'm trying to learn C++ ranges. As far as I can see, the simplest (and the only, short of implementing a custom range view class) way to create a range view object that generates a custom sequence is to use C++23' std::generator<>
:
std::generator<char> letters_gen(char start)
{
for (;;) co_yield start++;
}
How do I use a std::generator
object (as created by invoking letters_gen()
) in multiple expressions involving range operations? E.g. if I want to collect 10 letters into a vector, and then 15 more letters into another vector:
int main()
{
auto letters = letters_gen('a');
auto x = letters | std::views::take(10) | std::ranges::to<std::vector>();
auto y = letters | std::views::take(15) | std::ranges::to<std::vector>();
}
This does not compile:
main.cc:150:26: error: no match for ‘operator|’ (operand types are ‘std::generator<char>’ and ‘std::ranges::views::__adaptor::_Partial<std::ranges::views::_Take, int>’)
What is the proper way, if any, to achieve the desired effect?
You don't.
A generator
is an input range, you can only use it one time. One of the ways the library seeks to prevent misuse is to make it move-only instead of copyable - hence the complaint about trying to copy it (ranges compile errors are notoriously useless). It is undefined behavior to even call begin()
twice on the range.
There's not really a good way to do this within the library I don't think. You might try to work around the copying issue by doing something like this (which additionally ensures that both algorithms use the same generator):
auto x = ranges::ref_view(letters) | views::take(10) | ranges::to<vector>();
auto y = ranges::ref_view(letters) | views::take(15) | ranges::to<vector>();
This is undefined behavior, but even setting that aside this will give you the surprising outcome that x
contains the letters from a
thru j
as desired but y
contains l
thru z
intead of k
thru y
due to the way that take
interacts with input ranges (see my CppNow talk).
You'd have to write something more by hand:
// we have to use iterators and preserve them
auto it = letters.begin();
std::vector<char> x;
for (size_t count = 0; count < 10 && it != letters.end(); ++it, ++count) {
x.push_back(*it);
}
std::vector<char> y;
for (size_t count = 0; count < 15 && it != letters.end(); ++it, ++count) {
y.push_back(*it);
}