range-v3 для C++: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в range-v3: что это и зачем нужно

Библиотека range-v3 — это мощная, современная библиотека для работы с последовательностями данных в C++, разработанная Эриком Ниблером (Eric Niebler). Она стала предтечей и прототипом для стандартной библиотеки C++20 Ranges. range-v3 предоставляет удобные, выразительные и безопасные инструменты для обработки коллекций, векторов, списков и любых других итерируемых объектов, используя концепцию «ленивых вычислений» и композиции алгоритмов.

Зачем нужна эта библиотека? Традиционные алгоритмы STL (std::transform, std::copy_if и т.д.) работают с итераторами, что часто приводит к громоздкому и трудночитаемому коду. range-v3 решает эту проблему, позволяя работать с целыми диапазонами (ranges) как с едиными объектами. Вы можете объединять операции в цепочки (pipeline), не создавая временные контейнеры, и писать код, который выглядит как последовательность шагов, а не как вложенные вызовы функций.

Основные преимущества:

  • Выразительность: код становится похож на декларативное описание того, что нужно сделать, а не как это сделать.
  • Ленивость: многие операции (фильтрация, трансформация) выполняются только когда это действительно необходимо, что повышает производительность.
  • Композиция: легко комбинировать различные алгоритмы с помощью оператора | (pipe).
  • Безопасность: меньше ошибок, связанных с выходом за границы и неправильным использованием итераторов.
  • Совместимость: работает с любыми контейнерами STL и пользовательскими типами, поддерживающими итерацию.

Установка range-v3

В отличие от Python или JavaScript, в C++ нет единого менеджера пакетов, подобного pip или npm. Однако range-v3 — это header-only библиотека (состоит только из заголовочных файлов), что делает её установку очень простой.

Способ 1: Vcpkg (рекомендуемый для Windows, Linux, macOS)

Vcpkg — это кроссплатформенный менеджер пакетов для C++.

# Установите vcpkg, если ещё не сделали этого
git clone https://github.com/Microsoft/vcpkg.git
cd vcpkg
./bootstrap-vcpkg.sh  # или bootstrap-vcpkg.bat для Windows

# Установите range-v3
./vcpkg install range-v3

# Подключите в проекте (CMake):
# find_package(range-v3 CONFIG REQUIRED)
# target_link_libraries(my_project PRIVATE range-v3::range-v3 range-v3::meta)

Способ 2: Conan

Conan — ещё один популярный менеджер пакетов.

# Установите Conan
pip install conan

# Добавьте в файл conanfile.txt:
[requires]
range-v3/0.12.0

[generators]
cmake

# Затем в CMakeLists.txt:
# find_package(range-v3 CONFIG REQUIRED)
# target_link_libraries(my_project PRIVATE range-v3::range-v3 range-v3::meta)

Способ 3: Ручная установка (скачать и скопировать)

Поскольку библиотека header-only, вы можете просто скачать её с GitHub и добавить путь к include в ваш проект.

git clone https://github.com/ericniebler/range-v3.git
# Затем в настройках компилятора укажите:
# -I /path/to/range-v3/include

Способ 4: CMake FetchContent (современный подход)

Этот метод автоматически загружает библиотеку во время сборки.

include(FetchContent)
FetchContent_Declare(
  range-v3
  GIT_REPOSITORY https://github.com/ericniebler/range-v3.git
  GIT_TAG 0.12.0
)
FetchContent_MakeAvailable(range-v3)

target_link_libraries(my_project PRIVATE range-v3::range-v3 range-v3::meta)

Быстрый старт: первый пример

Давайте напишем простую программу, которая демонстрирует основные возможности range-v3. Мы создадим вектор чисел, отфильтруем чётные, умножим их на 2 и выведем результат.

#include <iostream>
#include <vector>
#include <range/v3/all.hpp>

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};

    // Используем pipeline: взять числа, отфильтровать чётные, умножить на 2
    auto result = numbers 
        | ranges::views::filter([](int n) { return n % 2 == 0; })
        | ranges::views::transform([](int n) { return n * 2; });

    std::cout << "Результат: ";
    for (int n : result) {
        std::cout << n << " ";
    }
    // Вывод: Результат: 4 8 12 16 20

    return 0;
}

Обратите внимание: result — это не вектор, а ленивое представление (view). Реальные вычисления происходят только в цикле for. Это позволяет экономить память и процессорное время.

Основные методы и функции range-v3

Библиотека состоит из двух основных пространств имён: ranges::views (для создания представлений) и ranges::actions (для модификации контейнеров). Рассмотрим 10 ключевых функций.

1. ranges::views::filter (фильтрация)

Что делает: Создаёт представление, содержащее только те элементы исходного диапазона, которые удовлетворяют заданному предикату (условию).

Параметры:

  • pred — унарный предикат (функция, возвращающая bool), принимающий элемент диапазона.

Пример:

#include <vector>
#include <range/v3/view/filter.hpp>
#include <iostream>

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5, 6};
    auto even = data | ranges::views::filter([](int i) { return i % 2 == 0; });
    for (int i : even) std::cout << i << " "; // Вывод: 2 4 6
    return 0;
}

2. ranges::views::transform (преобразование)

Что делает: Применяет заданную функцию к каждому элементу диапазона и возвращает новое представление с преобразованными значениями.

Параметры:

  • fun — унарная функция, принимающая элемент и возвращающая новое значение.

Пример:

#include <vector>
#include <range/v3/view/transform.hpp>
#include <iostream>

int main() {
    std::vector<int> data = {1, 2, 3};
    auto squared = data | ranges::views::transform([](int i) { return i * i; });
    for (int i : squared) std::cout << i << " "; // Вывод: 1 4 9
    return 0;
}

3. ranges::views::take (взять N элементов)

Что делает: Создаёт представление, содержащее первые N элементов исходного диапазона. Если элементов меньше N, возвращает все доступные.

Параметры:

  • n — количество элементов, которые нужно взять.

Пример:

#include <vector>
#include <range/v3/view/take.hpp>
#include <iostream>

int main() {
    std::vector<int> data = {10, 20, 30, 40, 50};
    auto first_three = data | ranges::views::take(3);
    for (int i : first_three) std::cout << i << " "; // Вывод: 10 20 30
    return 0;
}

4. ranges::views::drop (пропустить N элементов)

Что делает: Создаёт представление, которое пропускает первые N элементов и возвращает остальные.

Параметры:

  • n — количество элементов, которые нужно пропустить.

Пример:

#include <vector>
#include <range/v3/view/drop.hpp>
#include <iostream>

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5};
    auto after_two = data | ranges::views::drop(2);
    for (int i : after_two) std::cout << i << " "; // Вывод: 3 4 5
    return 0;
}

5. ranges::views::reverse (обратный порядок)

Что делает: Создаёт представление, которое перебирает элементы исходного диапазона в обратном порядке.

Параметры: Нет (работает с любым двунаправленным диапазоном).

Пример:

#include <vector>
#include <range/v3/view/reverse.hpp>
#include <iostream>

int main() {
    std::vector<int> data = {1, 2, 3, 4, 5};
    auto rev = data | ranges::views::reverse;
    for (int i : rev) std::cout << i << " "; // Вывод: 5 4 3 2 1
    return 0;
}

6. ranges::views::join (объединение)

Что делает: «Склеивает» диапазон диапазонов (например, вектор векторов) в один плоский диапазон.

Параметры: Необязательный разделитель (диапазон или одиночный элемент), который будет вставлен между объединяемыми диапазонами.

Пример:

#include <vector>
#include <range/v3/view/join.hpp>
#include <iostream>

int main() {
    std::vector<std::vector<int>> nested = {{1, 2}, {3, 4}, {5}};
    auto flat = nested | ranges::views::join;
    for (int i : flat) std::cout << i << " "; // Вывод: 1 2 3 4 5
    return 0;
}

7. ranges::views::zip (сжатие)

Что делает: Объединяет несколько диапазонов в один, создавая кортежи из элементов с одинаковыми индексами. Работает до тех пор, пока не закончится самый короткий диапазон.

Параметры: Произвольное количество диапазонов.

Пример:

#include <vector>
#include <string>
#include <range/v3/view/zip.hpp>
#include <iostream>

int main() {
    std::vector<int> ids = {1, 2, 3};
    std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
    auto zipped = ranges::views::zip(ids, names);
    for (auto [id, name] : zipped) {
        std::cout << id << ": " << name << std::endl;
    }
    // Вывод:
    // 1: Alice
    // 2: Bob
    // 3: Charlie
    return 0;
}

8. ranges::views::enumerate (нумерация)

Что делает: Добавляет к каждому элементу его индекс, возвращая пары (индекс, значение). Очень удобно для циклов с отслеживанием позиции.

Параметры: Необязательное начальное значение индекса (по умолчанию 0).

Пример:

Рекомендаций