Сегодня рассмотрим Range-v3 — библиотеку, которая изменила подход к обработке последовательностей в C++ и стала основой для std::ranges в C++20.
Range-v3 — это библиотека, расширяющая стандартную библиотеку C++ возможностью работать с диапазонами вместо begin()/end(). В основе идеи лежат три концепции:
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Фильтрация + преобразование
std::vector<int> filtered;
std::copy_if(data.begin(), data.end(), std::back_inserter(filtered),
[](int i) { return i % 2 == 0; });
std::vector<int> transformed;
std::transform(filtered.begin(), filtered.end(), std::back_inserter(transformed),
[](int i) { return i * i; });
for (int x : transformed) {
std::cout << x << " ";
}
Проблема:
#include <iostream>
#include <vector>
#include <range/v3/all.hpp>
int main() {
using namespace ranges;
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto rng = data
| views::filter([](int i) { return i % 2 == 0; }) // Оставляем чётные
| views::transform([](int i) { return i * i; }); // Возводим в квадрат
for (int x : rng) std::cout << x << " ";
}
Теперь у нас нет промежуточных векторов, никакого std::copy_if. Код читается сверху вниз, легко понять логику. Работает лениво и элементы вычисляются только при for
Вывод:
4 16 36 64 100
auto rng = data | views::filter([](int i) { return i % 2 == 0; });
происходит следующее:
auto rng = views::transform(data, [](int x) { return x * 2; }); // data копируется!
Ошибка: data передаётся по значению!
Правильный вариант:
auto rng = views::all(data) | views::transform([](int x) { return x * 2; });
Теперь data не копируется.
Пример сортировки и удаления дубликатов одной цепочкой:
#include <iostream>
#include <vector>
#include <range/v3/all.hpp>
int main() {
std::vector<int> data = {4, 2, 3, 1, 4, 3, 2, 5};
data |= ranges::actions::sort | ranges::actions::unique;
for (int x : data) std::cout << x << " ";
}
Выход:
1 2 3 4 5
actions::sort сортирует контейнер, actions::unique удаляет дубликаты
Альтернативный вариант (без |=):
std::vector<int> result = data | actions::sort | actions::unique;
Но он требует копирования.
ow, который возводит элементы в степень. Стандартные views::transform работают, но это некрасиво.
Сделаем свой View, который будет читаться так:
auto rng = data | views:
ow(3); // Возводим всё в куб
Создадим адаптер. Любой View в Range-v3 строится через view_adaptor. Это базовый класс, который управляет итерацией.
#include <range/v3/view/adaptor.hpp>
#include <cmath>
struct pow_view : ranges::view_adaptor<pow_view, ranges::view_base> {
friend ranges::range_access;
int power_;
explicit pow_view(int p) : power_(p) {}
// Магия: возведение в степень при итерации
template<typename It>
auto read(It it) const { return std:
ow(*it, power_); }
};
// Удобный адаптер
auto pow(int p) { return pow_view{p}; }
Этот pow_view работает как views::transform, но нагляднее.
Теперь кастомный View можно использовать точно так же, как стандартные.
#include <iostream>
#include <vector>
#include <range/v3/all.hpp>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
auto rng = data | pow(3); // Возводим в куб
for (int x : rng) std::cout << x << " ";
}
Выход:
1 8 27 64 125
В проде View должен уметь работать с разными контейнерами. Добавим поддержку любой последовательности.
template <typename Rng>
struct pow_view : ranges::view_adaptor<pow_view<Rng>, Rng> {
friend ranges::range_access;
int power_;
explicit pow_view(Rng rng, int p) : pow_view::view_adaptor(std::move(rng)), power_(p) {}
// Возводим в степень
auto read(auto it) const { return std:
ow(*it, power_); }
};
// Обёртка для удобного использования
template <typename Rng>
auto pow(Rng &&rng, int p) {
return pow_view<Rng>{std::forward<Rng>(rng), p};
}
Теперь это работает с любыми контейнерами:
std::list<double> numbers = {1.5, 2.0, 3.7, 4.1};
auto rng = pow(numbers, 2); // Возведение в квадрат
Теперь поддерживаем и std::list, и std::vector, и std::set!
Подробнее про Range-v3 можно посмотреть здесь.
12 февраля пройдет открытый урок «Отладка в С++. Место в жизненном цикле разработки», на котором:
- узнаете, как использовать отладчик GNU (GDB) для эффективной отладки программ на C++;
- научитесь выявлять и устранять проблемы, связанные с памятью в C++;
- разберетесь с понятием неопределенного поведения в C++ и с тем как его отладить;
- научитесь читать и интерпретировать трассировки стека, чтобы быстро определять местоположение ошибок.
habr.com
Range-v3 — это библиотека, расширяющая стандартную библиотеку C++ возможностью работать с диапазонами вместо begin()/end(). В основе идеи лежат три концепции:
- Views — ленивые представления данных
- Actions — eager-операции над контейнерами
- Pipeline (|) — декларативный синтаксис для обработки последовательностей
Почему Range-v3 может быть лучше стандартных алгоритмов?
Допустим, есть стандартный код на std::algorithm:std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// Фильтрация + преобразование
std::vector<int> filtered;
std::copy_if(data.begin(), data.end(), std::back_inserter(filtered),
[](int i) { return i % 2 == 0; });
std::vector<int> transformed;
std::transform(filtered.begin(), filtered.end(), std::back_inserter(transformed),
[](int i) { return i * i; });
for (int x : transformed) {
std::cout << x << " ";
}
Проблема:
- Куча промежуточных контейнеров filtered, transformed → лишние аллокации
- Много boilerplate кода → std::copy_if + std::transform
- Можно забыть std::back_inserter и получить UB
#include <iostream>
#include <vector>
#include <range/v3/all.hpp>
int main() {
using namespace ranges;
std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto rng = data
| views::filter([](int i) { return i % 2 == 0; }) // Оставляем чётные
| views::transform([](int i) { return i * i; }); // Возводим в квадрат
for (int x : rng) std::cout << x << " ";
}
Теперь у нас нет промежуточных векторов, никакого std::copy_if. Код читается сверху вниз, легко понять логику. Работает лениво и элементы вычисляются только при for
Вывод:
4 16 36 64 100
Как это работает?
Когда пишем:auto rng = data | views::filter([](int i) { return i % 2 == 0; });
происходит следующее:
- Создаётся ленивый View, который не делает никаких вычислений сразу
- rng хранит ссылку на data + фильтрующую лямбду
- При итерации по rng:
- Вызывается operator++
- Проверяется filter
- Неподходящие элементы пропускаются
auto rng = views::transform(data, [](int x) { return x * 2; }); // data копируется!
Ошибка: data передаётся по значению!
Правильный вариант:
auto rng = views::all(data) | views::transform([](int x) { return x * 2; });
Теперь data не копируется.
Actions
Если Views ленивы, то Actions сразу модифицируют контейнер.Пример сортировки и удаления дубликатов одной цепочкой:
#include <iostream>
#include <vector>
#include <range/v3/all.hpp>
int main() {
std::vector<int> data = {4, 2, 3, 1, 4, 3, 2, 5};
data |= ranges::actions::sort | ranges::actions::unique;
for (int x : data) std::cout << x << " ";
}
Выход:
1 2 3 4 5
actions::sort сортирует контейнер, actions::unique удаляет дубликаты
Альтернативный вариант (без |=):
std::vector<int> result = data | actions::sort | actions::unique;
Но он требует копирования.
Как писать свой кастомный View?
Допустим, нужен views:Сделаем свой View, который будет читаться так:
auto rng = data | views:
Создадим адаптер. Любой View в Range-v3 строится через view_adaptor. Это базовый класс, который управляет итерацией.
#include <range/v3/view/adaptor.hpp>
#include <cmath>
struct pow_view : ranges::view_adaptor<pow_view, ranges::view_base> {
friend ranges::range_access;
int power_;
explicit pow_view(int p) : power_(p) {}
// Магия: возведение в степень при итерации
template<typename It>
auto read(It it) const { return std:
};
// Удобный адаптер
auto pow(int p) { return pow_view{p}; }
Этот pow_view работает как views::transform, но нагляднее.
Теперь кастомный View можно использовать точно так же, как стандартные.
#include <iostream>
#include <vector>
#include <range/v3/all.hpp>
int main() {
std::vector<int> data = {1, 2, 3, 4, 5};
auto rng = data | pow(3); // Возводим в куб
for (int x : rng) std::cout << x << " ";
}
Выход:
1 8 27 64 125
В проде View должен уметь работать с разными контейнерами. Добавим поддержку любой последовательности.
template <typename Rng>
struct pow_view : ranges::view_adaptor<pow_view<Rng>, Rng> {
friend ranges::range_access;
int power_;
explicit pow_view(Rng rng, int p) : pow_view::view_adaptor(std::move(rng)), power_(p) {}
// Возводим в степень
auto read(auto it) const { return std:
};
// Обёртка для удобного использования
template <typename Rng>
auto pow(Rng &&rng, int p) {
return pow_view<Rng>{std::forward<Rng>(rng), p};
}
Теперь это работает с любыми контейнерами:
std::list<double> numbers = {1.5, 2.0, 3.7, 4.1};
auto rng = pow(numbers, 2); // Возведение в квадрат
Теперь поддерживаем и std::list, и std::vector, и std::set!
Подробнее про Range-v3 можно посмотреть здесь.
12 февраля пройдет открытый урок «Отладка в С++. Место в жизненном цикле разработки», на котором:
- узнаете, как использовать отладчик GNU (GDB) для эффективной отладки программ на C++;
- научитесь выявлять и устранять проблемы, связанные с памятью в C++;
- разберетесь с понятием неопределенного поведения в C++ и с тем как его отладить;
- научитесь читать и интерпретировать трассировки стека, чтобы быстро определять местоположение ошибок.

Range-v3 в C++
Привет, Хабр! Сегодня рассмотрим Range-v3 — библиотеку, которая изменила подход к обработке последовательностей в C++ и стала основой для std::ranges в C++20. Range-v3 — это библиотека, расширяющая...
