Search code examples
c++range

How do i make a viewable range based on input iterators


Code godbolt

template <typename T>
struct lattice_iterator {
   public:
    using difference_type = std::ptrdiff_t;
    using value_type = std::vector<T>;
    using iterator_category = std::input_iterator_tag;

   private:
    struct wrapped_iter {
        T value_;
        std::move_only_function<T(void)> adder_;

        wrapped_iter(const wrapped_iter&) = delete;
        wrapped_iter(wrapped_iter&&) = default;

        wrapped_iter(std::ranges::input_range auto&& range) {
            auto iter = range.begin();
            value_ = *iter;
            adder_ = [iter_ = std::move(iter)]() mutable { return *++iter_; };
        }

        bool operator<(wrapped_iter const& rhs) const {
            return value_ < rhs.value_;
        }

        wrapped_iter& operator++() {
            value_ = adder_();
            return *this;
        }

        T& operator*() { return value_; }
    };

   public:
    std::vector<wrapped_iter> iters_{};
    value_type values_{};

    template <std::ranges::input_range... R>
        requires(std::convertible_to<std::ranges::range_value_t<R>, T> && ...)
    lattice_iterator(R&&... ranges) {
        (iters_.emplace_back(std::forward<R>(ranges)), ...);
        values_ =
            iters_ |
            std::views::transform([](auto&& iter) { return iter.value_; }) |
            std::ranges::to<std::vector>();
    }

    lattice_iterator(lattice_iterator const&) = delete;
    lattice_iterator(lattice_iterator&&) = default;
    lattice_iterator() = delete;

    lattice_iterator& operator=(lattice_iterator&& rhs) = default;

    value_type operator*() const { return values_; }

    lattice_iterator& operator++() {
        std::size_t min_idx = std::ranges::min(
            std::views::iota(0) | std::views::take(iters_.size()), {},
            [this](std::size_t idx) { return values_[idx]; });

        values_[min_idx] = *++iters_[min_idx];
        return *this;
    }

    void operator++(int) { ++*this; }
};

std::generator<int> get_triangles() {
    for (int n = 1;; ++n) {
        co_yield n*(n + 1) / 2;
    }
}

std::generator<int> get_pentagonal() {
    for (int n = 1;; ++n) {
        co_yield n * (3 * n - 1) / 2;
    }
}

std::generator<int> get_hexagonal() {
    for (int n = 1;; ++n) {
        co_yield n * (2 * n - 1);
    }
}

int main() {
    auto lattice = std::ranges::subrange(
        lattice_iterator<int>{get_triangles(), get_pentagonal(),
                              get_hexagonal()},
        std::unreachable_sentinel);

    static_assert(std::same_as<std::vector<int>,
                               std::ranges::range_value_t<decltype(lattice)>>);

    auto nums =
        std::views::filter(
            lattice,
            [](auto&& pack) -> bool {
                static_assert(std::same_as<std::vector<int>, decltype(pack)>);
                auto p0 = pack.front();
                return std::ranges::all_of(pack,
                                           [&p0](auto px) { return px == p0; });
            }) |
        std::views::transform([](auto&& pack) { return pack.front(); }) |
        std::views::take(3) | std::ranges::to<std::vector>();

    std::cout << nums[1] << ' ' << nums[2] << '\n';
}

Context:

  • lattice_iterator is a zip-alike wrapper that iterates by incrementing the smallest field amongst all current inputs

  • the code in main is for solving this. not really related to my question and forgive me its unnecessarily complicated for the problem. its for learning new C++ feature sake

Where I am stuck & how I got here

  • compiler is complaining that the subrange created is not a viewable range

  • search result suggests that the only non-viewable range is practically the ones that doesn't support copy constructor, from this post

So basically, the only thing that isn't a viewable range is an lvalue non-copyable view, because of the desire to avoid taking references to views.

  • input iterators, or essentially generator iterators are by definition only guarantees move movable but not copyable

  • However, generator as a viewable range, or at least std::views::filter compatible from my experience.

I suspect I made the wrong reasoning somewhere that led me into designing the subrange non-viewable while it should be viewable instead. Greatly appreciated if someone could point it out for me


Solution

  • An lvalue non-copyable view isn't a viewable-range, but an rvalue one is.

    An easy fix is to std::move(lattice) into the filter. See Godbolt

    N.b. I also had to adjust the static_assert in the lambda, and you have lifetime issues with your generators.