> Does it bring any convenience to use functions as objects in C++?
Yes: The C++ template mechanism allows all other C/C++ programming styles (C style and OOP style, see below).
> Why can't we just use function pointer instead? Any examples?
But we can: A simple C function pointer is an object with a well defined operator(), too.
If we design a library, we do not want to force anyone to use that C pointer style if not desired. It is usually as undesired as forcing everything/everyone to be in/use OOP style; see below.
From C-programmers and functional programmers views, OOP not only tends to be slower but more verbose and in most cases to be the wrong direction of abstraction ("information" is not and should not be an "object"). Because of that, people tend to be confused whenever the word "object" is used in other contexts.
In C++, anything with the desired properties can be seen as an object. In this case, a simple C function pointer is an object, too. This does not imply that OOP paradigms are used when not desired; it is just a proper way to use the template mechanism.
To understand the performance differences, compare the programming(-language) styles/paradigms and their possible optimisations:
C style:
- Function pointer with its closure ("this" in OOP, pointer to some structure) as first parameter.
- To call the function, the address of the function needs to be accessed first.
- That is 1 indirection; no inlining possible.
C++ (and Java) OOP style:
- Reference to an object derived from a class with virtual functions.
- Reference is 1st pointer.
- Pointer to virtual-table is 2nd pointer.
- Function pointer in virtual-table is 3rd pointer.
- That are 3 indirections; no inlining possible.
C++ template style:
- Copy of an object with () function.
- No virtual-table since the type of that object is known at compile time.
- The address of the function is known at compile time.
- That are 0 indirections; inlining possible.
The C++ templates are versatile enough to allow the other two styles above, and in the case of inlining they can even outperform…
compiled functional languages: (excluding JVM and Javascript as target platforms because of missing "proper tail calls")
- Function pointer and reference to its closure in machine registers.
- It is usually no function "call" but a GOTO like jump.
- Functions do not need the stack, no address to jump back, no parameters nor local variables on the stack.
- Functions have their garbage collectable closure(s) containing parameters and a pointer to the next function to be called.
- For the CPU to predict the jump, the address of the function needs to be loaded to a register as early as possible.
- That is 1 indirection with possible jump prediction; everything is nearly as fast as inlined.