We are going to build our own transform_if function which works by supplying std::accumulate with the right function objects:
- We need to include some headers, as always:
#include <iostream>
#include <iterator>
#include <numeric>
- First, we will implement a function called map. It accepts an input-transforming function as parameter and returns a function object, which works well together with std::accumulate:
template <typename T>
auto map(T fn)
{
- What we return is a function object that accepts a reduce function. When this object is called with such a reduce function, it returns another function object, which accepts an accumulator and an input parameter. It calls the reduce function on this accumulator and the fn transformed input variable. Don't worry if this looks complicated, we'll put it together later and see how it really works:
return [=] (auto reduce_fn) {
return [=] (auto accum, auto input) {
return reduce_fn(accum, fn(input));
};
};
}
- Now we implement a function called filter. It works exactly the same way as the map function, but it leaves the input untouched, while the map function transforms it using a transform function. Instead, we accept a predicate function and skip input variables without reducing them in case they are not accepted by the predicate function:
template <typename T>
auto filter(T predicate)
{
- The two lambda expressions have exactly the same function signature as the expressions in the map function. The only difference is that the input parameter is left untouched. The predicate function is used to distinguish if we call the reduce_fn function on the input or if we just reach the accumulator forward without any change:
return [=] (auto reduce_fn) {
return [=] (auto accum, auto input) {
if (predicate(input)) {
return reduce_fn(accum, input);
} else {
return accum;
}
};
};
}
- Now let's finally use those helpers. We instantiate iterators that let us read integer values from the standard input:
int main()
{
std::istream_iterator<int> it {std::cin};
std::istream_iterator<int> end_it;
- Then we define a predicate function, even, which just returns true if we have an even number. The transformation function twice multiplies its integer parameter with the factor 2:
auto even ([](int i) { return i % 2 == 0; });
auto twice ([](int i) { return i * 2; });
- The std::accumulate function takes a range of values and accumulates them. Accumulating means summing the values up with the + operator in the default case. We want to provide our own accumulation function. This way, we do not maintain a sum of the values. What we do is we assign each value of the range to the dereferenced iterator, it, and then return this iterator after advancing it further:
auto copy_and_advance ([](auto it, auto input) {
*it = input;
return ++it;
});
- Now we finally put together the pieces. We iterate over the standard input and provide an output, ostream_iterator, which prints to the terminal. The copy_and_advance function object works on that output iterator by assigning the user input integers to it. Assigning to the output iterator effectively prints the assigned items. But we only want the even numbers from the user input, and we want to multiply them. To achieve this, we wrap the copy_and_advance function into an even filter and then into a twice mapper:
std::accumulate(it, end_it,
std::ostream_iterator<int>{std::cout, ", "},
filter(even)(
map(twice)(
copy_and_advance
)
));
std::cout << 'n';
}
- Compiling and running the program leads to the following output. The values 1, 3, and 5 are dropped because they are not even, and the values 2, 4, and 6 are printed after they have been doubled:
$ echo "1 2 3 4 5 6" | ./transform_if
4, 8, 12,