Relatively simple custom container signature does not fulfill <ranges> concept

Viewed 75

I am trying to use <ranges> (C++20 MSVC) to iterate a custom container called Span, whose begin/end() methods return a SpanIter. However, the following static assert fails in the test() method and the pipe operator is unrecognized with my class (but not vector<>).

static_assert(std::ranges::range<Span>, "Is not a range");

I think the method signatures match this working example. I haven't been able to track down what's missing, whether a method or typedef/using.

#include <ranges>
#include <iostream>
#include <vector>
namespace ptest {

  class SpanIter;
  struct Span;

  struct Span : std::ranges::view_base
  {
    using iterator = SpanIter;
    using sentinel = SpanIter;

    int start;
    int length;
    int index;
    Span() noexcept { start = length = index = 0; }
    Span(int s, int l, int i) noexcept : start(l), length(l), index(i) { }
    Span(const Span& other) noexcept : Span(other.start, other.length, other.index) { }
    Span(const Span&& other) noexcept : Span(other.start, other.length, other.index) { }
    Span& operator=(const Span& other) { start = other.start; length = other.length; index = other.index; }
    bool operator==(const Span& other) const { return start == other.start && length == other.length && index == other.index; }
    iterator begin();
    sentinel end();
    iterator cbegin() const;
    sentinel cend() const;
    size_t size() { return length; }
    void Print() {
      // eg. "range[5 - 9]: 5"
      std::cout << "range [" << start << "-" 
        << (start + length - 1)
        << "]: " << index << std::endl;
    }
    auto operator <=> (const Span& other) const = default;
  };

  class SpanIter {
    Span Empty;

    using value_type = Span;
    using reference = Span&;
    using const_reference = const Span&;
    using iterator = SpanIter;
    using sentinel = SpanIter;
    using const_iterator = const SpanIter;
    using iterator_category = std::forward_iterator_tag;
    using difference_type = ptrdiff_t;
    using pointer = Span*;
    using size_type = size_t;

  public:
    SpanIter() noexcept { }
    SpanIter(const SpanIter& other) noexcept : current_(other.current_) { }
    SpanIter(const SpanIter&& other) noexcept : current_(other.current_) { }
    SpanIter(Span t) noexcept : current_(t) {
      current_.index = current_.start;
    }
    ~SpanIter() { }
    SpanIter& operator++() {
      if (current_.index < current_.start + current_.length)
        ++current_.index;
      return *this;
    }
    SpanIter operator++(int) {
      SpanIter result = *this;
      ++(*this);
      return result;
    }
    SpanIter& operator = (const SpanIter& other)
    {
      current_ = other.current_;
      return *this;
    }
    bool operator==(const SpanIter& other) const {
      return this->operator*() == *other;
    }
    bool operator!=(const SpanIter& other) const {
      return !(*this == other);
    }
    template<typename T>
    bool operator==(T&& value) const {
      return this->operator*() == std::forward<T>(value);
    }
    template<typename T>
    bool operator!=(T&& value) const {
      return !(*this == value);
    }
    Span& operator*() {
      return current_.start <= current_.index &&
        current_.index < current_.start + current_.length
        ? current_
        : Empty;
    }
    const Span& operator*() const {
      return current_.start <= current_.index &&
        current_.index < current_.start + current_.length
        ? current_
        : Empty;
    }
  private:
    Span current_;
  };

  SpanIter Span::begin() { return SpanIter(*this); }
  Span::sentinel Span::end() { return SpanIter(Span()); }

  SpanIter Span::cbegin() const { return SpanIter(*this); }
  Span::sentinel Span::cend() const { return SpanIter(Span()); }

  SpanIter begin(const Span& span) { return span.cbegin(); }
  Span::sentinel end(const Span& span) { return span.cbegin(); }


  void test() {

    // WORKS (confirm pipes work with vector)
    // output: 5 6
    std::vector<int> v { 5, 6, 7, 8, 9};
    auto works = v | std::views::take(2);
    for (int i : works)
      std::cout << i << " ";
    

    // FAILS !!!!!!!!!!!!!!!!!!!
    // <ranges> doesn't recognize my Span container as a range
    static_assert(std::ranges::range<Span>, "Is not a range");

    // FAILS !!!!!!!!!!!!!!!!!!!
    // binary '|': 'Span' does not define this operator or a conversion
    // to a type acceptable to the predefined operator
    Span span(6, 5, 8);
    auto fails = span | std::views::take(2);

    // WORKS: confirm my Span operates as a container with normal C++ 11 features
    // output:
    // range[5 - 9] : 5
    // range[5 - 9] : 6
    // range[5 - 9] : 7
    // range[5 - 9] : 8
    // range[5 - 9] : 9
    for (Span s : span)
      s.Print();
  }
}
1 Answers

The problem was that using statements for iterator traits need to be public ... that's it. This is surely obvious to most people familiar with C++, but may help other relative newbies like myself.

Related