I like the idea of the lazy ranges you can make with std::views::iota
but was surprised to see that iota
is currently the only thing like it in the standard; it is the only "range factory" besides views::single
and views::empty
. There is not currently, for example, the equivalent of std::generate
as a range factory.
I note however it is trivial to implement the semantics of generate
by using a transform view on iota and just ignoring the value iota passes to transform i.e.
#include <iostream>
#include <ranges>
#include <random>
template<typename F>
auto generate1(const F& func) {
return std::views::iota(0) | std::views::transform([&func](int) {return func(); });
}
std::random_device dev;
std::mt19937 rng(dev());
int main() {
auto d6 = []() {
static std::uniform_int_distribution<> dist(1, 6);
return dist(rng);
};
for (int v : generate1(d6) | std::views::take(10)) {
std::cout << v << ' ';
}
std::cout << '\n';
}
My questions is what would be "the real way" to implement something like this? To make a range view object that is pipeable that does not just use iota
.
I tried inheriting from ranges::view_interface
-- no idea if this is the correct approach -- and just having it return a dummy iterator that calls a generator function but my code doesn't work because of the part where it needs to pipe the range view to std::views::take
in order to not cause an infinite loop. The object I define here does not end up being pipeable.
#include <iostream>
#include <ranges>
#include <random>
template<typename F>
class generate2 : public std::ranges::view_interface<generate2<F>>
{
using value_type = decltype(std::declval<F>()());
class iterator {
const F* gen_func_;
public:
iterator(const F* f) : gen_func_(f)
{}
value_type operator*() const {
return (*gen_func_)();
}
bool operator!=(const iterator&) {
return true;
}
iterator& operator++() {
return *this;
}
};
F generator_func_;
public:
generate2(const F& f) : generator_func_(f) {
}
iterator begin() {
return iterator(&generator_func_);
}
iterator end() {
return iterator(nullptr);
}
};
std::random_device dev;
std::mt19937 rng(dev());
int main() {
auto d6 = []() {
static std::uniform_int_distribution<> dist(1, 6);
return dist(rng);
};
// the following doesnt compile because of the pipe...
for (int v : generate2(d6) | std::views::take(10)) {
std::cout << v << ' ';
}
std::cout << '\n';
}