Lei Mao bio photo

Lei Mao

Machine Learning, Artificial Intelligence, Computer Science.

Twitter Facebook LinkedIn GitHub   G. Scholar E-Mail RSS

Introduction

Function object is any object for which the function call operator is defined, i.e., operator() is overloaded. It is also called functor.


Suppose we want to implement a function which requires a lot of parameters and the parameters would be set during runtime, using functors would be convenient because the parameters could be saved in the functor instance when it is created. This also means we can create a family of the functions using different parameters easily. In addition, by using C++ runtime polymorphism, i.e., function overriding, we could defer the functor type determination to runtime.


In this blog post, I would like to show some of the functor implementations.

Examples

The following example presents a couple of functors which use default and non-default parameters, how to pass functor to functions using pointer, and how to integrate functors with template.

/* 
 * functors.cpp
 */
#include <iostream>
#include <vector>
#include <algorithm>

class CircleArea
{
public:
    CircleArea()
    {
    }
    CircleArea(double pi) : mPi{pi}
    {
    }
    double operator() (double r) const
    {
        return this->mPi * r * r;
    }
private:
    double mPi{3.1415926};
};

template <class T>
class UnaryFunction
{
public:
    virtual T operator() (T x) const = 0;
};

template <class T>
class Increase : public UnaryFunction<T>
{
public:
    Increase(T val) : mVal{val}
    {
    }
    T operator() (T x) const override
    {
        return x + this->mVal;
    }
private:
    T mVal;
};

template <class T>
class Decrease : public UnaryFunction<T>
{
public:
    Decrease(T val) : mVal{val}
    {
    }
    T operator() (T x) const override
    {
        return x - this->mVal;
    }
private:
    T mVal;
};

template <class T>
class BinaryFunction
{
public:
    virtual T operator() (T x, T y) const = 0;
};

template <class T>
class Add : public BinaryFunction<T>
{
public:
    T operator() (T x, T y) const override
    {
        return x + y;
    }
};

template <class T>
class Minus : public BinaryFunction<T>
{
public:
    T operator() (T x, T y) const override
    {
        return x - y;
    }
};

template <class T>
T runBinaryFunction(T x, T y, BinaryFunction<T>* func)
{
    return (*func)(x, y);
}

// Functors for template
template <class T, class BinaryFunc>
class Foo
{
public:
    T runBinaryFunction(T x, T y)
    {
        return this->func(x, y);
    }
private:
    BinaryFunc func;
};

int main()
{
    // Use default parameters for the functor
    CircleArea computeCircleArea;
    // Specify custom parameters for the functor
    CircleArea computeCustomCircleArea{3.14};
    double r{1.0};
    std::cout << computeCircleArea(r) << std::endl;
    std::cout << computeCustomCircleArea(r) << std::endl;

    Add<double> addDouble;
    Minus<double> minusDouble;
    double x{1.1};
    double y{2.2};
    // Use functors
    std::cout << addDouble(x, y) << std::endl;
    std::cout << minusDouble(x, y) << std::endl;
    // Use functor pointers
    std::cout << runBinaryFunction(x, y, &addDouble) << std::endl;
    std::cout << runBinaryFunction(x, y, &minusDouble) << std::endl;

    std::vector<int> vec{1,2,3,4,5};
    std::vector<int> vecIncreaseOne;
    std::vector<int> vecIncreaseTen;
    Increase<int> increaseOne{1};
    Increase<int> increaseTen{10};
    std::transform(vec.begin(), vec.end(), std::back_inserter(vecIncreaseOne), increaseOne);
    std::transform(vec.begin(), vec.end(), std::back_inserter(vecIncreaseTen), increaseTen);
    for (int val : vecIncreaseOne)
    {
        std::cout << val << " ";
    }
    std::cout << std::endl;
    for (int val : vecIncreaseTen)
    {
        std::cout << val << " ";
    }
    std::cout << std::endl;

    Foo<int, Add<double>> fooAdd;
    std::cout << fooAdd.runBinaryFunction(1, 2) << std::endl;
    Foo<int, Minus<double>> fooMinus;
    std::cout << fooMinus.runBinaryFunction(1, 2) << std::endl;
}

To compile the program, please run the following command in the terminal.

$ g++ functors.cpp -o functors --std=c++11

Conclusions

Functors are less confusing and much easier to use compared to function pointers.