karen shea, captain's log

c++, technical-topics

Mon, Jan 11 2021

the fun in function pointers

To put it in linguistic terms, I think that the punctuation part of its orthography is what makes code fundamentally inscrutable to the uninitiated. In natural languages, punctuation is scary enough, isn't it? Haven't you seen the debates over all the different types of dashes? The oxford comma? In graphic design, hanging quotations are a sign of typographic pedigree, and dumb quotes are an incredible faux pas.

But in code, it goes a step further, the punctuation is arguably just as important as the words. Like, hello forkbomb. By punctuation I mean of course the operators. This past week, I tangled with some code that led me to better understand one of the less aesthetically pleasing combinations of C++ punctuation, function pointers.

function pointers

The cpp reference page on pointers says that pointers can be a few things, including "a pointer to an object or function" Coming from my now-distant homeland of javascript-land, I find this sentence sounds a bit redundant, because in JS functions are more or less objects with call operators. But in C++ functions are entities.

"Functions are not objects: there are no arrays of functions and functions cannot be passed by value or returned from other functions. Pointers and references to functions are allowed, and may be used where functions themselves cannot."

So, pointers-to-functions, i.e. function pointers, are indeed objects and can be passed and returned and assigned to and so on. How do you write this pointer type?

Return_Type (*Var_Name) (Arg types...);

If you squint, it looks a lot like the declaration of a function that returns a pointer type with some extra (*) around Var_Name right?

Return_Type* Function_Name(Arg types...);

Yes, those () are the only thing standing between a normal function declaration and a function pointer type declaration, like the C++ equivalent of the oxford comma's, "eats, shoots and leaves" joke.

// a function that measures stuff
int Measure(char grade, float distance) { ... }

// a variable called `FOperate` stores a pointer to a function
// that returns an int, and takes a char and a float as arguments
int(*FOperate)(char, float);
// sounds a lot like the  Measure function, huh.
// In fact we can store Measure in FOperate. FOperate is just a variable :)
FOperate = &Measure;

// same thing but inline assignment
int(*FOperate)(char, float) { &Measure };

// call the variable that stores the function pointer
FOperate('a', 1.0);

I hadn't seen this function pointer syntax with the variable name part before. More often what we want is a name for the function pointer type, so that it can be used in all the places that objects can be used. We need using or typedef for that.

// declare a user-defined type called `FOperate`, that
// happens to be a function pointer
using FOperate = int(*)(char, float);

// the function Call takes one argument that is of type FOperate
int Call(FOperate operation) {
    return operation('a', 1.0);
}

member function pointers

Member function pointers are actually yet another pointer type, similar to but also different from function pointers. There a whole section of isocpp guidelines devoted to member function pointers. It's really good; read it.

Return_Type (Class::*Var_Name) (Arg types...);

The new thing here is the Class::* part. Member functions are functions with an invisible first this argument that references the class that the member function is part of. Member functions can't be called without an instance of the class it's part of, so logically, to pass around a member function like an object, we also need to pass around the instance of the class that member is part of.

To recycle the first example:

struct Stick {
  int Measure(char grade, float distance) { ... }
};

// FOperate declaration looks similar, with an added class name
int(Stick::*FOperate)(char, float) { &Stick::Measure };

Stick stick;

// call the variable that stores the function pointer
(stick.*FOperate)('a', 1.0);

Oh my punctuation. This isn't a horribly practical example, but it is what it is.

Put it together

Here's a small program I distilled down from the code I wrote last week that shows how I used a function pointers. In short, I modified a class to accept a function object so that it could call that function as part of the class's overall call chain. Then I had to figure out how to make it work with a member function.

#include <functional>
#include <iostream>
#include <optional>
#include <string>
#include <unordered_map>

using namespace std;
// 1. using std::function instead of a plain function pointer
using FGetValue = function<optional<int>(int)>;

class TMap {
public:
  void Put(int key, int value) { mMap.insert({key, value}); }
  optional<int> GetValue(int value) const {
    return mMap.find(value) != mMap.end() ? make_optional(mMap.at(value))
                                          : nullopt;
  }
  optional<int> GetValue(const string &value) const {
    return GetValue(stoi(value));
  }
private:
  unordered_map<int, int> mMap{};
};
optional<int> SGetValue(int value) { return make_optional(value * value); }
void RequiresAValue(int value, FGetValue func) {
  std::cout << *func(value) << std::endl;
}

int main() {
  TMap FooMap{};
  FooMap.Put(2, 5);
  // 2. bind the `TMap::GetValue` method to a variable of type FGetValue
  FGetValue f_get =
      bind(static_cast<optional<int> (TMap::*)(int) const>(&TMap::GetValue),
           &FooMap, placeholders::_1);
  RequiresAValue(2, f_get);
  RequiresAValue(5, SGetValue);
}
  1. Here I'm using std::function but I easily could have written this using with a plain function pointer type, e.g. using FGetValue = optional<int>(*)(int). std::function is a nice, handy std way to treat functions like objects, that in most cases can replace using a function pointer type. The docs and examples are good, read em.
  2. There's a lot here! Importantly, std::bind returns std::function objects. I use the std::bind here to wrap the member function I want to capture (TMap::GetValue) into an std::function that I can then pass into RequiresAValue.

It actually took me a few days to write the 2. line because of a combination of post-holidays blues, existential democratic dread, and this stuff is hard (I also had some issues with some smart pointers, but it's tedious).

The debugging story

Initially I didn't have the static_cast and stared at this error for a couple days:

FGetValue f_get = bind(&TMap::GetValue, &FooMap, placeholders::_1);
...

main.cpp:38:21: error: no matching function for call to 'bind'
  FGetValue f_get = bind((&TMap::GetValue), &FooMap, placeholders::_1);
                    ^~~~
.../usr/bin/../include/c++/v1/functional:2953:1: note: candidate template ignored: couldn't infer
      template argument '_Fp'
bind(_Fp&& __f, _BoundArgs&&... __bound_args)
^
.../usr/bin/../include/c++/v1/functional:2962:1: note: candidate template ignored: couldn't infer
      template argument '_Rp'
bind(_Fp&& __f, _BoundArgs&&... __bound_args)
^
1 error generated.

I eventually figured out why the compiler didn't know what kind of std::function object to create with this std::bind call.

First, std::function has a templated constructor and so can be initialized with anything. Without an explicit hint, it will fail to infer what to intialize. (A helpful StackOverflow explanation.) TMap has two GetValue methods, and I hadn't provided a hint, so the compiler was confused and informed as such.

Second, I learned that overload resolution doesn't consider return types. The fact that the f_get variable was of type FGetValue had no effect on the compiler's sfinae process.

Thus, by using static_cast on the TMap::GetValue argument to std::bind I provided the hint that the compiler needed to return a function object of type FGetValue. In this static cast, you can see the function pointer type in use.

So that's it, a high-level explanation of how I spent my week. Much thanks to the include_cpp discord as always.