On this page I found a good example of function pointers in C++ (as well as of functors, but this question isn't about functors). Below is some copypasta from that page.
#include <iostream>
double add(double left, double right) {
return left + right;
}
double multiply(double left, double right) {
return left * right;
}
double binary_op(double left, double right, double (*f)(double, double)) {
return (*f)(left, right);
}
int main( ) {
double a = 5.0;
double b = 10.0;
std::cout << "Add: " << binary_op(a, b, add) << std::endl;
std::cout << "Multiply: " << binary_op(a, b, multiply) << std::endl;
return 0;
}
I understand the code in general terms, but there are a couple of things that I've always found confusing. Function binary_op()
takes a function pointer *f
, but when it's used, for example on line 19 binary_op(a, b, add)
, the function symbol add
is passed in, not what one would think of as its pointer, &add
. Now you may say that this is because the symbol add
is a pointer; it's the address of the bit of code corresponding to the function add()
. Very well, but then there still seems to be a type discrepancy here. The function binary_op()
takes *f
, which means f
is a pointer to something. I pass in add
, which itself is a pointer to code. (Right?) So then f
is assigned the value of add
, which makes f
a pointer to code, which means that f
is a function just like add
, which means that f
should be called like f(left, right)
, exactly how add
should be called, but on line 12, it's called like (*f)(left, right)
, which doesn't seem right to me because it would be like writing (*add)(left, right)
, and *add
isn't the function, it's the first character of the code that add
points to. (Right?)
I know that replacing the original definition of binary_op()
with the following also works.
double binary_op(double left, double right, double f(double, double)) {
return f(left, right);
}
And in fact, this makes much more sense to me, but the original syntax doesn't make sense as I explained above.
So, why is it syntactically correct to use (*f)
instead of just f
? If the symbol func
is itself a pointer, then what precisely does the phrase "function pointer" or "pointer to a function" mean? As the original code currently stands, when we write double (*f)(double, double)
, what kind of thing is f
then? A pointer to a pointer (because (*f)
is itself a pointer to a bit of code)? Is the symbol add
the same sort of thing as (*f)
, or the same sort of thing as f
?
Now, if the answer to all of this is "Yeah C++ syntax is weird, just memorise function pointer syntax and don't question it." , then I'll reluctantly accept it, but I would really like a proper explanation of what I'm thinking wrong here.
I've read this question and I think I understand that, but haven't found it helpful in addressing my confusion. I've also read this question , which also didn't help because it doesn't directly address my type discrepancy problem. I could keep reading the sea of information on the internet to find my answer but hey, that's what Stack Overflow is for right?
This is because C function pointer are special.
First of, the expression add
will decay into a pointer. Just like reference to array will decay into a pointer, reference to function will decay into a pointer to function.
Then, the weird stuff it there:
return (*f)(left, right);
So, why is it syntactically correct to use
(*f)
instead of justf
?
Both are valid, you can rewrite the code like this:
return f(left, right);
This is because the dereference operator will return the reference to the function, and both a reference to a function or a function pointer are considered callable.
The funny thing is that a function reference decay so easily that it will decay back into a pointer when calling the dereference operator, allowing to dereference the function as many time as you want:
return (*******f)(left, right); // ah! still works
As the original code currently stands, when we write
double (*f)(double, double)
, what kind of thing isf
then?
The type of f
is double (*)(double, double)
ie it is a pointer to a function of type double(double,double)
.
because
(*f)
is itself a pointer
It is not.
Q: What do you get when you indirect through a pointer (such as in *f
)? A: You get an lvalue reference. For example, given an object pointer int* ptr
, the type of the expression *ptr
is int&
ie lvalue reference to int
.
The same is true for function pointers: When you indirect through a function pointer, you get an lvalue reference to the pointed function. In the case of *f
, the type is double (&)(double, double)
ie reference to function of type double(double,double)
.
Is the symbol
add
the same sort of thing as(*f)
, or the same sort of thing asf
?
The unqualified id expression add
is the same sort of thing as *f
ie it is an lvalue:
Standard draft [expr.prim.id.unqual]
... The expression is an lvalue if the entity is a function ...
the function symbol
add
is passed in, not what one would think of as its pointer,&add
. Now you may say that this is because the symboladd
is a pointer;
No. That's not the reason.
add
is not a pointer. It is an lvalue. But lvalues of function type implicitly convert to a pointer (this is called decaying):
Standard draft [conv.func]
An lvalue of function type T can be converted to a prvalue of type “pointer to T”. The result is a pointer to the function.
As such, the following are semantically equivalent:
binary_op(a, b, add); // implicit function-to-pointer conversion
binary_op(a, b, &add); // explicit use of addressof operator
So, why is it syntactically correct to use
(*f)
instead of justf
?
Turns out that calling a function lvalue has the same syntax as calling a function pointer:
Standard draft [expr.call]
A function call is a postfix expression followed by parentheses containing a possibly empty, comma-separated list of initializer-clauses which constitute the arguments to the function. The postfix expression shall have function type or function pointer type. For a call to a non-member function or to a static member function, the postfix expression shall either be an lvalue that refers to a function (in which case the function-to-pointer standard conversion ([conv.func]) is suppressed on the postfix expression), or have function pointer type .
These are all the same function call:
add(parameter_list); // lvalue
(*f)(parameter_list); // lvalue
(&add)(parameter_list); // pointer
f(parameter_list); // pointer
PS These two declarations are equivalent:
double binary_op(double, double, double (*)(double, double))
double binary_op(double, double, double (double, double))
This is because of the following rule, which is complementary to the implicit decay into function pointer:
Standard draft [dcl.fct]
The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T” ...
First of all a function parameter specified as a function declaration is adjusted to pointer to the function when the compiler determinates the type of the parameter. So for example following function declarations
void f( void h() );
void f( void ( *h )() );
are equivalent and declare the same one function.
Consider the following demonstrative program
#include <iostream>
void f( void h() );
void f( void ( *h )() );
void h() { std::cout << "Hello Ray\n"; }
void f( void h() ) { h(); }
int main()
{
f( h );
}
From the c++ 17 Standard (11.3.5 Functions):
5 The type of a function is determined using the following rules. The type of each parameter (including function parameter packs) is determined from its own decl-specifier-seq and declarator. After determining the type of each parameter, any parameter of type “array of T” or of function type T is adjusted to be “pointer to T ”.
On the other hand, according to the C++ 17 Standard
9 When there is no parameter for a given argument, the argument is passed in such a way that the receiving function can obtain the value of the argument by invoking va_arg (21.11). [ Note: This paragraph does not apply to arguments passed to a function parameter pack. Function parameter packs are expanded during template instantiation (17.6.3), thus each such argument has a corresponding parameter when a function template specialization is actually called. — end note ] The lvalue-to-rvalue (7.1), array-to-pointer (7.2), and function-to-pointer (7.3) standard conversions are performed on the argument expression
So what is the difference between these two declarations
void f( void h() );
void f( void ( *h )() );
For the first declaration you may consider the parameter h
within the function body like a typedef for a function pointer.
typedef void ( *H )();
For example
#include <iostream>
void f( void h() );
void f( void ( *h )() );
void h() { std::cout << "Hello Ray\n"; }
typedef void ( *H )();
void f( H h ) { h(); }
int main()
{
f( h );
}
According to the C++ 17 Standard (8.5.1.2 Function call)
1 A function call is a postfix expression followed by parentheses containing a possibly empty, comma-separated list of initializer-clauses which constitute the arguments to the function. The postfix expression shall have function type or function pointer type .
So you may also define the function like
void f( void h() ) { ( *h )(); }
Or even like
void f( void h() ) { ( ******h )(); }
because when the operator * is applied to a function name then the function name is implicitly convereted to pijnter to the function.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.