As we all know that a pointer holds the address of another variable. Function pointer are similar, except that instead of pointing to variables, they point to functions.
Consider the following function:
int foo()
{
return 7;
}
Identifier foo is the function's name. But what type is the function?
Functions have their own l-value 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.
When a function called (via the () operator), execution jumps to the address of the function being called:
int foo() // code for foo starts at memory address 0x007000f0
{
return 7;
}
int main()
{
foo(); // jump to address 0x007000f0
return 0;
}
At some point in your programming career, you will probably make a simple mistake:
#include <iostream>
int foo() // code starts at memory address 0x002717f0
{
return 7;
}
int main()
{
std::cout << foo << '\n'; // we meant to call foo(), but instead we're printing foo itself!
return 0;
}
Instead of calling function foo() and printing the return value, we unintentionally sent function foo directly to std::cout. What happens in this case?operator<<
does not know how to output a function pointer. The standard says in this case, foo
should be converted to a bool
(which operator<<
does know how to print). And since the function pointer of foo
is non-void pointer, it should always evaluate to Boolean true
. This it should print:
1
Anatomy of Function Pointers
Declaration and Syntax:
In C++, a function pointer is a variable that can store the address of a function. To declare a function pointer, you specify the return type and parameter types of the function it can point to . Here's a basic declaration syntax:
return_type (*pointer_name)(parameter_types);
For example, to declare a function pointer that points to a function taking two integers and returning an integer:
int (*addPointer)(int, int);
The parentheses around *addPointer
are necessary for precedence reasons, as int* addPointer()
would be interpreted as a forward declaration for a function named addPointer
that takes two integer parameters and returns a pointer to an integer.
To make a const function pointer, the const goes after the asterisk:
int (*const fcnPtr)();
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 &foo
to get a function pointer to foo.
int foo()
{
return 5;
}
int goo()
{
return 6;
}
int main()
{
int (*fcnPtr)(){ &foo }; // fcnPtr points to function foo
fcnPtr = &goo; // fcnPtr now points to function goo
return 0;
}
- 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:
// function prototypes
int foo();
double goo();
int hoo(int x);
// function pointer initializers
int (*fcnPtr1)(){ &foo }; // okay
int (*fcnPtr2)(){ &goo }; // wrong -- return types don't match!
double (*fcnPtr4)(){ &goo }; // okay
fcnPtr1 = &hoo; // wrong -- fcnPtr1 has no parameters, but hoo() does
int (*fcnPtr3)(int){ &hoo }; // okay
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.
// function prototypes
int foo();
// function initializations
int (*fcnPtr5)() { foo }; // okay, foo implicitly converts to function pointer to foo
void* vPtr { foo }; // not okay, though some compilers may allow
- Function pointers can also be initialized or assigned the value
nullptr
:
int (*fcnPtr)() { nullptr }; // Okay
Calling a function using a function pointer
The primary thing you can do with a function pointer is use it to actually call the function. There are two ways to do this. This first is via explicit dereference:
int foo(int x)
{
return x;
}
int main()
{
int (*fcnPtr)(int){ &foo }; // Initialize fcnPtr with function foo
(*fcnPtr)(5); // call function foo(5) through fcnPtr.
return 0;
}
The second way is via implicit dereference:
int foo(int x)
{
return x;
}
int main()
{
int (*fcnPtr)(int){ &foo }; // Initialize fcnPtr with function foo
fcnPtr(5); // call function foo(5) through fcnPtr.
return 0;
}
As you can see, the implicit dereference method looks just like a normal function call – which is what you would expect, since normal function names are pointer to functions anyway. However some old compilers do not support the implicit dereference method, but all modern compilers should.
One Interesting note: Default parameters won't work for functions called through function pointers. Default parameters are resolved at compile-time (that is, if you don't supply an argument for a defaulted parameters, the compiler substitutes one in for you when the code is compiled). However, function pointers are resolved at run-time. Consequently, default parameters cannot be resolved when making a function call with a function pointer, You will explicitly have to pass in values for any defaulted parameters in this case.
Also note that because pointers can be set to nullptr, it's a good idea to assert or conditionally test whether you function pointer is a null pointer before calling it. Just like with normal pointers, dereferencing a null pointer leads to undefined behavior.
int foo(int x)
{
return x;
}
int main()
{
int (*fcnPtr)(int){ &foo }; // Initialize fcnPtr with function foo
if (fcnPtr) // make sure fcnPtr isn't a null pointer
fcnPtr(5); // otherwise this will lead to undefined behavior
return 0;
}
Passing functions as arguments to other functions
One of the most useful things to do with function pointers is to pass a function as an argument to another function. Functions used as arguments to another function are 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 the that task will be performed (such as whether the array is sorted in ascending or descending order). For example:
#include <iostream>
#include <algorithm>
// Define a function pointer type for comparison functions
typedef bool (*CompareFunction)(int, int);
// Function to perform array sorting with user-defined comparison
void customSort(int arr[], int size, CompareFunction compare) {
std::sort(arr, arr + size, compare);
}
// Comparison function for ascending order
bool ascending(int a, int b) {
return a < b;
}
// Comparison function for descending order
bool descending(int a, int b) {
return a > b;
}
int main() {
int arr[] = {4, 2, 7, 1, 9};
const int size = sizeof(arr) / sizeof(arr[0]);
std::cout << "Original array: ";
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// Sort in ascending order
customSort(arr, size, ascending);
std::cout << "Array sorted in ascending order: ";
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// Sort in descending order
customSort(arr, size, descending);
std::cout << "Array sorted in descending order: ";
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
In this example, the customSort
function takes an array, its size, and a function pointer compare
representing the user-defined comparison function. The std::sort
function from the C++ Standard Library is then used with the provided comparison function to perform the sorting.
The ascending
and descending
functions serve as the user-defined comparison functions, and the customSort
function can be easily extended to accommodate additional custom comparison functions based on the user's requirements.
bool (*ptr)(int, int); // definition of function pointer ptr
bool fcn(int, int); // forward declaration of function fcn
Providing default functions
If you are going to allow the caller to pass in a function as a parameter, it can often be useful to provide some standard function for the caller to use for their convenience. For example,
// Default the sort to ascending sort
void selectionSort(int* array, int size, bool (*comparisonFcn)(int, int) = ascending);
In this case, as long as the user calls selectionSort normally (not through function pointer), the comparisonFcn parameter will default to ascending. You will need to make sure that the ascending
function is declared prior to this point.
Making function pointers prettier with type aliases
Type aliases can be used to simplify complex type declarations, including those involving function pointers.
using ValidateFunction = bool(*)(int, int);
This defines a type alias called “ValidateFunction” that is a pointer to a function that takes two integers and returns a bool.
Now instead of doing this:
bool validate(int x, int y, bool (*fcnPtr)(int, int)); // ugly
We can do this:
bool validate(int x, int y, ValidateFunction pfcn) // clean
Look at the complete example given below:
#include <iostream>
// Type alias for a function pointer that takes two integers and returns an integer
using AddFunction = int (*)(int, int);
// Function that adds two integers
int add(int a, int b) {
return a + b;
}
int main() {
// Declare a pointer using the type alias
AddFunction addPointer = add;
// Call the function through the pointer
int result = addPointer(3, 4); // result is 7
std::cout << "Result: " << result << std::endl;
return 0;
}
In this example, the using AddFunction = int (*)(int, int);
line creates a type alias AddFunction
for a function pointer that takes two integers and returns an integer. This makes the declaration of addPointer
more concise and enhances code readability.
More example:
Type Alias for Comparison Functions in Sorting:
#include <iostream>
#include <algorithm>
// Type alias for a function pointer representing a comparison function
using CompareFunction = bool (*)(int, int);
// Function to perform array sorting with user-defined comparison
void customSort(int arr[], int size, CompareFunction compare) {
std::sort(arr, arr + size, compare);
}
// Comparison function for ascending order
bool ascending(int a, int b) {
return a < b;
}
// Comparison function for descending order
bool descending(int a, int b) {
return a > b;
}
int main() {
int arr[] = {4, 2, 7, 1, 9};
const int size = sizeof(arr) / sizeof(arr[0]);
// Sort in ascending order using type alias
customSort(arr, size, ascending);
std::cout << "Array sorted in ascending order: ";
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
// Sort in descending order using type alias
customSort(arr, size, descending);
std::cout << "Array sorted in descending order: ";
for (int i = 0; i < size; ++i) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
return 0;
}
Using std::function
An alternate method of defining and storing function pointers is to use std::function, which is part of the standard library <functional> header. To define a function pointer using this method, declare a std::function object like so:
#include <functional>
bool validate(int x, int y, std::function<bool(int, int)> fcn); // std::function method that returns a bool and takes two int parameters
As you can, 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.
For Example:
#include <functional>
#include <iostream>
int foo()
{
return 5;
}
int goo()
{
return 6;
}
int main()
{
std::function<int()> fcnPtr{ &foo }; // declare function pointer that returns an int and takes no parameters
fcnPtr = &goo; // fcnPtr now points to function goo
std::cout << fcnPtr() << '\n'; // call the function just like normal
return 0;
}
Type aliasing std::function can be helpful for readability:
using ValidateFunctionRaw = bool(*)(int, int); // type alias to raw function pointer
using ValidateFunction = std::function<bool(int, int)>; // type alias to std::function
Also note that std::function only allows calling function via implicit dereference (e.g., fcnPtr()
), not explicit dereference (e.g. (*fcnPtr)()
).
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 foo(int x)
{
return x;
}
int main()
{
auto fcnPtr{ &foo };
std::cout << fcnPtr(5) << '\n';
return 0;
}
This works exactly like you would expect, and the syntax is very clean. The downside is, of course, that all of the details about the function's parameters 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.