C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц
Книгу C++17 STL Стандартная библиотека шаблонов - Яцек Галовиц читаем онлайн бесплатно полную версию! Чтобы начать читать не надо регистрации. Напомним, что читать онлайн вы можете не только на компьютере, но и на андроид (Android), iPhone и iPad. Приятного чтения!
Шрифт:
Интервал:
Закладка:
(void)std::initializer_list<int>{
((void)f(xs), 0)...
};
});
Сердцем данной функции является выражение f(xs).xs — набор параметров, и нужно распаковать его, чтобы получить отдельные значения и передать их отдельным вызовам функции f. К сожалению, мы не можем просто написать конструкцию f(xs)... с помощью нотации ..., с которой уже знакомы.
Вместо этого можно создать список значений с помощью std::initializer_list, имеющего конструктор с переменным числом параметров. Выражение наподобие return std::initializer_list<int>{f(xs)...}; решает задачу, но имеет недостатки. Взглянем на реализацию функции for_each, которая тоже работает и при этом выглядит проще нашего варианта:
auto for_each ([](auto f, auto ...xs) {
return std::initializer_list<int>{f(xs)...};
});
Она более проста для понимания, но имеет следующие недостатки.
1. Создает список инициализаторов для возвращаемых значений на основе вызовов функции f. К этому моменту нас не волнуют возвращаемые значения.
2. Возвращает данный список, а нам нужна функция, работающая в стиле «запустил и забыл», которая не возвращает ничего.
3. Вполне возможно, что f — функция, которая не возвращает ничего, в таком случае код даже не будет скомпилирован.
Гораздо более сложная функция for_each решает все эти проблемы. Она делает следующее.
1. Не возвращает список инициализаторов, а приводит все выражение к типу void с помощью (void)std::initializer_list<int>{...}.
2. Внутри инициализирующего выражения преобразует выражение f(xs)... в выражение (f(xs),0). Это приводит к тому, что возвращаемое выражение отбрасывается, а значение 0 все еще помещается в список инициализаторов.
3. Конструкция f(xs) в выражении (f(xs),0). также преобразуется к типу void, поэтому возвращаемое значение, если таковое существует, нигде не обрабатывается.
Объединение этих особенностей, к сожалению, ведет к появлению уродливой конструкции, но она корректно работает и компилируется для множества объектов функций независимо от того, возвращают ли они какое-то значение.
Приятной особенностью описанного механизма является тот факт, что порядок вызовов функций будет сохраняться в строгой последовательности.
Выполнять преобразование конструкции (void)выражение в рамках старой нотации языка С не рекомендуется, поскольку в языке С++ имеются собственные операции преобразования. Вместо этого стоит использовать конструкцию reinterpret_cast<void>(выражение), но данный вариант еще больше снизит удобочитаемость кода.
Реализуем функцию transform_if с применением std::accumulate и лямбда-выражений
Большинство разработчиков, применяющих std::copy_if и std::transform, могли задаваться вопросом, почему не существует функции std::transform_if. Функция std::copy_if копирует элементы из исходного диапазона по месту назначения, но опускает элементы, не соответствующие определенной пользователем функции-предикату. Функция std::transform безусловно копирует все элементы из исходного диапазона по месту назначения, но при этом преобразует их в процессе. Это происходит с помощью функции, которая определена пользователем и может выполнять как нечто простое (например, умножение чисел), так и полные преобразования к другим типам.
Эти функции существуют достаточно давно, но функции std::transform_if все еще нет. Ее можно легко создать, реализовав функцию, которая итерирует по диапазонам данных и копирует все элементы, соответствующие предикату, выполняя в процессе их преобразование. Однако мы воспользуемся случаем и разберем решение данной задачи с точки зрения лямбда-выражений.
Как это делается
В этом примере мы создадим собственную функцию transform_if, которая работает, передавая алгоритму std::accumulate правильные объекты функций.
1. Как и всегда, включим некоторые заголовочные файлы:
#include <iostream>
#include <iterator>
#include <numeric>
2. Сначала реализуем функцию с именем map. Она принимает функцию преобразования входных данных и возвращает объект функции, который будет работать с функцией std::accumulate:
template <typename T>
auto map(T fn)
{
3. Мы будем возвращать объект функции, принимающий функцию reduce. Когда данный объект вызывается с этой функцией, он возвращает другой объект функции, который принимает аккумулятор и входной параметр. Он вызывает функцию reduce для этого аккумулятора и преобразованной входной переменной fn. Если это описание кажется вам слишком сложным — не волнуйтесь, далее мы соберем все вместе и посмотрим, как работают эти функции.
return [=] (auto reduce_fn) {
return [=] (auto accum, auto input) {
return reduce_fn(accum, fn(input));
};
};
}
4. Теперь реализуем функцию filter. Она работает точно так же, как и функция map, но не затрагивает входные данные, в то время как map преобразует их с помощью функции transform. Вместо этого принимаем функцию-предикат и опускаем те входные переменные, которые не соответствуют данному предикату, не выполняя для них функцию reduce.
template <typename T>
auto filter(T predicate)
{
5. Два лямбда-выражения имеют такие же сигнатуры функций, что и выражения в функции map. Единственное отличие заключается в следующем: входной параметр остается неизменным. Функция-предикат используется для определения того, будем ли мы вызывать функцию reduce_fn для входных данных или же получим доступ к аккумулятору, не внося никаких изменений.
return [=] (auto reduce_fn) {
return [=] (auto accum, auto input) {
if (predicate(input)) {
return reduce_fn(accum, input);
} else {
return accum;
}
};
};
}
6. Теперь воспользуемся этими вспомогательными функциями. Создадим экземпляры итераторов, которые позволяют считать целочисленные значения из стандартного потока ввода:
int main()
{
std::istream_iterator<int> it {std::cin};
std::istream_iterator<int> end_it;
7. Далее определим функцию-предикат even, которая возвращает значение true, если перед нами четное число. Функция преобразования twice умножает свой целочисленный параметр на 2:
auto even ([](int i) { return i % 2 == 0; });
auto twice ([](int i) { return i * 2; });
8. Функция std::accumulate принимает диапазон значений и аккумулирует их. Аккумулирование по умолчанию означает суммирование значений с помощью оператора +. Мы хотим предоставить собственную функцию аккумулирования. Таким образом, хранить сумму значений не нужно. Мы присвоим каждое значение из диапазона разыменованному итератору it, а затем вернем его после продвижения вперед.
auto copy_and_advance ([](auto it, auto input) {
*it = input; return ++it;
});
9. Наконец мы готовы собрать все воедино. Мы итерируем по стандартному потоку ввода и предоставляем вывод ostream_iterator, который
Прочитали книгу? Предлагаем вам поделится своим отзывом от прочитанного(прослушанного)! Ваш отзыв будет полезен читателям, которые еще только собираются познакомиться с произведением.
Уважаемые читатели, слушатели и просто посетители нашей библиотеки! Просим Вас придерживаться определенных правил при комментировании литературных произведений.
- 1. Просьба отказаться от дискриминационных высказываний. Мы защищаем право наших читателей свободно выражать свою точку зрения. Вместе с тем мы не терпим агрессии. На сайте запрещено оставлять комментарий, который содержит унизительные высказывания или призывы к насилию по отношению к отдельным лицам или группам людей на основании их расы, этнического происхождения, вероисповедания, недееспособности, пола, возраста, статуса ветерана, касты или сексуальной ориентации.
- 2. Просьба отказаться от оскорблений, угроз и запугиваний.
- 3. Просьба отказаться от нецензурной лексики.
- 4. Просьба вести себя максимально корректно как по отношению к авторам, так и по отношению к другим читателям и их комментариям.
Надеемся на Ваше понимание и благоразумие. С уважением, администратор knigkindom.ru.
Оставить комментарий
-
Р.Д.У.22 август 02:17
...мне тоже понравился этот русский вестерн. И озвучено неплохо. Советую....
Силантьев Вадим – Засада
-
Гость Любовь21 август 20:01
Прочитала залпом.... интересный сюжет, история захватывает, плакала вместе с героями. спасибо автору за интересное...
Вернуть жену. Без права на прощение? - Ира Орлова
-
Ма21 август 02:06
Роман хороший, но очень топорный и поэтому скучноватый, все как будто поверхностно, акцент на работе героев - киллер и главбух, а...
Гектор - Ольга Дашкова
