Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What am I missing in my custom std::ranges iterator?

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> >
like image 818
dromodel Avatar asked Dec 07 '25 02:12

dromodel


1 Answers

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.

like image 113
Barry Avatar answered Dec 08 '25 15:12

Barry