Zip-cpp
- это написанная на C++ библиотека, представляющая шаблонную функцию zip
, имитирующую поведение одноименной функции в Python 3.
Эта функция возвращает объект, поддерживающий семантику range-based for и позволяющий параллельно итерироваться по нескольким контейнерам.
Функция zip поддерживает следующие контейнеры:
- Все контейнеры STL (vector, string, set, map, list, ...).
- Массивы в стиле C (int[10]), при условии, что тип соответствующего выражения не был преобразован в указатель.
- Другие объекты, полученные ранее при помощи функции zip.
- Любые диапазоны, заданные двумя итераторами, удовлетворяющими требованиям категории InputIterator и шаблона iterator_traits, в том числе, определенными пользователем.
Пару итераторов необходимо скомпоновать в единый объект, при помощи тривиального класса-обертки
zipcpp::IterRange
или его аналога. Пример использования этой возможности для итераторов потока ввода и числовой последовательности приведен в файле advanced_examples.cpp.
Категория итератора контейнера, возвращаемого функцией zip
, соответствует наиболее общей категории итераторов переданных контейнеров.
Например, если в функцию были переданы только вектора, то возвращается итератор, соответствующий категории RandomAccessIterator
.
Если функция zip
была вызвана без аргументов, то возвращается пустой диапазон, заданный итераторами категории InputIterator
.
Поддерживаются все операции, требуемые для итераторов соответствующей категории со следующими исключениями:
- Не поддерживается оператор
->
для непосредственного доступа к методам кортежа значений, поскольку разыменование итератора возвращает временный объект. - Поскольку значение, возвращаемое разыменованным итератором, содержит кортеж ссылок, обмен значений при помощи временной переменной, некорректен (фактически, эквивалентен присваиванию
*it1 = *it2
)
// Некорректно, так как element хранит ссылки на те же объекты, что и *it1
auto element = move(*it1);
*it1 = std::move(*it2);
*it2 = std::move(element);
// Корректно
using std::swap;
swap(*it1, *it2);
// Корректно
std::iter_swap(it1, it2);
Открытым интерфейсом библиотеки являются следующие функции и классы, вложенные в пространство имен zipcpp
:
zip
- шаблонная функция, принимающая любое количество контейнеров и возвращающая объект, который можно рассматривать как "контейнер кортежей ссылок".IterRange
- шаблонный класс-контейнер, принимающий пару итераторов одного типа и представляющий заданный ими диапазон.
Вызов zip используется для прохождения по произвольному количеству итерируемых объектов произвольных типов. Например, следующий код:
iterable1 = [1, 2, 3, 4]
iterable2 = "abcde" # Этот контейнер длиннее остальных
iterable3 = {40, 30, 20, 10}
for v1, v2, v3 in zip(iterable1, iterable2, iterable3):
print(v1, v2, v3)
генерирует приведенный ниже вывод с точностью до порядка элементов в множестве:
1 a 40
2 b 10
3 c 20
4 d 30
При этом цикл завершается, как только итерация достигает конца хотя бы одного из диапазонов.
При помощи данной библиотеки приведенный выше код на Python можно переписать на C++ с сохранением семантики:
#include <iostream>
#include <string>
#include <unordered_set>
#include <vector>
#include "zip.h"
using zipcpp::zip;
int main(int, char**) {
std::vector<int> iterable1 = {1, 2, 3, 4};
std::string iterable2 = "abcde";
std::unordered_set<int> iterable3 = {40, 30, 20, 10};
for (const auto& [v1, v2, v3] : zip(iterable1, iterable2, iterable3)) {
std::cout << v1 << ' '
<< v2 << ' '
<< v3 << std::endl;
}
return 0;
}
Итераторы, взятые от переданных контейнеров, не должны инвалидироваться в течение всего срока жизни объекта Zip.
В частности, вызов zip(zip(a, b), zip(c, d))
некорректен: следует сохранить оба аргумента в отдельные переменные.
Фактически тип, возвращаемый выражением *zip_iterator
- обертка над кортежем ссылок, таким, как, std::tuple<int&, const int&>
.
Константность ссылки соответствует константности переданного контейнера.
Исключением является случай вложенных вызовов zip, в котором вложенные кортежи возвращаются по значению, а не по ссылке.
Кортеж можно использовать для инициализации отдельных переменных при помощи structured binding declaration:
const auto& [var1, var2, var3] = *zip_iterator;
В таком объявлении необходимо использовать либо const auto&
, чтобы переменные имели ссылочный тип (при этом ссылки не обязательно будут константными!), либо auto
для копирования значений.
Все классы и функции, необходимые для использования zip
, находятся в заголовочном файле zip.h.
Остальные файлы с исходным кодом в данном репозитории предоставляют юнит-тесты для библиотеки, а также функцию main, вызывающую эти тесты.
Наиболее простым способом использования данной библиотеки в другом проекте является копирование файла zip.h.
В качестве альтернативного решения для проектов, использующих Git и CMake можно добавить данный репозиторий в качестве подмодуля и соответствующим образом настроить команды add_subdirectory
, target_include_directories
, target_link_libraries
.
Для сборки проекта, использующего данную библиотеку, необходимо использовать версию стандарта не ниже C++17.