2.
3. Вполне возможно, что f
— функция, которая не возвращает ничего, в таком случае код даже не будет скомпилирован.
Гораздо более сложная функция for_each
решает все эти проблемы. Она делает следующее.
1. void
с помощью (void)std::initializer_list
.
2. Внутри инициализирующего выражения преобразует выражение f(xs)...
в выражение (f(xs),0)
. Это приводит к тому, что возвращаемое выражение 0
все еще помещается в список инициализаторов.
3. Конструкция f(xs)
в выражении (f(xs),0)
. также преобразуется к типу void
, поэтому возвращаемое значение, если таковое существует, нигде не обрабатывается.
Объединение этих особенностей, к сожалению, ведет к появлению уродливой конструкции, но она корректно работает и компилируется для множества объектов функций независимо от того, возвращают ли они какое-то значение.
Приятной особенностью описанного механизма является тот факт, что порядок вызовов функций будет сохраняться в
(void)выражение
в рамках старой нотации языка С не рекомендуется, поскольку в языке С++ имеются собственные операции преобразования. Вместо этого стоит использовать конструкцию reinterpret_cast
, но данный вариант еще больше снизит удобочитаемость кода.
Реализуем функцию 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
#include
#include
2. Сначала реализуем функцию с именем map
. Она принимает функцию преобразования входных данных и возвращает объект функции, который будет работать с функцией std::accumulate
:
template
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
auto filter(T predicate)
{
5. Два лямбда-выражения имеют такие же сигнатуры функций, что и выражения в функции map
. Единственное отличие заключается в следующем: входной параметр остается неизменным. Функция-предикат используется для определения того, будем ли мы вызывать функцию reduce_fn
для входных данных или же получим доступ к аккумулятору, не внося никаких изменений.
return [=] (auto reduce_fn) {
return [=] (auto accum, auto input) {