C++17 STL Стандартная библиотека шаблонов — страница 36 из 119

      return t(parameters...);

    };

  }

}


Напишем более простую версию этой функции, которая объединяет ровно три функции:


template 

auto concat(F f, G g, H h)

{

  return [=](auto ... params) {

    return f(g(h(params...)));

  };

}


Эта функция выглядит аналогично, но уже не так сложна. Мы возвращаем лямбда-выражение, которое захватывает

f
,
g
и
h
. Оно принимает произвольно большое количество параметров и просто перенаправляет их по цепочке вызовов
f
,
g
и
h
. Если мы пользуемся конструкцией
auto combined(concat(f,g,h))
, а затем вызываем данный объект функции с двумя параметрами, например
combined(2,3)
, то
2
,
3
представлены набором параметров из предыдущей функции
concat
.

Повторный взгляд на гораздо более сложную обобщенную функцию

concat
позволяет увидеть следующее: единственное, что мы действительно делаем по-другому, — выполняем конкатенацию
f(g(h(params...)))
. Вместо этого мы пишем выражение
f(concat(g,h))(params...)
, которое будет преобразовано в конструкцию
f(g(concat(h)))(params...)
при следующем рекурсивном вызове, а затем — в конструкцию
f(g(h(params...)))

Создаем сложные предикаты с помощью логической конъюнкции

При фильтрации данных с помощью обобщенного кода мы определяем предикаты, которые указывают, какие именно данные нужны. Иногда предикаты являются комбинациями нескольких «собратьев».

При фильтрации строк, например, можно реализовать предикат, который возвращает значение

true
, если входная строка начинается со слова "
foo
". Еще один предикат должен возвращать значение
true
, если входная строка заканчивается словом "
bar
".

Вместо того чтобы постоянно писать собственные предикаты, можно повторно использовать предикаты, объединив их. Для фильтрации строк, которые начинаются со слова "

foo
" и заканчиваются словом "
bar
", можно просто выбрать уже существующие предикаты и объединить их с помощью логического
И
. В данном разделе мы будем работать с лямбда-выражениями, чтобы найти удобный способ сделать это.


Как это делается

В этом примере мы реализуем очень простые предикаты для фильтрации строк, а затем объединим их с помощью небольшой вспомогательной функции, которая создаст их комбинацию в обобщенном виде.


1. Как обычно, сначала включим несколько заголовочных файлов:


#include 

#include 

#include 

#include 

#include 


2. Поскольку они понадобятся нам в дальнейшем, реализуем две простые функции-предиката. Одна из них говорит о том, начинается ли строка с символа

'a'
, а вторая — заканчивается ли строка символом
'b'
:


static bool begins_with_a (const std::string &s)

{

  return s.find("a") == 0;

}


static bool ends_with_b (const std::string &s)

{

  return s.rfind("b") == s.length() - 1;

}


3. Теперь реализуем вспомогательную функцию и назовем ее

combine
. Она принимает бинарную функцию в качестве первого параметра — это может быть, например, логическое
И
либо логическое
ИЛИ
. Затем она принимает два других параметра, представляющих собой две функции-предиката, которые нужно объединить:


template 

auto combine(F binary_func, A a, B b)

{


4. Просто возвращаем лямбда-выражение, которое захватывает новую комбинацию предикатов. Оно направляет параметр обоим предикатам, а затем помещает результаты работы их обоих в бинарную функцию и возвращает ее результат:


  return [=](auto param) {

    return binary_func(a(param), b(param));

  };

}


5. Укажем, что будем использовать пространство имен

std
с целью сэкономить немного времени при написании функции
main
:


using namespace std;


6. Теперь объединим две функции-предиката в другую функцию-предикат, говорящую, начинается ли заданная строка с символа

a
и заканчивается ли символом
b
, как, например, строки
"ab"
или
"axxxb"
. На роль бинарной функции выбираем
std::logical_and
. Это шаблонный класс, экземпляр которого нужно создавать, вследствие чего будем использовать его вместе с фигурными скобками. Обратите внимание: мы не предоставляем дополнительный параметр шаблона, поскольку для данного класса он по умолчанию будет равен
void
. Эта специализация класса самостоятельно выводит все типы параметров:


int main()

{

  auto a_xxx_b (combine(

    logical_and<>{},

    begins_with_a, ends_with_b));


7. Итерируем по стандартному потоку ввода и выводим на экран все слова, которые удовлетворяют условиям предиката:


  copy_if(istream_iterator{cin}, {},

    ostream_iterator{cout, ", "},

    a_xxx_b);

  cout << '\n';

}


8. Компиляция и запуск программы дадут следующий результат. Мы передаем программе четыре слова, но только два из них удовлетворяют условиям предиката:


$ echo "ac cb ab axxxb" | ./combine

ab, axxxb,


Дополнительная информация

Библиотека STL уже предоставляет несколько функциональных объектов наподобие

std::logical_and
,
std::logical_or
, а также множество других, поэтому не нужно реализовывать их в каждом проекте. Ознакомиться со справочным материалом по С++ и исследовать доступные варианты можно на http://en.cppreference.com/w/cpp/utility/functional.

Вызываем несколько функций с одинаковыми входными данными

 Существует множество задач, чьи решения требуют написания повторяющегося кода. Большую его часть можно легко заменить лямбда-выражениями, и очень просто создать вспомогательную функцию, которая будет оборачивать подобные повторяющиеся задачи.

В данном разделе мы поработаем с лямбда-выражениями, чтобы направить один вызов функции со всеми его параметрами нескольким получателям. Мы сделаем это, не задействовав никаких дополнительных структур данных, так что перед компилятором будет стоять простая задача: сгенерировать бинарный файл без лишних затрат.


Как это делается

В этом примере мы напишем вспомогательную функцию для работы с лямбда-выражениями, которая направляет один вызов нескольким объектам, и еще одну такую функцию, направляющую один вызов нескольким функциям. Эту комбинацию мы используем в нашем примере, чтобы вывести на экран одно сообщение с помощью разных функций-принтеров.


1. Включим заголовочный файл, необходимый для вывода данных на экран:


#include 


2. Сначала реализуем функцию

multicall
, которая является основной для этого примера. Она принимает произвольное количество функций в качестве параметров и возвращает лямбда-выражение, принимающее один параметр. Она перенаправляет данный параметр всем функциям, предоставленным ранее. Таким образом, можно определить функцию
auto call_all(multicall(f,g,h))