Search code examples
c++c++20std-rangesrange-based-loop

A c++20 range adaptor whose produced values are iterators to the underlying range


I want to have the following work

std::vector<int> range{0,1,2,3};

for(std::vector<int>::iterator vit : range | iterator_range  ){
   int v = *vit;
}

this would be equivalent to the standard for loop

std::vector<int> range{0,1,2,3};

for(std::vector<int>::iterator vit = begin(range); vit !=end(range); vit++)  ){
   int v = *vit;
}

except more elegant.

iterator_range is an adapter that I require.

I have some algorithms where it is useful to require to have an iterator to the element inside the loop rather than the element itself.


Solution

  • Note that views::iota is not limited to integers, it's anything that's incrementable and comparable. Which includes iterators!

    inline constexpr auto iterators_of = []<ranges::range R>(R&& r){
        static_assert(ranges::borrowed_range<R>);
        return views::iota(ranges::begin(r), ranges::end(r));
    };
    

    This at least gets you started, and maybe is good enough. Ideally you have a proper range adapter that captures views::all(r) without requiring borrowed, and actually gives out these iterators directly.

    A more complete implementation (if actually necessary) would be:

    template <std::ranges::view V>
    struct iterators_for_view : std::ranges::view_interface<iterators_for_view<V>> {
        V view;
    
        template <class R>
        using iota_for = std::ranges::iota_view<std::ranges::iterator_t<R>, std::ranges::sentinel_t<R>>;
    
        explicit iterators_for_view(V v) : view(std::move(v)) { }
    
        auto begin() { return std::ranges::iterator_t<iota_for<V>>(std::ranges::begin(view)); }
        auto end() { return std::ranges::sentinel_t<iota_for<V>>(std::ranges::end(view)); }
    
        auto begin() const requires std::ranges::range<V const> { return std::ranges::iterator_t<iota_for<V const>>(std::ranges::begin(view)); }
        auto end() const requires std::ranges::range<V const> { return std::ranges::sentinel_t<iota_for<V const>>(std::ranges::end(view)); }    
    
        auto size() requires std::ranges::sized_range<V> { return std::ranges::size(view); }
        auto size() const requires std::ranges::sized_range<V const> { return std::ranges::size(view); }
    };
    
    struct iterators_for_fn : std::ranges::range_adaptor_closure<iterators_for_fn> {
        template <std::ranges::viewable_range R>
        constexpr auto operator()(R&& r) const {
            return iterators_for_view(std::views::all((R&&)r));
        }
    };
    
    inline constexpr iterators_for_fn iterators_for{};
    

    Which you can see here.