📚 Hub Books: Онлайн-чтение книгРазная литератураC++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц

Шрифт:

-
+

Интервал:

-
+
1 ... 29 30 31 32 33 34 35 36 37 ... 121
Перейти на страницу:
реализаций STL это может понадобиться для успешной компиляции кода:

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, очевидно, содержат строку. Обратите внимание: тип этой структуры и объект не являются безымянными, в отличие от лямбда-выражений. С точки зрения замыканий можно утверждать, что они захватывают строку. Когда экземпляр-пример вызывается

1 ... 29 30 31 32 33 34 35 36 37 ... 121
Перейти на страницу:

Комментарии

Обратите внимание, что комментарий должен быть не короче 20 символов. Покажите уважение к себе и другим пользователям!

Никто еще не прокомментировал. Хотите быть первым, кто выскажется?