A course about pointers would not be complete without pointers to
functions. I will first show you the legacy syntax, which is still used
in some situations, and then the modern C++ way of using functions as
function arguments. As you can see,
I defined a simple function which takes in two integers and
then returns the result of their addition.
Every program has a special place in memory where functions
are stored as a sequence of instructions.
The function's name is actually a pointer to the place in memory which holds
the instructions for executing that specific function.
I can prove this by printing out the function's identifier without
actually using parentheses to call the function.
Since this is a pointer to a function,
the output may vary on different systems, so I will cast it to a void
pointer just to be sure that we will get a memory address.
Okay, let's compile this.
As you can see, the output of this expression is a memory
address, the address of this function in memory.
So if functions can have pointers, what do these pointers even look like?
The syntax for this is inherited from C, and yes, it is a little bit clunky.
A function pointer needs to be aware of the function signature.
So, for example,
if I want to point to a function that has two integers
parameters and returns an integer,
I need to explicitly declare the data type of the returned value and
the data type of every parameter. On the left side of the parameter
list, which is the usual spot for the function's name, we need to
define the name of the pointer.
Of course, since this is a pointer, asterisk is also required. And that's it.
This is how a pointer to a function looks like.
Now let's point this pointer to the sum function.
If you really have a need for a pointer like this one,
a better way would be to use the auto keyword.
This will force the compiler to deduce the type of the pointer itself.
Even though this statement is valid,
I will add the asterisk next to the pointer name, because I like to
be explicit and make sure that I don't forget that this is a
pointer. And now we can use this pointer to access the
functionality provided by the sum function.
I can execute this function by dereferencing the pointer and
then providing the arguments in the usual way.
Since pointers to functions are really special,
we can avoid the dereference operator and use the pointer
like this, just like any regular function.
Both ways are syntactically correct, but the second one is
available because it's more convenient.
I will compile this to prove that it's working, and it does.
So now that we know how to point to functions,
let's refactor the code from the calculator program. Instead of having a
special function for each operation, let's only have one function which
will do all kinds of different calculations.
I will name this function calculate. The function definition will
resemble the definition of two previous functions.
You might have noticed that the only difference between those previous
two functions was the arithmetic operator inside of the for loop. If we
need to add numbers we use the plus operator, and if we need to multiply
them we can use the multiplication operator, but what if instead of
directly using these two operators, we can pass these two numbers to a
special function.
Let's name this function fun.
This function will either multiply or add these two numbers, but in any case
it will return an integer and it will take in two integers.
We can define this function as a pointer to a function with that kind
of signature, so this function will actually be a parameter which we
need to pass to the calculate function.
Okay, now we actually have to pass the function to this pointer.
So instead of using the sum function here, we can use the calculate function.
The third argument needs to be a pointer to a function. Since
this case is the one where we do addition,
we could define a function that will do that and then pass
the name of that function as an argument.
But this makes no sense, because we will only use that simple
function here and nowhere else, so why would we name a function
that will only be used in one place?
Fortunately, C++ allows us to create anonymous functions, the so‑called
lambdas. Lambda is a function definition without a name,
usually for the purposes like this one, where you need a
function, but you don't want to give it a name because you
only need it in one place.
But to turn this definition into a lambda,
you need to add a pair of these square brackets before
the argument list. And that's it.
We defined an inline function which will add these two numbers, and we
passed it to the calculate function as a pointer.
Let's do the same for multiplication, but don't forget to change this operator.
Okay, let's compile the program.
I will pick some random numbers and choose multiplication.
I think that this result is correct.
Great.
Lambdas can be assigned to legacy function pointers only if they don't
contain any captures. Captures are defined inside of these square
brackets as a list of variables from the surrounding scope that you want
to make available inside of the lambda,
basically making your function aware of the outside context.
We will not cover this, because this course is not about lambdas,
but if you use these captures, then you cannot point to this
function with a simple function pointer.
Instead,
you should use a special standard function class
available from the C++ functional library.
So let's first include that library.
I will remove this pointer and define the std::function parameter.
This is a templated class, so you need to pass in the function
signature of the function that you want to receive.
I will also turn this parameter into a constant reference, because I want
to pass it by reference, and I made it constant because anonymous
functions cannot be modified after their definition.
Okay,
let's compile this to check if it works. And it seems
that it does, just like before.
Since this is C++, you should always prefer using
std::function class instead of a raw pointer.
It does have a slight overhead, but it's unnoticeable in most cases,
so unless you have a good reason to use a raw pointer,
always use the std::function class, especially if you are working with lambdas.