Вопрос или проблема
Мой исходный вопрос
Как я могу написать класс на 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, включая:
- Как заставить мой пользовательский тип работать с “циклами for на основе диапазонов”?
- Создание итератора с концепциями C++20 для пользовательского контейнера
- В чем разница между std::ranges::begin и std::begin?
- Что я пропускаю в своем пользовательском итераторе std::ranges?
прежде чем в конечном итоге оказался здесь:
Как поддерживать адаптеры диапазонов в пользовательском контейнере?
Мои новые вопросы
При чтении принятого ответа 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) и предоставляет дополнительные концепции и требования для итераторов.
Пошаговая реализация собственного итератора
-
Определение типов: Начнем с указания необходимых типов и категорий итератора. Для
std::forward_iterator
нужны следующие типы:value_type
: тип элементов, которые будет возвращать итератор.difference_type
: тип, используемый для представления разности между итераторами.iterator_category
: категория итератора (std::forward_iterator_tag
).
-
Создание класса итератора: В определении класса
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
. Надеюсь, данное руководство окажется для вас полезным в вашей разработке. Если у вас возникнут дополнительные вопросы или потребуется помощь с конкретными примерами, не стесняйтесь обращаться за поддержкой.