Function pointers, functors and lambda functions

Programming Workshop 2 (CSCI 1061U)

Faculty of Science, UOIT

http://vclab.science.uoit.ca

Function Pointers

Consider the following file

square.cpp

``````#include <iostream>
using std::cout;
using std::endl;

int square(int x)
{
return x*x;
}

int main()
{
int (*fnptr)(int);
fnptr = square;

cout << fnptr(2) << endl;

return 0;
}``````

When we compile and excute this file, we get the following

``````> g++ square.cpp -o square
> ./square
4``````

Here statement `int (*fnptr)(int)` declares a variable `fnptr` that can store a pointer to a function that takes one `int` argument and returns an `int`. In the above program `square` is such a function. Function `square` takes one `int` argument and returns an `int`. We can assign the address of this function to variable `fnptr` using the assignment `fnptr = square`. Note that the name of the function is also its address. So, we don't need to use the `&` to get the address of this function. We can then call the function `square` by using the variable `fnptr`.

Example 1

Consider the following function

``````void swap(float* a, float* b)
{
float tmp;
tmp = *a;
*a = *b;
*b = tmp;
}``````

We can declare a variable to store the pointer to function `swap` as follows

``void (*m)(float* a, float* b);``

We can then assign the address of function `swap` to variable `m` as follows

``m = swap;``

Example 2

Consider the following function

``````bool gt(int a, int b)
{
return a > b;
}``````

We can declare a variable to store the pointer to function `gt` as follows

``bool (*l)(int a, int b);``

We can then assign the address of function `gt` to variable `l` as follows

``l =gt;``

Example 3

Consider the following function

``````double sum(double a[], int i, int j, int s)
{
double sum=0.0;
for (int k=i; k<=j; k+=s) {
sum += a[k];
}
return sum;
}``````

We can declare a variable to store the pointer to function `sum` as follows

``double (*n)(double a[], int i, int j, int s)``

We can then assign the address of function `sum` to variable `n` as follows

``n = sum;``

Function pointers as arguments

Having variables that can store pointers to functions allow us to pass functions as arguments to other functions. These is a very powerful feature that enables us to write code where processing can change on the fly.

Consider the following code. Here `do_some_process` is a function that relies upon the function passed to it as its argument to do the actually processing. This means that we can change the behavior of `do_some_process` by passing a different function at runtime.

function-ptr-arguments.cpp

``````#include <iostream>
using std::cout;
using std::endl;

int square(int x)
{
return x*x;
}

int neg(int x)
{
return -x;
}

int do_some_process(int (*fn)(int), int x)
{
return fn(x);
}

int main()
{
int x = 5;

cout << do_some_process(square, x) << endl;
cout << do_some_process(neg, x) << endl;

return 0;
}``````

When we compile and run this code, we get the following

``````> g++ function-ptr-arguments.cpp -o prog
> ./prog
25
-5``````

Listener and callback functions

Function pointers are frequently used to set up listener or callback functions. These function are invoked when a certain event occurs. These provide an easy way for a user to inject their own functionality into a larger system. To reiterate this pointe, function pointers are often used to pass around processing instructions.

Below you see an example of a callback function for the `GLUT` library that allows a user to respond to mouse events.

``void glutMouseFunc(void (*func)(int button, int state, int x, int y));``

Virtual functions

It is also possible to avoid explicit function pointers by using virtual functions and polymorphism. Virtual function, however, are implemented behind the scene using function pointers.

C++ Functors

In addition to function pointers seen above, C++ also provides functors. Functors are objects that can be used as if these are functions. Functors are more powerful than good'ol function pointers, since functors can carry around state.

Consider the code below:

functors.cpp

``````#include <iostream>

using std::cout;
using std::endl;

class Square {
public:

int operator()(int x) {
return x*x;
}
};

int main()
{
Square a;

cout << a(3) << endl;

return 0;
}``````

`Square` is a class. However, we have overloaded its `()` operator, which allows us to use it as if it is a function. In the above code instance `a` of `Square` behaves as if it is a function---`a(3)`.

Using functors for callback

Functors are simply classes, and we can use templates to pass a functor as an argument.

``````#include <iostream>

using std::cout;
using std::endl;

class Square {
public:

int operator()(int x) {
return x*x;
}
};

class Neg {
public:

int operator()(int x) {
return -x;
}
};

template<typename T>
int do_some_process(T process, int x)
{
return process(x);
}

int main()
{
Square a;
Neg b;

cout << do_some_process(a, 2) << endl;
cout << do_some_process(b, 2) << endl;

return 0;
}``````

An advantage of using the functors way of defining function arguments `template<typename T> int do_some_process(T process, int x) { ... }` is that it also allows us to pass in functions as below

``````#include <iostream>

using std::cout;
using std::endl;

class Square {
public:

int operator()(int x) {
return x*x;
}
};

class Neg {
public:

int operator()(int x) {
return -x;
}
};

int timestwo(int x)
{
return 2*x;
}

template<typename T>
int do_some_process(T process, int x)
{
return process(x);
}

int main()
{
Square a;
Neg b;
int x = 9;

cout << do_some_process(a, x) << endl;
cout << do_some_process(b, x) << endl;
cout << do_some_process(timestwo, x) << endl;

return 0;
}``````

Lambda functions (C++11)

C++11 standard introduces lambda functions. These are noname functions that can be defined and called at the same time. They are used extensively in some other languages, such as Javascript.

Consider the code below

``````#include <iostream>
using std::cout;
using std::endl;

int main()
{
auto func = [](int x)->int{ return x*x; };

int x = 11;
cout << func(x) << endl;

return 0;
}``````

`[](int x)->int{ return x*x; }` defines the lambda function. Here `[]` is compiler specification, indicating the compiler that we are creating a lambda function. `()` is used to specify the argument list. In this case, this function takes one argument of type `int`, hence we use `(int x)`. `->int` indicates the return type. In this case the function returns an `int`. `{ return x*x; }` is the function body. Here the lambda function is created and its pointer is assigned to variable `func`. `auto` is a keyword that can be used to define a variable. In this case the type of the variable is infered from context.

Syntactic sugar

It is okay to miss `()` if the function takes no arguments.

It is okay to miss `->` if the compiler can discern the retunr type.

It is possible to call the function right away by using `()` at the end of the body as shown below

See the example below.

``````#include <iostream>
using std::cout;
using std::endl;

int main()
{
int val = 11;
cout << [](int x){ return x*x; }(val) << endl;

return 0;
}``````

Lambda functions as arguments

It is possible to pass lambda functions as arguments where function pointers are expected. Of course the signature of the lamda function should match the signature of the argument, representing the function pointer.

``````#include <iostream>
using std::cout;
using std::endl;

int do_some_process(int x, int (*process)(int))
{
return process(x);
}

int main()
{
cout << do_some_process(2, [](int x)->int{ return x*x;} ) << endl;
return 0;
}``````

Variable capture

Consider the following program

``````#include <iostream>
using std::cout;
using std::endl;

int main()
{
int val = 11;
cout << [&](){ return val*val; }() << endl;

return 0;
}``````

Notice that the lambda function `[&](){ return val*val; }` is referring to a variable `val`, which is not defined in this lambda function. The variable `val` is also not passed into this lambda function. So how did this variable got here. We use the variable capture indicated by `[&]` to capture the variable `val` defined in the caller context to be available within the lambda function.

Notation Effect
`[]` Capture nothing
`[&]` Capture any variable referenced in the lambda function by reference
`[=]` Capture any variable referenced in the lambda function by value (i.e., making a copy)
`[=,&foo]` Capture any variable referenced in the lambda function by value (i.e., making a copy); capture variable `foo` by reference.
[this] Capture the `this` pointer of the enclosing class. This means that all members of the enclosing class are available within the lambda function
[foo] Capture variable `foo` by making a copy; do not capture anything else

Use care since lambda function can modify the values of the variables that are captured by reference. Also take care when returning lambda function from a function, since captured variables might become invalid.

Lambda functions and STL

Below we see an example of lambda function being used with the Standard Template Library (STL)

``````#include <iostream>
#include <vector>
using std::cout;
using std::endl;

int main()
{
std::vector<int> v;
for (int i=0; i<10; ++i) v.push_back(i*2);

std::for_each(v.begin(), v.end(), [](int val){ cout << val << endl; });

return 0;
}``````