{}
12. Функции begin
и end
просто передают пары начальных и конечных указателей, чтобы создать с их помощью экземпляры итераторов-упаковщиков:
zip_iterator begin() const {
return {std::begin(vec1), std::begin(vec2)};
}
zip_iterator end() const {
return {std::end(vec1), std::end(vec2)};
}
};
13. Как и в примерах кода на языках Haskell и Python, определяем два вектора, содержащих значения типа double
. Кроме того, указываем, что используем пространство имен std
внутри функции main
по умолчанию:
int main()
{
using namespace std;
vector
vector
14. Объект класса zipper
объединяет их в один диапазон данных, напоминающий вектор, где можно увидеть пары значений векторов a
и b
:
zipper zipped {a, b};
15. Используем метод std::accumulate
, чтобы сложить все элементы диапазона данных. Сделать это напрямую нельзя, поскольку в результате мы сложим экземпляры типа std::pair
, для которых концепция суммирования не определена. Поэтому зададим вспомогательное лямбда-выражение, которое принимает пару, перемножает ее члены и складывает результат со значением переменной-аккумулятора. Функция std::accumulate
хорошо работает с лямбда-выражениями со следующей сигнатурой:
const auto add_product ([](double sum, const auto &p) {
return sum + p.first * p.second;
}
);
16. Теперь передадим его функции std::accumulate
, а также пару итераторов для упаковываемых диапазонов и стартовое значение 0.0
для переменной-аккумулятора, которая, в конечном счете, будет содержать сумму произведений:
const auto dot_product (accumulate(
begin(zipped), end(zipped), 0.0, add_product));
17. Выведем на экран полученный результат:
cout << dot_product << '\n';
}
18. Компиляция и запуск программы дадут правильный результат:
32
Дополнительная информация
Да, нам пришлось написать много строк, чтобы добавить в код немного синтаксического сахара, и он все еще не так элегантен, как версия кода на Haskell. Большим недостатком является тот факт, что наш итератор-упаковщик жестко закодирован — он работает только для векторов, содержащих переменные типа double
.
С помощью шаблонов и типажей типов такой итератор может стать более обобщенным. Таким образом, он сможет работать со списками, векторами, двусторонними очередями и ассоциативными массивами, даже если они специализированы для совершенно других типов элементов контейнера.
Нельзя недооценивать объем работы, которую нужно выполнить, чтобы сделать эти классы обобщенными. К счастью, такие библиотеки уже существуют. Одной из популярных библиотек, не входящих в STL, является zip_iterator
). Ее легко использовать для самых разных классов.
Кстати, если хотите узнать о наиболее элегантном способе определения std::valarray
. Взгляните сами:
#include
#include
int main()
{
std::valarray
std::valarray
std::cout << (a * b).sum() << '\n';
}
Библиотека ranges. Это очень интересная библиотека для языка C++, которая поддерживает упаковщики и все прочие виды волшебных адаптеров итераторов, фильтров и т.д. Ее создатели вдохновлялись библиотекой Boost ranges
, и какое-то время казалось, что она может попасть в C++17, но, к сожалению, придется ждать следующего стандарта. Библиотека значительно улучшит возможности написания выразительного и быстрого кода на C++ путем создания сложных механизмов из универсальных и простых блоков кода. В документации к ней можно найти очень простые примеры.
1. Определение суммы квадратов всех чисел от 1
до 10
:
const int sum = accumulate(view::ints(1)
| view::transform([](int i){return i*i;})
| view::take(10), 0);
2. Фильтрация всех четных чисел из вектора чисел и преобразование остальных чисел в строки:
std::vector
auto rng = v | view::remove_if([](int i){return i % 2 == 1;})
| view::transform([](int i){return std::to_string(i);});
// rng == {"2"s,"4"s,"6"s,"8"s,"10"s};