Functor, Lamdba and Predicate

Within this blog article I want to explain the C++ functor, lambda and predicate concepts by using them in short and easy to understand examples.

 

Functor

A functor is a class or struct which acts like a function. This is made possible by overloading the () operator. As this function is implemented by using a class, it has all the possibilities of a class. That means you can set parameters during class creation and it can hold state before and between function calls. So it is like a closure in functional languages. Furthermore a functor allows implementing another nice feature, mainly used in functional languages: higher order functions. The functor itself is a higher order function as it returns a function and it can be used in higher order functions which take one or more functions as arguments. Functors and higher order functions are commonly used in STL algorithms.

Let us start with a basic example. We have a vector with integer values and want to increase all values by a fixed or variable number. The STL offers the higher order function “for_each” which can be used to iterate over the vector and execute a function on each element. Therefore we implement a functor which is used to change an element. Within the first example we create a base functor which increases the given value by five.

struct IncreaseBy5
{
  void operator()(int& val)
  {
    val = val + 5;
  }
};

int _tmain(int argc, _TCHAR* argv[])
{  
  auto data = std::vector < int > {3,7,15};
  
  std::for_each(data.begin(), data.end(), IncreaseBy5());

	return 0;
}

 

The class IncreaseBy5 overloads the () operator and can therefore be used like a function. So you can use it within the “for_each” function of the STL.

In the next step we want to use the nice possibilities of functor classes and make our functor more universal. Therefore we allow defining the value which shall be added. This value can set during creation of the functor. The following source code shows the adapted example.

class IncreaseByX
{
public:
  IncreaseByX(int addend) : mAddend{ addend }
  {}

  void operator()(int& val)
  {
    val = val + mAddend;
  }

private:
  int mAddend;
};

int _tmain(int argc, _TCHAR* argv[])
{
  auto data = std::vector < int > {3, 7, 15};

  std::for_each(data.begin(), data.end(), IncreaseByX(10));

  return 0;
}

 

As we have heard before, this closure like concept allows us to access internal parameters during function execution. This will open a wide range of use cases. You may use the internal state of the class and the possibility of changeable class properties to define and modify the behavior of your functor.

Of course, you can also pass more parameters to the functor. The previous example with a variable value could also be implemented with a second function parameter. In such a case you must respect that the “for_each” STL function, as well as many other of STL algorithm higher order functions will accept unary functions only. Our new functor is a binary function as he has two parameters now. A unary function has one parameter only. But we can use the “bind” function to solve this issue. The bind adapter will get a function with several parameters and returns a unary function with one parameter. The following source code shows the according example.

struct IncreaseByX
{
  void operator()(int& val, int addend)
  {
    val = val + addend;
  }
};

int _tmain(int argc, _TCHAR* argv[])
{
  auto data = std::vector < int > {3, 7, 15};

  std::for_each(data.begin(), data.end(), std::bind(IncreaseByX(), std::placeholders::_1, 10));

  return 0;
}

 

STL Functors

As mentioned before, the STL already contains some basic functors. So we don’t have to reinvent the wheel and can use the existing functors. In our example application we have created a function which increases the given value. The STL already offers a nearly equivalent functor: “plus(p1,p2)” which creates the sum of the two given parameters.

With some little modification we can use the existing STL functor within our example. At first we cannot longer use the “for_each” loop because the STL function returns the result instead of use a pointer and change the value directly. Therefore we have to use the “transform” loop to modify the vector values. Furthermore, as the functor is a binary function and we need a unary one, we have to use the bind method once again.

int _tmain(int argc, _TCHAR* argv[])
{
  auto data = std::vector < int > {3, 7, 15};

  std::transform(data.begin(), data.end(), data.begin(), std::bind(std::plus<int>(), std::placeholders::_1, 10));

  return 0;
}

 

For better readability we can create the functor first and use it within the transform method.

int _tmain(int argc, _TCHAR* argv[])
{
  auto data = std::vector < int > {3, 7, 15};

  auto functor = std::bind(std::plus<int>(), std::placeholders::_1, 10);

  std::transform(data.begin(), data.end(), data.begin(), functor);

  return 0;
}

 

Use member function in higher order function

The STL higher order functions like “fore_each” or “transform” expect a functor or static member function as member. But by using “mem_fun_ref” or “mem_fun” it is possible to pass a normal member function as parameter. “mem_fun_ref” is used in case you have an object and “mem_fun” in case you have a pointer. The following example shows how to use a member function as parameter for the “for_each” method.

class Name
{
public:
  Name(std::string name) : mName(name)
  {}

  void Print()
  {
    std::cout << mName << std::endl;
  }

private:
  std::string mName;
};

int _tmain(int argc, _TCHAR* argv[])
{
  auto data = std::vector < Name >();
  data.push_back(Name("Foo"));
  data.push_back(Name("Bar"));

  std::for_each(data.begin(), data.end(), std::mem_fun_ref(&Name::Print));

  return 0;
}

 

Predicate

A predicate is a functor which returns a Boolean value. Therefore it can be used in higher order functions like “remove_if” which executes their functionality according to the return value of the given functor. Within the next example we want to remove all odd numbers from a given vector. So we create a predicte which checks whether a value is odd and use this predicate within the “remove_if” function. This function returns an iterator from start to end and removes all iterator elements matching the predicate. With this iterator we can use the erase function of the vector and delete the according elements.

class IssOddNumber
{
public:
  bool operator()(int& val)
  {
    return val % 2 ? true : false;
  }

private:
  int mAddend;
};

int _tmain(int argc, _TCHAR* argv[])
{
  auto data = std::vector < int > {3, 7, 15, 22};

  data.erase(std::remove_if(data.begin(), data.end(), IssOddNumber()), data.end());

  return 0;
}

 

Lambda functions

Last but not least I want to show how to use lambda functions to implement the functionality of the example above which increases the vector values. A lambda function is an anonymous function. You can create the function code directly where it is needed without the need of a function declaration. The following example shows how to increase the vector values by 5. Of course you can also pass additional parameters to the lambda function and add a variable value instead of the “5”.

int _tmain(int argc, _TCHAR* argv[])
{
  auto data = std::vector < int > {3, 7, 15};

  std::for_each(data.begin(), data.end(), 
    [](int& val)
    {
      val = val + 5;
    });

  return 0;
}

 

To increase the readability of the code you can create the lambda function first and use it within the for_each loop.

int _tmain(int argc, _TCHAR* argv[])
{
  auto data = std::vector < int > {3, 7, 15};

  auto IncreaseBy5 = [](int& val)
  {
    val = val + 5;
  };

  std::for_each(data.begin(), data.end(), IncreaseBy5);

  return 0;
}
Werbung
Dieser Beitrag wurde unter C++ veröffentlicht. Setze ein Lesezeichen auf den Permalink.

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s