Function pointers in C++
May 17, 2023
Hello there,
I’ve been writing an interpreter for the Monkey programming langugage in C++ for over a month now. It is nearing completion :)
One of the steps involved in acheiving a fully functional interpreter is writing a parser. The interpreter I’m writing uses top-down operator precedence by Vaughan Pratt. It is also popularly known as Pratt parsing.
Thorsten cleanly implements pratt parsing by storing parse functions for each different token encountered. The implementation is actually quite simple….in Golang :(
The implementation, when translated into C++ demands storage, retrieval and invocation of function pointers.
Function pointers are deceptively easy
A lot of tutorials online cover the usage of function pointers in very trivial situations, but storing functions defined in different namespaces, spanning different classes, having different return types…is most definitely harder than it looks.
It took me more effort than I anticipated to wrap my head around them, and I wish to put my findings in this article.
fptr syntax
void (*fptr)();
fptr
is a pointer to a function that takes no arguments and has a void
return type
int (*fptr)(int, int);
fptr
is a pointer to a function that takes two int
arguments and has an int
return type
- function pointer that takes in two pointers and returns a pointer?
int *(*fptr)(int *, int *);
The function now takes two pointers to int
as arguments returns another pointer to int
- what if the objects span across different namespaces and classes?
ns1::object *(*fptr)(ns2::object *, ns3::object *);
let ns1
, ns2
and ns3
be namespaces defined in the project
fptr
is a pointer to a function that takes two pointers of type object
that belong to ns2
and ns3
and returns a pointer to object
belonging to ns1
.
- binding a function pointer to point only to functions belonging to a namespace/class?
ns1::object *(*generic::fptr)(ns2::object *, ns3::object *);
now fptr
points to functions that are defined in the generic
namespace or class
typedef to the rescue
If you’re working in a large project, you’re bound to end up classifying objects into separate namespaces, and eventually end up with a large, ambiguous (and ugly) syntax.
This is where typedef
comes to rescue. But how would you define a generic structure for a function pointer and use the typedef at other places in your code?
// no args
typedef ns1::object *(generic::*ftype1)();
// takes in args
typedef ns1::object *(generic::*ftype1)(ns2::object *);
ftype
now represents the type of function pointers that point to functions defined inside of generic
namespace or class and return pointer to an ns1::object
- usage
ftype ptr;
Storing function pointers
let’s say you want to store function pointers inside a collection, like std::map
or std::vector
simple enough
// key = int
// value = function pointer that returns pointer to int
std::map<int, int *(*)()> ptr_map;
// using typedef
std::map<int, ftype> ptr_map;
Assigning fptrs to functions
what’s point of defining function pointers if you can’t actually assign them to methods defined in your libraries
let us first define a method named some_method
inside namespace ns1
// ns1.h
namespace ns1{
std::string *some_method(){
std::string hello = "hello";
return &hello;
}
}
syntax for assigning
#include <ns1.h>
// explicit conversion
std::string *(*fptr)() { &ns1::some_method };
// implicit conversion
std::string *(*fptr)() { ns1::some_method };
// vectors
ptr_vec.push_back(&ns1::some_method);
// make associations inside maps
ptr_map[0] == &ns1::some_method;
// when key is enum
ptr_map[some_enum::name] = &ns1::some_method;
Invocation
Congrats! Now you know how to create function pointers and assign them to functions already defined. Let’s invoke these pointers now:
first, let us retrieve a pointer we earlier stored in a map, and assign it to a variable
// simple right
std::string *(*fptr)() = ptr_map[0];
// fptr now points to the some_method function we wrote earlier
what about namespaces and classes? not so simple :(
genericNs::object *(genericClass::*fptr)() = ptr_map["key"]
fptr
now points to an instance of genericClass
if you’re running this inside the genericClass
object, the invocation looks like this
// inside genericClass scope
std::string *result = (this->*fptr)();
you have to use the this
pointer to access the function the pointer points to, which is done by de-referencing fptr
.
If invocation doesn’t happen inside genericClass
scope, then it’s pretty straight-forward: de-reference the pointer and invoke.
Example
- Higher order functions
A higher order function is a function that takes in one or more functions as arguments or returns a function
Let’s see how we can implement this in c++
This is a short example on how a general purpose function can be written, that applies a function on other arguments.
Below is a demonstration of one such general purpose function. The calculate
method takes in area
method and applies it on the radius
argument.
The usage here is simple enough, but the idea is powerful. This calculate
method is now agnostic of the object being passed in, so we can now calculate the area of a square or a circle.
// fptrs.cpp
#include <iostream>
#include <string>
using namespace std;
typedef string (*string_fptr)();
typedef int (*int_fptr)(int);
string even_string()
{
return "result is even";
}
string odd_string()
{
return "result is odd";
}
string_fptr calculate(int_fptr function, int value)
{
int result = (*function)(value);
if (result % 2 == 0)
return &even_string;
return &odd_string;
}
int area(int radius)
{
return 3.14 * radius * radius; // say pi = 3.14
}
int main()
{
string result = (*calculate(&area, 5))();
cout << result << endl;
return 0;
}
quick check using g++
g++ ./fptrs.cpp -o output
./output
# result is even
And…..this is how you use function pointers. Simple, yet powerful stuff.
Well, that’s it for this article. See you in the next one :)
~rahultumpala