比较简单的自定义容器签名不满足<ranges>概念

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

我正在尝试使用 <ranges> (C++20 MSVC) 迭代名为 Span 的自定义容器,其 begin/end() 方法 return a SpanIter.但是,以下静态断言在 test() 方法中失败,并且我的 class(但不是 vector<>)无法识别管道运算符。

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

我认为方法签名匹配 this working example。我无法找到丢失的内容,无论是方法还是 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();
  }
}

问题是迭代器特征的 using 语句需要 public ... 就是这样。这对于大多数熟悉 C++ 的人来说肯定是显而易见的,但可能会帮助像我这样的其他相关新手。