c++c++20std-rangesrange-v3

Custom view works for vector, but not array or initializer_list


All code details for this question are in this godbolt example:

#include <array>
#include <iostream>
#include <random>
#include <range/v3/all.hpp>
#include <string>
#include <type_traits>
#include <unordered_set>
#include <vector>

#include "fmt/format.h"
#include "fmt/ranges.h"

template <typename T>
decltype(auto) deref(T&& t) {
    return std::forward<T>(t).get();
}

template <ranges::range Range>
class deref_view : public ranges::view_base {
   public:
    struct iterator;
    deref_view() = default;
    deref_view(ranges::range auto&& base) : m_base(base) {}

    iterator begin() { return ranges::begin(m_base); }
    iterator end() { return ranges::end(m_base); }

   private:
    Range m_base;
};

template <ranges::range Range>
struct deref_view<Range>::iterator : public ranges::iterator_t<Range> {
    using base = ranges::iterator_t<Range>;
    using value_type =
        std::remove_cvref_t<decltype(deref(*(std::declval<Range>().begin())))>;
    using difference_type = ranges::range_difference_t<Range>;

    iterator() = default;

    iterator(const base& b) : base{b} {}

    iterator operator++(int) { return static_cast<base&>(*this)++; }

    iterator& operator++() {
        ++static_cast<base&>(*this);
        return (*this);
    }

    decltype(auto) operator*() const {
        return deref(*static_cast<base>(*this));
    }
};

template <ranges::range Range>
deref_view(Range&&) -> deref_view<ranges::cpp20::views::all_t<Range> >;

struct deref_fn {
    template <typename Rng>
    auto operator()(Rng&& rng) const {
        return deref_view{ranges::views::all(std::forward<Rng>(rng))};
    }

    template <typename Rng>
    friend auto operator|(Rng&& rng, deref_fn const&) {
        return deref_view{ranges::views::all(std::forward<Rng>(rng))};
    }
};

// add the deref view to the ranges namespace

namespace ranges::views {

constexpr deref_fn deref{};

}  // namespace ranges::views

int main() {
    std::vector v{1, 2, 3, 4, 5, 6};

    // auto list =           {std::ref(v), std::ref(v)};  // this does NOT work
    auto list = std::array{std::ref(v), std::ref(v)};  // this does NOT work
    // auto list = std::vector{std::ref(v), std::ref(v)};  // this WORKS

    for (const auto& arr : list | ranges::views::deref) {
        fmt::print("{}\n", arr);
    }

    return 0;
}

Building on my previous question Create a custom transformable view in ranges-v3, I have a deref view which merely dereferences std::reference_wrappers by calling .get() on the range's elements,

template < typename T >
decltype(auto) deref(T&& t)
{
      return std::forward< T >(t).get();
}

I have used this method to create a deref view for the ranges-v3 library which has worked for my use cases as expected. However, I cannot apply it to a std::array or std::initializer_list container, but it does work for a std::vector. The error I get for e.g. std::array is:

<source>: In instantiation of 'struct deref_view<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >::iterator':
<source>:120:49:   required from here
<source>:66:29: error: base type 'ranges::iterator_t<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >' {aka 'std::reference_wrapper<std::vector<int> >*'} fails to be a struct or class type
   66 | struct deref_view< Range >::iterator: public ranges::iterator_t< Range > {
      |                             ^~~~~~~~
<source>: In function 'int main()':
<source>:120:49: error: no match for 'operator!=' (operand types are 'deref_view<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >::iterator' and 'deref_view<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >::iterator')
  120 |     for(const auto& arr : list | ranges::views::deref) {
      |                                                 ^~~~~
<source>: In instantiation of 'decltype(auto) deref_view<Range>::iterator::operator*() const [with Range = ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> >]':
<source>:120:49:   required from here
<source>:83:53: error: invalid 'static_cast' from type 'const deref_view<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >::iterator' to type 'deref_view<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >::iterator::base' {aka 'std::reference_wrapper<std::vector<int> >*'}
   83 |    decltype(auto) operator*() const { return deref(*static_cast< base >(*this)); }
      |                                                     ^~~~~~~~~~~~~~~~~~~~~~~~~~

What specifics am I missing in how ranges handle arrays and vectors?

(The problem also occurs with std::ranges)


Solution

  • The compiler error here indicates the issue:

    <source>:66:29: error: base type 'ranges::iterator_t<ranges::ref_view<std::array<std::reference_wrapper<std::vector<int> >, 2> > >' {aka 'std::reference_wrapper<std::vector<int> >*'} fails to be a struct or class type
    

    When you write:

    template <ranges::range Range>
    struct deref_view<Range>::iterator : public ranges::iterator_t<Range> {
    

    You're only allowed to inherit from class types. But iterator_t<Range> need not actually be a class type. And in the particular case that you're trying here, it's not a class type - it's just a pointer (specifically std::reference_wrapper<std::vector<int> >*). That's because std::array<T, N>'s iterator is allowed to be just T* or T const* (it's implementation defined, but a simple pointer meets all the requirements).

    The solution here is straightforward: instead of inheriting from ranges::iterator_t<Base>, have a member of that type. This is what all the range adapters have to do - first because of the pointer issue, since pointers are valid iterators, but also because avoiding inheritance ensures that you can actually get all the operations correct.

    This additionally actually makes the implementation cleaner anyway, since you can just refer to the base member instead of having to write static_cast<base>(*this) everywhere.

    Demo.