Edit: As it was pointed out by 康桓瑋 in the comments, there has been a better, cleaner solution presented at Cppcon, link, by Tristan Brindle.
I think this could serve as a quick reference to make custom iterator-based generators.
By your requirements, something
must be a std::ranges::view
, meaning it must be a moveable std::ranges::range
deriving from std::ranges::view_interface<something>
.
We can tackle all three with the following:
#include <ranges>
template<typename T>
class fib : public std::ranges::view_interface<fib<T>>{
public:
struct iterator;
auto begin() const { return iterator{}; }
auto end() const { return std::unreachable_sentinel; }
};
Notice std::unreachable_sentinel
which makes creating sequences without end really simple.
We still have to define the iterator which does the actual work. In your case we want fib
to be "the source" of the values, so our iterator should actually be std::input_iterator
. There's some boiler plate code needed for that but it's basically just a type which can be incremented and dereferenced to yield its current value.
Something like this will do:
#include <iterator>
template<typename T>
struct fib<T>::iterator {
using iterator_category = std::input_iterator_tag;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = T*;
using reference = T;
constexpr iterator() noexcept = default;
iterator& operator++() {
auto old_next = next;
next = next + current;
current = old_next;
return *this;
}
iterator operator++(int) {
iterator current{*this};
++(*this);
return current;
}
value_type operator*() const {
return current;
}
bool operator==(const iterator& other) const { return current == other.current && next==other.next; }
private:
T current= {};
T next = T{} + 1; // Could perhaps be fancier.
};
The increment operator does the computation itself, it's the simple iterating algorithm.
That's it, here's a working example:
#include <cstdint>
#include <iostream>
int main() {
auto is_even = [](auto a) { return a % 2 == 0; };
for (auto v :
fib<std::uint64_t>{} | std::ranges::views::filter(is_even) | std::ranges::views::take(10))
std::cout << v << std::endl;
return 0;
}
which outputs:
0
2
8
34
144
610
2584
10946
46368
196418
Of course you won't get very far even with std::uint64_t
. But T
can be anything numeric enough.
One can easily generalize the iterator to hold a stateful functor, likely passed from the range itself, call it during each increment and store the yielded value for dereferencing later. This would be very crude, but simple, way how to at least simulate "yield-based" generators.