Home > Software engineering >  What am I missing in my custom std::ranges iterator?
What am I missing in my custom std::ranges iterator?

Time:03-19

I'd like to provide a view for a customer data structure, with it's own iterator. I wrote a small program to test it out, shown below. It I uncomment begin(), then it works. But if I use DummyIter, then I get a compile error.

In my full program, I've implemented a full iterator but for simplicity, I narrowed it down to the necessary functions here.

#include <iostream>
#include <ranges>
#include <vector>

template<class T>
struct DummyIter
{
  using iterator_category = std::random_access_iterator_tag;
  using value_type = T;
  using difference_type = std::ptrdiff_t;

  DummyIter() = default;

  auto operator*() const { T t; return t; }

  auto& operator  () { return *this; }

  auto operator  (int val) { DummyIter tmp = *this;   *this; return tmp; }

  auto operator==(const DummyIter& iter) const { return true; }
};

template<class V>
struct DummyView : std::ranges::view_interface<DummyView<V>>
{
  //auto begin() const { return std::ranges::begin(v); }
  auto begin() const { return DummyIter<int>(); }

  auto end() const { return std::ranges::end(v); }

  V v;
};

int main() {
  auto view = DummyView<std::vector<int>>();
  view | std::views::filter([](auto i) { return i > 0; });
}

I'm using GCC 11.1.0. What am I missing in my iterator for it to be ranges compliant?

error: no match for 'operator|' (operand types are 'DummyView<std::vector<int> >' and 'std::ranges::views::__adaptor::_Partial<std::ranges::views::_Filter, main()::<lambda(auto:15)> >')
   37 |   view | std::views::filter([](auto i) { return i > 0; });
      |   ~~~~ ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      |   |                        |
      |   |                        std::ranges::views::__adaptor::_Partial<std::ranges::views::_Filter, main()::<lambda(auto:15)> >
      |   DummyView<std::vector<int> >

CodePudding user response:

This can't work since return types of your begin and end do not match. So basically those iterators can't be compared to each other.

Prove:

Minimum requirement is that result of begin() and end() are comparable. Different types for begin() and end() are useful when size of range is not known. Here is nice explanation of sentinel (mentioned in comment).

CodePudding user response:

Because your DummyView does not meet the concept of range, to be precise, the return type of view.end() cannot be used as a sentinel for view.begin(), because there is no suitable operator==() for DummyIter to compare with std::vector<int>::const_iterator, which means that there is no way to know if the iterator has reached the end of the range.

The workaround is just to add bool operator==(std::vector<int>::const_iterator) const to your DummyIter

template<class T>
struct DummyIter {
  // ...
  bool operator==(std::vector<T>::const_iterator) const;
};

Demo

CodePudding user response:

The way to check this kind of thing for ranges is:

  1. Verify that your iterator is an input_iterator.
  2. Verify that your sentinel is a sentinel_for your iterator.

Those are the checks that will tell you what functionality you're missing.

In this case, that's:

using I = DummyIter<int>;
using S = std::vector<int>::const_iterator;

static_assert(std::input_iterator<I>);   // ok
static_assert(std::sentinel_for<S, I>);  // error

And the issue there is that S isn't a sentinel_for<I> because it's not equality comparable. You need to know when the iteration stops, and you don't have that operator - your DummyIter<T> is comparable to another DummyIter<T>, but not to what you're returning from end().

So you either need to add another operator== to DummyIter<T>, or have DummyView<V>::end() return some kind of DummySentinel<V> that is comparable to DummyIter<T>. It depends on the real problem which is the better approach, there are examples of both in the standard library.

  • Related