Functors in C++ - Part II
06 Jul 2017This post is in continuation to Functors in C++ - Part I.
In last blog we talked about basics of functors. In this post let’s dive deep into functors and talk about some application of functors.
Functors are commonly used in STL algorithms. The power of functors can clearly be realised when we use them with STL.
STL Functors
There is one more good thing to this whole story. We don’t have to write all functors by ourselves. STL provides some built-in functors for basic operations like addition, divison, some bitwise operators etc. Some of the STL Functors are^:
- Binary Arithmetic Functors : plus (for addition, equivalent to arg1 + arg2), minus (for subtraction, same as arg1 - arg2 ), divides (division) etc. ^^
- Binary Logical Functors : logical_and, logical_or
- Unary Functors : negate, logical_not.
Binary functors are functors those take two parameters while unary take only one parameter.
^ Complete list and their descriptions can be found here.
^^ arg1 and arg2 are two input parameters to functors. Example: std::plus<int>()(arg1 + arg2)
Let’s take a example here. Assume we need to multiply each element of vector by -1. This can be done very easily as:
// Compile this code with -std=c++11 flag.
// like: g++ -std=c++11 main.cpp
#include<vector>
#include<algorithm>
#include<functional>
#include<iostream>
int main(){
// Initializing vector with 1,2 and 3 as it's elements
std::vector<int> vec = {1,2,3};
// Transforming each element of vector by multiplying
// it with -1. First argument to std::Transform is
// start iterator. Second argument is end iterator
// Third argument says where we need to store the
// result. Here we are storing result in vec itself.
// And the last argument says which functor do we
// need to call for each element of vector from
// start iterator to end iterator
std::transform(vec.begin(),vec.end(),
vec.begin(),std::negate<int>());
// print the transformed vector
for(int i=0;i<vec.size();i++){
std::cout << vec[i] << ' ';
}
// output would be
// -1 -2 -3
}
It’s so easy and much more readble than doing same thing without using STL. Isn’t it!?
But we do have one problem here if we use binary functor with std::transform instead of unary functor. Binary functor takes two input arguments while std::transform expects functor with just one argument. In this scenario std::bind
can be useful.
Bind
Let’s take a example where we need to add 5 to each element of std::vector, vec1 and store the result in another vector named vec2. How can this be done using std::transform and std::plus?
Here’s the solution:
// Compile this code with -std=c++11.
// g++ -std=c++11 main.cpp
#include<vector>
#include<algorithm>
#include<functional>
#include<iostream>
#define ELEM_TO_ADD 5
int main(){
// Initializing vector with 1,2 and 3 as three elements
std::vector<int> vec1 = {1,2,3};
std::vector<int> vec2;
std::transform(vec1.begin(),vec1.end(),
std::back_inserter(vec2),
std::bind(std::plus<int>(),
std::placeholders::_1,
ELEM_TO_ADD)
);
// print the transformed vector
for(int i=0;i<vec2.size();i++){
std::cout << vec2[i] << ' ';
}
// output would be
// 6 7 8
}
Let’s try to understand logic of above piece of code. std::transform
as discussed previously invoke the functor (specified by last argument) for each element iterating from vec1.begin() to vec1.end(). Before moving forward let’s first understand what std::bind
does.
std::bind
is partial function application. What this means is suppose you have a function func1
which takes two args as func1(arg1,arg2)
.
Now, you want to define a new function func2
as:
func2(arg1){
func1(arg1,5)
}
func2
has been defined by fixing argument arg2, so func2
is said as partial application of func1
. std::bind
does same. In terms of C++ STL func2
can be defined as:
auto func2 = std::bind(func1,std::placeholders::_1,5)
What we are saying by above line is: Create func2
by binding func1, first argument of func1 (specified as, std::placeholders::_1) and 5.^
^std::placeholders::_1
means first argument, std::placeholders::_2
means second parameter and so on.
You can do more crazy things with std::bind
like changing order of arguments of a function.
Back to example
Let’s move back to our example. std::bind(std::plus<int>(),std::placeholders::_1, ELEM_TO_ADD)
in our example binds std::plus functor, first argument passed to bind (which in our case would be elements of vector, passed one by one) and constant ELEM_TO_ADD. In short this line transforms binary functor std::plus
into unary functor. Another argument of functor is fixed value ELEM_TO_ADD.
Finally, we are storing the result of functor in vector, vec2, by using stl function std::back_inserter. This function just uses push_back function of vector to insert result at back of specfied vector.
So, now you might be feeling that functors are indeed powerful. In next blog we will talk about lambdas, which are quite concise way of writing anonymous functions.
Till then Sayonara.
Exercise
Can you think of solution to re-order arguments of a functions and create a new function? Basically, we want to define func3 as
func3(arg1,arg2,arg3){
func4(arg2,arg3,arg1);
}
If yes, let me know in comments how would you achieve this using std::bind
Thanks