Function Objects

Often we want to write numerical algorithms to be general, so we’d like to be able to pass a function as an argument for the algorithm to operate on. std::function provides the mechanism for us to pass a function to a function. It has the form:

std::function<return_type(arg1_type, arg2_type)> f

where we specify both the function’s return type (as return_type) and the type of any arguments that the function takes (as arg1_type and arg2_type above, for a function with 2 arguments).

First example

Here we write a function doit() that takes a value and a function that operates on that value and it just prints out some information before and after calling the passed-in function.

Listing 111 simple_function.cpp
#include <iostream>
#include <functional>

void doit(double x, std::function<double(double)> f) {
    std::cout << "calling our function" << std::endl;
    double r = f(x);
    std::cout << "result = " << r << std::endl;
}

double f(double x) {
    return x*x*x;
}

int main() {

    double x{2};
    doit(x, f);

}

Templated example

Here’s a more complicated example that performs an operator on an input vector, where the operation is determined by a function passed into our reduce() function.

To make it more general, we template everything, so our reduce() can operate on a vector of any type. (Note: there are routines in the C++ SL that already do this, but here we can see a simple implementation.)

We will apply a function of the form:

template <typename T>
T op(T x, T y);

to the elements of the vector. We assume that the order of x and y doesn’t matter.

Listing 112 functional_reduce.cpp
#include <iostream>
#include <functional>
#include <vector>
#include <limits>
#include <cmath>

template <typename T>
T add(T x, T y) {
    return x + y;
}

template <typename T>
T max(T x, T y) {
    return std::max(x, y);
}

template <typename T>
T reduce(std::vector<T>&  v, T init, std::function<T(T, T)> f) {
    T val{init};
    for (auto e : v) {
        val = f(val, e);
    }
    return val;
}

int main() {
    std::vector<int> a{0, 1, 2, 3, 4, 5};

    std::cout << "sum a: " <<
        reduce<int>(a, 0, add<int>) << std::endl;

    std::cout << "max a: " <<
        reduce<int>(a, std::numeric_limits<int>::min(), max<int>) << std::endl;

    std::vector<double> b{-1.32, M_PI, 20.0, 1.e3, std::sqrt(2.0)};

    std::cout << "sum b: " <<
        reduce<double>(b, 0.0, add<double>) << std::endl;

    std::cout << "max b: " <<
        reduce<double>(b, std::numeric_limits<double>::lowest(), max<double>) << std::endl;

}

Note

In some cases, we can remove the specialization in the reduce() call if the compiler can infer it from the arguments. For instance, we can do:

std::cout << "sum b: " <<
   reduce(b, 0.0, add<double>) << std::endl;

But note that this does not work if we write 0 instead of 0.0.

Tip

We can also use Lambda Functions with the same interface, e.g.,

std::cout << "min b: " <<
  reduce<double>(b, std::numeric_limits<double>::max(),
                 [] (double x, double y) {return std::min(x, y);}) << std::endl;