Перейти к содержанию

Последнее обновление:2026-10-12

Библиотека Ranges в C++20

ranges — одна из главных возможностей C++20, и "view" является весьма важной её частью. До C++20 алгоритмы стандартной библиотеки реализовывались на основе итераторов, например: std::sort.

std::sort(v.begin() + 2, v.end())

Итераторы + алгоритмы позволяют выполнять ряд сложных операций; например, я хочу отсортировать в обратном порядке:

std::sort(v.rbegin(), v.rend())

Но это сопряжено с некоторыми проблемами:

  • Легко смешать два несовместимых итератора.
  • Композиционность алгоритмов слишком слабая, приходится хранить промежуточные переменные

Например: пусть есть система информации о студентах, и мы хотим вычислить суммарный GPA студентов в возрасте от 21 до 25 лет с GPA >= 3.5. До C++17 мы могли бы написать такой псевдокод:

std::vector<Student> students
std::vector<Student> selected;
std::copy_if(students.begin(), students.end(), std::back_inserter(selected),
             [](const Student& student) {
               return student.age >= 21 && student.age <= 25 && student.gpa >= 3.5;
             });

double s = std::accumulate(
    selected.begin(), selected.end(), 0.0,
    [](double sum, const Student& student) { return sum + student.gpa; });

Видно, что при фильтрации студентов, удовлетворяющих условиям, нам приходится копировать их в selected и лишь затем суммировать.

C++20 привнёс более лаконичный и эффективный способ: используя диапазоны и оператор конвейера | для соединения нескольких операций, можно просуммировать напрямую, без промежуточных переменных, например:

double s = 0.0;
for (const auto& student :
     students | std::views::filter([](const auto& s) {
       return s.age >= 21 && s.age <= 25 && s.gpa >= 3.5;
     }) | std::views::transform([](const auto& s) { return s.gpa; })) {
  s += student;
}

Видно, что несколько алгоритмов стыкуются бесшовно, уменьшая выделение памяти под промежуточные временные переменные, а читабельность значительно возрастает: конвейер | соединяет разные операции воедино.

Давайте вместе разберёмся с материалом, связанным с ranges в C++20.

1.Базовые понятия

1.range

range — это абстрактное понятие, обозначающее последовательность. Это может быть любой контейнер с итераторами или объект, определяющий функции begin() и end(). Например, std::vector, std::list и другие — примеры диапазонов; массив тоже можно рассматривать как диапазон.

2.view

view — это доступ только для чтения к Range. Это способ ленивых вычислений: расчёт происходит только тогда, когда он нужен, а это значит, что view фактически не хранит данные. Например, std::views::filter и std::views::transform — типичные примеры view. Они позволяют фильтровать и преобразовывать range, не создавая фактически новый контейнер.

3.algorithm

Алгоритмы — это функции, работающие с range или view; например, std::sort, std::find и другие — примеры алгоритмов.

4.|

Оператор конвейера | может соединять представления и алгоритмы, передавая результат слева на вход справа. Он делает код более ясным и лаконичным. Например: students | std::views::filter(...) передаёт диапазон students в std::views::filter для фильтрации, а затем результат передаётся последующим операциям.

Возьмём для примера описанное выше вычисление по студентам: здесь мы используем диапазон students как вход представления filter через |, затем результат подаём на вход представления transform, и в конце возвращается диапазон, по которому мы итерируемся в цикле, получая сумму с помощью алгоритма накопления.

double s = 0.0;
for (const auto& student :
     students | std::views::filter([](const auto& s) {
       return s.age >= 21 && s.age <= 25 && s.gpa >= 3.5;
     }) | std::views::transform([](const auto& s) { return s.gpa; })) {
  s += student;
}

Видно, что использование range даёт такие преимущества:

  • Повышение читабельности кода
  • Ленивые вычисления: Ranges вводят понятие представления (view), позволяя ленивые вычисления, то есть вычисление результата только тогда, когда он нужен.

Например: вычисление произойдёт только при обращении *v.begin().

auto v = std::views::reverse(vec);
std::cout << *v.begin() << std::endl;
  • Меньше ошибок: дизайн Ranges помогает избежать ряда ошибок, типичных для классических операций с итераторами, например использования несовместимых итераторов.
  • и так далее.

Концепция диапазона вводит разные понятия для описания разных типов диапазонов. Эти понятия помогают в обобщённом программировании лучше понимать и ограничивать свойства диапазонов. Вот некоторые часто используемые понятия диапазонов:

https://en.cppreference.com/w/cpp/ranges

Понятие Описание Примеры контейнеров
std::ranges::input_range Можно итерировать от начала до конца хотя бы раз std::forward_list、std::list、std::duque、std::array、std::vector
std::ranges::forward_range Можно итерировать от начала до конца много раз std::forward_list、std::list、std::duque、std::array、std::vector
std::ranges::bidirectional_range Итератор может также двигаться назад -- std::list、std::duque、std::array、std::vector
std::ranges::random_access_range Можно перейти к элементу [] за константное время std::duque、std::array、std::vector
std::ranges::contiguous_range Элементы всегда хранятся в памяти непрерывно std::array、std::vector

2.Использование

Использовать эту возможность довольно просто: достаточно подключить заголовочный файл и использовать интерфейсы.

Например: отфильтровать чётные числа из набора чисел.

#include <ranges>
auto evenNumbers = numbers | std::views::filter([](int x) { return x % 2 == 0; });

Компиляция: достаточно указать -std.

g++ -std=c++20 main.cc -o main

О поддержке компиляторами можно прочитать в списке ниже:

https://en.cppreference.com/w/cpp/compiler_support/20

gcc >=10, clang >=13 (частично), >=15 — полная поддержка.

Похожие темы

Комментарии