Шрифт:
Интервал:
Закладка:
namespace std {
template <>
struct iterator_traits<zip_iterator> {
using iterator_category = std::forward_iterator_tag;
using value_type = std::pair<double, double>;
using difference_type = long int;
};
}
10. Следующий шаг — определение класса диапазона данных, функции begin и end которого возвращают итераторы-упаковщики:
class zipper {
using vec_type = std::vector<double>;
vec_type &vec1;
vec_type &vec2;
11. Он должен сослаться на два существующих контейнера, чтобы создать итераторы-упаковщики:
public:
zipper(vec_type &va, vec_type &vb)
: vec1{va}, vec2{vb}
{}
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<double> a {1.0, 2.0, 3.0};
vector<double> b {4.0, 5.0, 6.0};
14. Объект класса zipper объединяет их в один диапазон данных, напоминающий вектор, где можно увидеть пары значений векторов a и b:
zipper zipped {a, b};
15. Используем метод std::accumulate, чтобы сложить все элементы диапазона данных. Сделать это напрямую нельзя, поскольку в результате мы сложим экземпляры типа std::pair<double, double>, для которых концепция суммирования не определена. Поэтому зададим вспомогательное лямбда-выражение, которое принимает пару, перемножает ее члены и складывает результат со значением переменной-аккумулятора. Функция 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, является Boost (zip_iterator). Ее легко использовать для самых разных классов.
Кстати, если хотите узнать о наиболее элегантном способе определения скалярного произведения в C++ и вам не особо нужна концепция итераторов-упаковщиков, то обратите внимание на std::valarray. Взгляните сами:
#include <iostream>
#include <valarray>
int main()
{
std::valarray<double> a {1.0, 2.0, 3.0};
std::valarray<double> b {4.0, 5.0, 6.0};
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<int> v {1,2,3,4,5,6,7,8,9,10};
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};
Если вы заинтересовались и не можете дождаться выхода следующего стандарта С++, то обратитесь к документации для ranges, которая находится по адресу https://ericniebler.github.io/range-v3/.
Глава 4
Лямбда-выражения
В этой главе:
□ динамическое определение функций с помощью лямбда-выражений;
□ добавление полиморфизма путем оборачивания лямбда-выражений в конструкцию std::function;
□ создание функций с помощью конкатенации;
□ создание сложных предикатов с помощью логической конъюнкции;
□ вызов нескольких функций с одними и теми же входными данными;
□ реализация transform_if с применением std::accumulate и лямбда-выражений;
□ генерация декартова произведения на основе любых входных данных во время компиляции.
Введение
Одной из важных новых функций C++11 были лямбда-выражения. В C++14 и C++17 они получили новые возможности, и это сделало их еще мощнее. Но что же такое лямбда-выражение?
Лямбда-выражения или лямбда-функции создают замыкания. Замыкание — очень обобщенный термин для безымянных объектов, которые можно вызывать как функции. Чтобы предоставить подобную возможность в С++, такой объект должен реализовывать оператор вызова функции (), с параметрами или без. Создание аналогичного объекта без лямбда-выражений до появления С++11 выглядело бы так:
#include <iostream>
#include <string>
int main() {
struct name_greeter {
std::string name; void operator()() {
std::cout << "Hello, " << name << 'n';
}
};
name_greeter greet_john_doe {"John Doe"};
greet_john_doe();
}
Экземпляры структуры name_greeter, очевидно, содержат строку. Обратите внимание: тип этой структуры и объект не являются безымянными, в отличие от лямбда-выражений. С точки зрения замыканий можно утверждать, что они захватывают строку. Когда экземпляр-пример вызывается