Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Understanding Function Pointers
Store and invoke functions through pointer variables.
Function pointers
In the Introduction to pointers lesson, you learned that a pointer is a variable that holds the address of another variable. Function pointers are similar, except that instead of pointing to variables, they point to functions!
Consider the following function:
int calculate()
{
return 42;
}
Identifier calculate() is the function's name. But what type is the function? Functions have their own function type -- in this case, a function type that returns an integer and takes no parameters. Much like variables, functions live at an assigned address in memory (making them lvalues).
When a function is called (via operator()), execution jumps to the address of the function being called:
int calculate()
{
return 42;
}
int main()
{
calculate();
return 0;
}
At some point in your programming career (if you haven't already), you'll probably make a simple mistake:
#include <iostream>
int calculate()
{
return 42;
}
int main()
{
std::cout << calculate << '\n';
return 0;
}
Instead of calling function calculate() and printing the return value, we've unintentionally sent function calculate directly to std::cout. What happens in this case?
When a function is referred to by name (without parentheses), C++ converts the function into a function pointer (holding the address of the function). Then operator<< tries to print the function pointer, which it fails at because operator<< does not know how to print function pointers. The standard says that in this case, calculate should be converted to a bool (which operator<< does know how to print). And since the function pointer for calculate is a non-null pointer, it should always evaluate to Boolean true. Thus, this should print:
1
Some compilers (e.g., Visual Studio) have a compiler extension that prints the address of the function instead:
0x00401234
If your platform doesn't print the function's address and you want it to, you may be able to force it to do so by converting the function to a void pointer and printing that:
int calculate() { return 42; }
int main() { std::cout << reinterpret_cast<void*>(calculate) << '\n';
return 0;
}
This is implementation-defined behavior, so it may not work on all platforms.
Just like it is possible to declare a non-constant pointer to a normal variable, it's also possible to declare a non-constant pointer to a function. In the rest of this lesson, we'll examine these function pointers and their uses. Function pointers are a fairly advanced topic, and the rest of this lesson can be safely skipped or skimmed by those only looking for C++ basics.
## Pointers to functions
The syntax for creating a non-const function pointer is one of the ugliest things you will ever see in C++:
```cpp
int (*operation)();
In the above snippet, operation is a pointer to a function that has no parameters and returns an integer. operation can point to any function that matches this type.
The parentheses around *operation are necessary for precedence reasons, as int* operation() would be interpreted as a forward declaration for a function named operation that takes no parameters and returns a pointer to an integer.
To make a const function pointer, the const goes after the asterisk:
int (*const operation)();
If you put the const before the int, then that would indicate the function being pointed to would return a const int.
Assigning a function to a function pointer
Function pointers can be initialized with a function (and non-const function pointers can be assigned a function). Like with pointers to variables, we can also use &calculate to get a function pointer to calculate.
int calculate()
{
return 42;
}
int process()
{
return 84;
}
int main()
{
int (*operation)(){ &calculate };
operation = &process;
return 0;
}
One common mistake is to do this:
operation = process();
This tries to assign the return value from a call to function process() (which has type int) to operation (which is expecting a value of type int(*)()), which isn't what we want. We want operation to be assigned the address of function process, not the return value from function process(). So no parentheses are needed.
Note that the type (parameters and return type) of the function pointer must match the type of the function. Here are some examples of this:
int calculate();
double compute();
int transform(int x);
int (*op1)(){ &calculate };
int (*op2)(){ &compute };
double (*op4)(){ &compute };
op1 = &transform;
int (*op3)(int){ &transform };
Unlike fundamental types, C++ will implicitly convert a function into a function pointer if needed (so you don't need to use the address-of operator (&) to get the function's address). However, function pointers will not convert to void pointers, or vice-versa (though some compilers like Visual Studio may allow this anyway).
int calculate();
int (*op5)(){ calculate };
void* vPtr{ calculate };
Function pointers can also be initialized or assigned the value nullptr:
int (*operation)(){ nullptr };
Calling a function using a function pointer
The other primary thing you can do with a function pointer is use it to actually call the function. There are two ways to do this. The first is via explicit dereference:
int calculate(int x)
{
return x * 2;
}
int main()
{
int (*operation)(int){ &calculate };
(*operation)(15);
return 0;
}
The second way is via implicit dereference:
int calculate(int x)
{
return x * 2;
}
int main()
{
int (*operation)(int){ &calculate };
operation(15);
return 0;
}
As you can see, the implicit dereference method looks just like a normal function call -- which is what you'd expect, since normal function names are pointers to functions anyway! However, some older compilers do not support the implicit dereference method, but all modern compilers should.
Also note that because function pointers can be set to nullptr, it's a good idea to assert or conditionally test whether your function pointer is a null pointer before calling it. Just like with normal pointers, dereferencing a null function pointer leads to undefined behavior.
int calculate(int x)
{
return x * 2;
}
int main()
{
int (*operation)(int){ &calculate };
if (operation)
operation(15);
return 0;
}
Default arguments don't work for functions called through function pointers Advanced
When the compiler encounters a normal function call to a function with one or more default arguments, it rewrites the function call to include the default arguments. This process happens at compile-time, and thus can only be applied to functions that can be resolved at compile time.
However, when a function is called through a function pointer, it is resolved at runtime. In this case, there is no rewriting of the function call to include default arguments.
Because the resolution happens at runtime, default arguments are not resolved when a function is called through a function pointer.
This means that we can use a function pointer to disambiguate a function call that would otherwise be ambiguous due to default arguments. In the following example, we show two ways to do this:
void display(int x) { std::cout << "display(int)\n"; }
void display(int x, int y = 25) { std::cout << "display(int, int)\n"; }
int main() { using displayPtr = void(*)(int); displayPtr fn{ display }; fn(10);
static_cast<void(*)(int)>(display)(10);
return 0;
}
## Passing functions as arguments to other functions
One of the most useful things to do with function pointers is pass a function as an argument to another function. Functions used as arguments to another function are sometimes called **callback functions**.
Consider a case where you are writing a function to perform a task (such as sorting an array), but you want the user to be able to define how a particular part of that task will be performed (such as whether the array is sorted in ascending or descending order). Let's take a closer look at this problem as applied specifically to sorting, as an example that can be generalized to other similar problems.
Many comparison-based sorting algorithms work on a similar concept: the sorting algorithm iterates through a list of numbers, does comparisons on pairs of numbers, and reorders the numbers based on the results of those comparisons. Consequently, by varying the comparison, we can change the way the algorithm sorts without affecting the rest of the sorting code.
Here is our selection sort routine from a previous lesson:
```cpp
#include <utility>
void selectionSort(int* collection, int size)
{
if (!collection)
return;
for (int current{ 0 }; current < (size - 1); ++current)
{
int minIndex{ current };
for (int scan{ current + 1 }; scan < size; ++scan)
{
if (collection[scan] < collection[minIndex])
{
minIndex = scan;
}
}
std::swap(collection[current], collection[minIndex]);
}
}
Let's replace that comparison with a function to do the comparison. Because our comparison function is going to compare two integers and return a boolean value to indicate whether the elements should be swapped, it will look something like this:
bool ascending(int x, int y)
{
return x > y;
}
And here's our selection sort routine using the ascending() function to do the comparison:
#include <utility>
void selectionSort(int* collection, int size)
{
if (!collection)
return;
for (int current{ 0 }; current < (size - 1); ++current)
{
int minIndex{ current };
for (int scan{ current + 1 }; scan < size; ++scan)
{
if (ascending(collection[minIndex], collection[scan]))
{
minIndex = scan;
}
}
std::swap(collection[current], collection[minIndex]);
}
}
Now, in order to let the caller decide how the sorting will be done, instead of using our own hard-coded comparison function, we'll allow the caller to provide their own sorting function! This is done via a function pointer.
Because the caller's comparison function is going to compare two integers and return a boolean value, a pointer to such a function would look something like this:
bool (*compareFunc)(int, int);
So, we'll allow the caller to pass our sort routine a pointer to their desired comparison function as the third parameter, and then we'll use the caller's function to do the comparison.
Here's a full example of a selection sort that uses a function pointer parameter to do a user-defined comparison, along with an example of how to call it:
#include <utility>
#include <iostream>
void selectionSort(int* collection, int size, bool (*compareFunc)(int, int))
{
if (!collection || !compareFunc)
return;
for (int current{ 0 }; current < (size - 1); ++current)
{
int bestIndex{ current };
for (int scan{ current + 1 }; scan < size; ++scan)
{
if (compareFunc(collection[bestIndex], collection[scan]))
{
bestIndex = scan;
}
}
std::swap(collection[current], collection[bestIndex]);
}
}
bool ascending(int x, int y)
{
return x > y;
}
bool descending(int x, int y)
{
return x < y;
}
void printCollection(int* collection, int size)
{
if (!collection)
return;
for (int index{ 0 }; index < size; ++index)
{
std::cout << collection[index] << ' ';
}
std::cout << '\n';
}
int main()
{
int data[9]{ 8, 2, 6, 1, 9, 4, 7, 3, 5 };
selectionSort(data, 9, descending);
printCollection(data, 9);
selectionSort(data, 9, ascending);
printCollection(data, 9);
return 0;
}
This program produces the result:
9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9
Is that cool or what? We've given the caller the ability to control how our selection sort does its job.
The caller can even define their own "strange" comparison functions:
bool evensFirst(int x, int y)
{
if ((x % 2 == 0) && !(y % 2 == 0))
return false;
if (!(x % 2 == 0) && (y % 2 == 0))
return true;
return ascending(x, y);
}
int main()
{
int data[9]{ 8, 2, 6, 1, 9, 4, 7, 3, 5 };
selectionSort(data, 9, evensFirst);
printCollection(data, 9);
return 0;
}
The above snippet produces the following result:
2 4 6 8 1 3 5 7 9
As you can see, using a function pointer in this context provides a nice way to allow a caller to "hook" their own functionality into something you've previously written and tested, which helps facilitate code reuse! Previously, if you wanted to sort one array in descending order and another in ascending order, you'd need multiple versions of the sort routine. Now you can have one version that can sort any way the caller desires!
Note: If a function parameter is of a function type, it will be converted to a pointer to the function type. This means:
void selectionSort(int* collection, int size, bool (*compareFunc)(int, int))
can be equivalently written as:
void selectionSort(int* collection, int size, bool compareFunc(int, int))
This only works for function parameters, and so is of somewhat limited use. On a non-function parameter, the latter is interpreted as a forward declaration:
bool (*ptr)(int, int);
bool fcn(int, int);
Providing default functions
If you're going to allow the caller to pass in a function as a parameter, it can often be useful to provide some standard functions for the caller to use for their convenience. For example, in the selection sort example above, providing the ascending() and descending() function along with the selectionSort() function would make the caller's life easier, as they wouldn't have to rewrite ascending() or descending() every time they want to use them.
You can even set one of these as a default parameter:
void selectionSort(int* collection, int size, bool (*compareFunc)(int, int) = ascending);
In this case, as long as the user calls selectionSort normally (not through a function pointer), the compareFunc parameter will default to ascending. You will need to make sure that the ascending function is declared prior to this point, otherwise the compiler will complain it doesn't know what ascending is.
Making function pointers prettier with type aliases
Let's face it -- the syntax for pointers to functions is ugly. However, type aliases can be used to make pointers to functions look more like regular variables:
using CompareFunction = bool(*)(int, int);
This defines a type alias called "CompareFunction" that is a pointer to a function that takes two ints and returns a bool.
Now instead of doing this:
bool validate(int x, int y, bool (*operation)(int, int));
You can do this:
bool validate(int x, int y, CompareFunction operation);
Using std::function
An alternate method of defining and storing function pointers is to use std::function, which is part of the standard library
#include <functional>
bool validate(int x, int y, std::function<bool(int, int)> operation);
As you see, both the return type and parameters go inside angled brackets, with the parameters inside parentheses. If there are no parameters, the parentheses can be left empty.
Updating our earlier example with std::function:
#include <functional>
#include <iostream>
int calculate()
{
return 42;
}
int process()
{
return 84;
}
int main()
{
std::function<int()> operation{ &calculate };
operation = &process;
std::cout << operation() << '\n';
std::function op2{ &calculate };
return 0;
}
Type aliasing std::function can be helpful for readability:
using CompareFunctionRaw = bool(*)(int, int);
using CompareFunction = std::function<bool(int, int)>;
Also note that std::function only allows calling the function via implicit dereference (e.g., operation()), not explicit dereference (e.g., (*operation)()).
When defining a type alias, we must explicitly specify any template arguments. We can't use CTAD in this case since there is no initializer to deduce the template arguments from.
Type inference for function pointers
Much like the auto keyword can be used to infer the type of normal variables, the auto keyword can also infer the type of a function pointer.
#include <iostream>
int calculate(int x)
{
return x * 2;
}
int main()
{
auto operation{ &calculate };
std::cout << operation(15) << '\n';
return 0;
}
This works exactly like you'd expect, and the syntax is very clean. The downside is, of course, that all of the details about the function's parameter types and return type are hidden, so it's easier to make a mistake when making a call with the function, or using its return value.
Conclusion
Function pointers are useful primarily when you want to store functions in an array (or other structure), or when you need to pass a function to another function. Because the native syntax to declare function pointers is ugly and error prone, we recommend using std::function. In places where a function pointer type is only used once (e.g., a single parameter or return value), std::function can be used directly. In places where a function pointer type is used multiple times, a type alias to a std::function is a better choice (to prevent repeating yourself).
Summary
Function pointers: Variables that hold the address of a function. Functions have types based on their return type and parameters. Without parentheses, a function name converts to a function pointer.
Declaring function pointers: Syntax is returnType (*pointerName)(parameters). Parentheses around *pointerName are required. For const function pointers, const goes after the asterisk: int (*const operation)().
Assigning functions: Function pointers can be initialized or assigned using either the function name (implicit conversion) or &functionName (explicit). They can also be assigned nullptr.
Calling through function pointers: Two approaches - explicit dereference (*funcPtr)(args) or implicit dereference funcPtr(args). Modern compilers support both. Always check for nullptr before calling.
Default arguments: Don't work with function pointers because resolution happens at runtime, not compile-time. This can be used to disambiguate overloaded functions.
Callback functions: Functions passed as arguments to other functions, allowing callers to customize behavior. Common in algorithms like sorting where the caller provides a comparison function.
Type aliases for readability: Use using to simplify function pointer syntax: using CompareFunction = bool(*)(int, int). This makes function pointer parameters more readable.
std::function: From <functional> header, provides a cleaner alternative to raw function pointers. Syntax: std::function<returnType(params)>. Only supports implicit dereference calling.
Type inference with auto: Can use auto to infer function pointer types from initialization, but hides parameter and return type details.
Best practice: Prefer std::function over raw function pointer syntax due to better readability and error handling. Use type aliases when a function pointer type is used multiple times.
Function pointers enable powerful patterns like callbacks and configurable algorithms, making code more flexible and reusable. While the native syntax is complex, std::function and type aliases make them much more practical.
Understanding Function Pointers - Quiz
Test your understanding of the lesson.
Practice Exercises
Function Pointers Basics
Learn to declare, assign, and call functions through function pointers. Practice passing function pointers as arguments to create flexible callback mechanisms.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!