Holy syntax! How can pf and (*pf) be equivalent? One school of thought maintains that because pf is a pointer to a function, *pf is a function; hence, you should use (*pf)() as a function call. A second school maintains that because the name of a function is a pointer to that function, a pointer to that function should act like the name of a function; hence you should use pf() as a function call. C++ takes the compromise view that both forms are correct, or at least can be allowed, even though they are logically inconsistent with each other. Before you judge that compromise too harshly, reflect that the ability to hold views that are not logically self-consistent is a hallmark of the human mental process.
A Function Pointer Example
Listing 7.18 demonstrates using function pointers in a program. It calls the estimate() function twice, once passing the betsy() function address and once passing the pam() function address. In the first case, estimate() uses betsy() to calculate the number of hours necessary, and in the second case, estimate() uses pam() for the calculation. This design facilitates future program development. When Ralph develops his own algorithm for estimating time, he doesn’t have to rewrite estimate(). Instead, he merely needs to supply his own ralph() function, making sure it has the correct signature and return type. Of course, rewriting estimate() isn’t a difficult task, but the same principle applies to more complex code. Also the function pointer method allows Ralph to modify the behavior of estimate(), even if he doesn’t have access to the source code for estimate().
Listing 7.18. fun_ptr.cpp
// fun_ptr.cpp -- pointers to functions
#include
double betsy(int);
double pam(int);
// second argument is pointer to a type double function that
// takes a type int argument
void estimate(int lines, double (*pf)(int));
int main()
{
using namespace std;
int code;
cout << "How many lines of code do you need? ";
cin >> code;
cout << "Here's Betsy's estimate:\n";
estimate(code, betsy);
cout << "Here's Pam's estimate:\n";
estimate(code, pam);
return 0;
}
double betsy(int lns)
{
return 0.05 * lns;
}
double pam(int lns)
{
return 0.03 * lns + 0.0004 * lns * lns;
}
void estimate(int lines, double (*pf)(int))
{
using namespace std;
cout << lines << " lines will take ";
cout << (*pf)(lines) << " hour(s)\n";
}
Here is a sample run of the program in Listing 7.18:
How many lines of code do you need? 30
Here's Betsy's estimate:
30 lines will take 1.5 hour(s)
Here's Pam's estimate:
30 lines will take 1.26 hour(s)
Here is a second sample run of the program:
How many lines of code do you need? 100
Here's Betsy's estimate:
100 lines will take 5 hour(s)
Here's Pam's estimate:
100 lines will take 7 hour(s)
Variations on the Theme of Function Pointers
With function pointers, the notation can get intimidating. Let’s look at an example that illustrates some of the challenges of function pointers and ways of dealing with them. To begin, here are prototypes for some functions that share the same signature and return type:
const double * f1(const double ar[], int n);
const double * f2(const double [], int);
const double * f3(const double *, int);
The signatures might look different, but they are the same. First, recall that in a function prototype parameter list const double ar[] and const double * ar have exactly the same meaning. Second, recall that in a prototype you can omit identifiers. Therefore, const double ar[] can be reduced to const double [], and const double * ar can be reduced to const double *. So all the function signatures shown previously have the same meaning. Function definitions, on the other hand, do provide identifiers, so either const double ar[] or const double * ar will serve in that context.
Next, suppose you wish to declare a pointer that can point to one of these three functions. The technique, you’ll recall, is if pa is the desired pointer, take the prototype for a target function and replace the function name with (*pa):
const double * (*p1)(const double *, int);
This can be combined with initialization:
const double * (*p1)(const double *, int) = f1;
With the C++11 automatic type deduction feature, you can simplify this a bit:
auto p2 = f2; // C++11 automatic type deduction
Now consider the following statements:
cout << (*p1)(av,3) << ": " << *(*p1)(av,3) << endl;
cout << p2(av,3) << ": " << *p2(av,3) << endl;
Both (*p1)(av,3) and p2(av,3), recall, represent calling the pointed-to functions (f1() and f2(), in this case) with av and 3 as arguments. Therefore, what should print are the return values of these two functions. The return values are type const double * (that is, address of double values). So the first part of each cout expression should print the address of a double value. To see the actual value stored at the addresses, we need to apply the * operator to these addresses, and that’s what the expressions *(*p1)(av,3) and *p2(av,3) do.