Are there any differences between these two higher-order function definitions?

Solution 1:

First off, function pointers are hard. Thinking that you can pass a function as a parameter to another function requires some mind-bending similar to understanding recursion. You won't get it at first, but then all of a sudden it's like the floodgates of understanding open in your brain and you're enlightened.

But then, you still have to know the rules of passing functions as parameters in C and C++. In these languages, functions are not first-class citizens, so there are a lot of restrictions on what you can do with them.

Syntax

The function pointer syntax is a little ugly. The basic anatomy is [return type] (*[name])([argument list]). The parentheses around *name are necessary to disambiguate between a function pointer and a function returning a pointer:

// not function pointers: * not grouped to function name
int x(); // function that returns an int
int* x(); // function that returns an int*
int *x(); // also a function that returns an int*, spaces don't matter

// function pointers: * grouped to function name
int (*x)(); // pointer to a function that returns an int
int* (*x)(); // pointer to a function that returns an int*

Decay

In terms of passing as parameters, functions behave about the same as arrays. When passed, they change into a pointer. Compare:

void Foo(int bar[4]); // equivalent to: void Foo(int* bar)
void Bar(int baz()); // equivalent to: void Bar(int (*baz)())

This is simply because functions and arrays are non-assignable and non-copyable:

int foo[4];
int bar[4] = foo; // invalid

int foo();
int bar() = foo; // invalid

Therefore, the only way to pass them as function parameters is to pass their address instead of copying them. (This is disputable for arrays, but that's how it works.) The fact that these "values" are transformed into pointers when passed as parameters is called "decay".

These two prototypes are compatible (that is, they refer to the same function, not different overloads), and therefore, there is no difference between the two:

int foo(void bar());
int foo(void (*bar)());

Visuals aside, there is absolutely no difference between those two declarations. Both functions accept a function pointer, whether it looks like it or not, because of decay. Though, since decay is often considered a nasty and confusing thing, most developers will prefer to explicitly ask for a function pointer (and a lot of developers don't even know function types can decay).

Implicit Conversions

Now, about passing functions as parameters. This one is simply a consequence of decay: functions have to be implicitly convertible to their function pointer type. This means that you can pass a function where a function pointer is expected, and the compiler will get its address for you. For this purpose, these are, once again, the same:

int foo();
int (*bar)() = foo; // the compiler implicitly assigns the address of foo to bar
int (*baz)() = &foo; // you explicitly assign the address of foo to baz

Combine those two explanations, and you'll realize that your four function calls are all the same. apply1 and apply2 both accept the same type of parameter (int (*)(void)), even if it's not obvious for apply1; and when you call the functions with func instead of &func, the compiler implicitly takes the address for you and makes it equivalent to &func.


The following is outside the scope of the question, but it elaborates on the previous part, and I think it's kind of neat.

Function References [C++ only]

It is a little-known fact, but it is also possible to pass references to arrays and functions: in this case, no decay happens. Like this:

void Foo(int (&bar)[4]); // NOT equivalent to void Foo(int* bar)
void Bar(int (&baz)()); // NOT equivalent to void Bar(int (*baz)())

In this scenario, you are not allowed to use the address-of operator, because there is no implicit conversion between pointer types and reference types. Defeating decay is generally seen as a good thing, as decay is often confusing.

int baz();
Bar(baz); // valid
Bar(&baz); // INVALID

Function references obey the same rules as normal references: they can be assigned only at definition time, and cannot be null.

Typedefs

You can make function pointers less ugly using typedef.

typedef int (*X)();
X func; // func is a pointer to a function that returns an int

Things get more interesting if you take out the (*) part:

typedef int X();
X* func; // func is a function pointer
X& func; // func is a function reference [C++ only]
X func; // func is a function declaration (!!)

In the latter case, X func; is equivalent to a declaration saying int func();. Don't do this at home, unless you want to confuse the hell out of everyone.

decltype makes a difference [C++ only]

Another interesting difference between functions and function pointers arises with the use of decltype. decltype "returns" the type of an expresson. For this construct, there is a difference between function and &function:

int bar();
decltype(bar); // type is int ()
decltype(&bar); // type is int (*)()

This difference is especially important if you want to pass the type as a template parameter, say, to std::unique_ptr.

std::unique_ptr<void, decltype(free)> foo; // INVALID
std::unique_ptr<void, decltype(&free)> foo; // valid

The first is invalid because it would attempt to create a function as an instance field of unique_ptr.