Как написать настраиваемый итератор прямого диапазона, работающий с диапазонами C++20

Вопрос или проблема

Мой исходный вопрос

Как я могу написать класс на C++, который работает с библиотекой std::ranges C++20?

Происхождение моей проблемы

Я пытался написать класс-обертку с итераторами на C++. Но мой компилятор (clang++18) сказал мне, что требования не выполнены, и самым глубоким невыполненным требованием является: /usr/bin/../lib/gcc/x86_64-linux-gnu/14/../../../../include/c++/14/ranges:946:30: note: because '__adaptor::__is_range_adaptor_closure_fn(__t, __t)' would be invalid: no matching function for call to '__is_range_adaptor_closure_fn'.

Мое путешествие к решению этой проблемы на данный момент

Я прочитал несколько постов на SO, включая:

прежде чем в конечном итоге оказался здесь:
Как поддерживать адаптеры диапазонов в пользовательском контейнере?

Мои новые вопросы

При чтении принятого ответа bolov на последний пост, у меня возникли несколько новых вопросов:

  • Почему итератору необходимо быть конструируемым по умолчанию? В моей текущей реализации это проблема, потому что он содержит константные ссылки на большие объекты. (Это может быть разрешено, но нужно ли это делать и как — это разные вопросы.)
  • Почему нужно difference_type, если цель — это std::forward_iterator без реализации operator-- и operator-?

Пример

Следующий код — это упрощенный пример моего класса-обертки.

#include <cstddeff>
#include <unordered_set>

using Set = std::unordered_set<std::ptrdiff_t>;

class MyFacadeClass {
private:
  class Iterator {
  public:
    using iterator_category = std::forward_iterator_tag;
    using value_type        = std::ptrdiff_t;

    Iterator(const std::ptrdiff_t self,
             const Set& a,
             const Set& b,
             const Set::const_iterator it,
             const bool inB = false) :
      pSelf(self),
      pA(a),
      pB(b),
      pIt(it),
      pInB(inB) {}

    std::ptrdiff_t operator*() const { return *pIt; }

    bool operator!=(const Iterator& other) const { return pIt != other.pIt; }

    Iterator& operator++() {
        if (pInB) {
          ++pIt;
          return *this;
        }
        else {
          do {
            ++pIt;
            if (pIt == pA.end()) {
              pIt        = pB.begin();
              pInB = true;
              return *this;
            }
          } while (*pIt == pSelf);
          return *this;
        }
      }

  private:
    const std::ptrdiff_t pSelf;
    const Set& pA;
    const Set& pB;
    Set::const_iterator pIt;
    bool pInB;
  };

  static_assert(std::forward_iterator<Iterator>);

public:
  Iterator begin() const { return Iterator(self, pA, pB, pA.begin()); }
  Iterator end() const { return Iterator(self, pA, pB, pB.end()); }

private:
  const std::ptrdiff_t self;
  const Set& pA;
  const Set& pB;
};

Цель

Было бы здорово использовать синтаксис C++, такой как


for(const std::ptrdiff_t& member : myClass | std::views::filter(CONDITION)) {

}

Здесь мы предполагаем, что myClass имеет тип const MyFacadeClass, определенный ранее в коде.

Прояснение перечисленных вопросов было бы приятно. Спасибо заранее!

Ответ или решение

Создание настраиваемого итератора с использованием C++20: Пошаговое руководство

Вопрос о том, как написать собственный итератор для работы с библиотекой std::ranges в C++20, требует понимания как концепций, так и деталей реализации. Ниже предоставляю подробную информацию о том, как создать пользовательский итератор типа std::forward_iterator, который будет работать в рамках вашей фасадной модели.

Основы: Зачем нужен итератор?

Итераторы – это стандартный способ доступа к элементам, которые могут быть коллекцией данных или любой другой последовательностью. Стандартная библиотека C++20 вводит новые возможности для работы с диапазонами (ranges) и предоставляет дополнительные концепции и требования для итераторов.

Пошаговая реализация собственного итератора

  1. Определение типов: Начнем с указания необходимых типов и категорий итератора. Для std::forward_iterator нужны следующие типы:

    • value_type: тип элементов, которые будет возвращать итератор.
    • difference_type: тип, используемый для представления разности между итераторами.
    • iterator_category: категория итератора (std::forward_iterator_tag).
  2. Создание класса итератора: В определении класса Iterator создадим необходимые типы и реализуем основные операторы.

class Iterator {
public:
    using iterator_category = std::forward_iterator_tag;
    using value_type        = std::ptrdiff_t;
    using difference_type   = std::ptrdiff_t; // Обозначение разности 

    Iterator(const std::ptrdiff_t self,
             const Set& a,
             const Set& b,
             const Set::const_iterator it,
             const bool inB = false) :
      pSelf(self), pA(a), pB(b), pIt(it), pInB(inB) {}

    std::ptrdiff_t operator*() const { return *pIt; }

    bool operator!=(const Iterator& other) const { return pIt != other.pIt; }

    Iterator& operator++() {
        if (pInB) {
            ++pIt;
            return *this;
        } else {
            do {
                ++pIt;
                if (pIt == pA.end()) {
                    pIt = pB.begin();
                    pInB = true;
                    return *this;
                }
            } while (*pIt == pSelf);
            return *this;
        }
    }
private:
    const std::ptrdiff_t pSelf;
    const Set& pA;
    const Set& pB;
    Set::const_iterator pIt;
    bool pInB;
};

Ответы на основные вопросы

  • Зачем необходим difference_type?

    Значение difference_type служит для определения расстояния между элементами в рамках последовательности. Даже если у вас и не реализованы операторы декремента operator-- и вы не будете использовать operator-, difference_type необходим для соблюдения требований, предъявляемых к стандартным итераторам. Он также может быть полезен для алгоритмов и функций, которые задействуют разницу между итераторами.

  • 为什么需要默认构руируемость итератора?

    Итераторы должны быть по умолчанию конструктивными, чтобы позволить их применение в различных алгоритмах и контейнерах. Это стандартное требование, так как некоторые алгоритмы могут создавать временные итераторы. Если ваш итератор содержит ссылки на большие объекты и не может быть создан без этих объектов, рассмотрите возможность создания специального "пустого" итератора, который будет представлять отсутствие данных.

Использование в коде

Теперь, когда итератор определён, вы можете использовать его вfor циклах и комбинировать с <ranges>:

const MyFacadeClass myClass(/*...*/);
for (const std::ptrdiff_t& member : myClass | std::views::filter(CONDITION)) {
   // Ваши действия с членом
}

Заключение

Создание настраиваемого итератора для работы с C++20 std::ranges является сложной, но выполнимой задачей. Убедитесь, что соблюдены все требования и концепции для достижения совместимости с std::ranges. Надеюсь, данное руководство окажется для вас полезным в вашей разработке. Если у вас возникнут дополнительные вопросы или потребуется помощь с конкретными примерами, не стесняйтесь обращаться за поддержкой.

Оцените материал
Добавить комментарий

Капча загружается...