Настоящий View в C++20 должен наследовать от
std::ranges::view_base или std::ranges::view_interface<T>. Давайте создадим практический пример — stride_view, который пропускает элементы диапазона с заданным шагом.🥝 Наследование от view_interface
Любой приличный view должен наследоваться от
std::ranges::view_interface<T>. Это даёт тебе begin(), end(), size() и прочие плюшки бесплатно.template<std::ranges::input_range R>
class stride_view : public std::ranges::view_interface<stride_view<R>> {
private:
// Если R уже view, используем R. Иначе — ref_view для безопасности
R base_;
std::ranges::range_difference_t<R> stride_;
public:
stride_view() = default;
stride_view(R base, std::ranges::range_difference_t<R> stride)
: base_(std::move(base)), stride_(stride) {
// Критическая проверка: stride должен быть положительным!
if (stride_ <= 0) {
throw std::invalid_argument("stride must be > 0");
}
}
auto begin() {
return stride_iterator<R>{ std::ranges::begin(base_),
std::ranges::end(base_), stride_ };
}
auto begin() const {
return stride_iterator<R>{ std::ranges::begin(base_),
std::ranges::end(base_), stride_ };
}
auto end() {
return stride_sentinel<R>{};
}
auto end() const {
return stride_sentinel<R>{};
}
};
😏 Видишь? Сохраняем базовый диапазон и размер шага. Дальше — только логика в итераторе.
☝️ Как работает stride_iterator и stride_sentinel
Итератор — это сердце view. На каждый
operator++() он прыгает на stride_ позиций вперёд, не переходя за конец диапазона:template<std::ranges::input_range R>
struct stride_sentinel {};
template<std::ranges::input_range R>
struct stride_iterator {
std::ranges::iterator_t<R> current_;
std::ranges::sentinel_t<R> end_;
std::ranges::range_difference_t<R> stride_;
// Pre-increment: ++it
stride_iterator& operator++() {
// 🔴 Критический момент: проверяем, не за пределами ли мы
for (int i = 0; i < stride_ && current_ != end_; ++i) {
++current_;
}
return *this;
}
// Post-increment: it++
stride_iterator operator++(int) {
auto tmp = *this;
++(*this);
return tmp;
}
auto operator*() const {
return *current_;
}
// Сравнение с концом диапазона через sentinel
bool operator==(const stride_sentinel<R>&) const {
return current_ == end_;
}
};
☝️ Почему sentinel? Потому что
begin() и end() могут быть разных типов в C++20 ranges. Это позволяет избежать ошибки типов при сравнении.⚡️ Пример использования
std::vector v = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto strided = stride_view(v, 2);
for (auto x : strided) {
std::cout << x << " "; // 0 2 4 6 8
}👌 Красиво: не создали новый вектор, просто гуляем по старому с шагом 2. Никакого копирования, только ленивые вычисления!
⚠️ Замечание: В C++23 это уже есть!
for (auto x : v | std::views::stride(2)) {
std::cout << x << " "; // 0 2 4 6 8
}🫰 std::views::stride уже входит в стандартную библиотеку! Пример выше — исключительно учебный📍Навигация: Вакансии • Задачи • Собесы
Библиотека C/C++ разработчика
#константная_правильность