DRY Function Pointers in C

Just a quick post today about C function pointers. Over the past two years I have seen the occasional function pointer introduction post on Hacker News, but I rarely see this one weird trick.

The most recent I have read was this one by Dennis Kubes. I haven’t hung out with C for a while (I more frequently visit its octopussier cousin, onto which a lambda-shaped leg has been nailed), so the post triggered some fond memories.

This post is just a leg nailed to his post, so go read it now if you would like to know about function pointers in C.

Kubes presents an example where he defines a domath function, that accepts a pointer to a function that performs an operation on two integers:

int domath(int (*mathop)(int, int), int x, int y) {
  return (*mathop)(x, y);
}

For a more realistic example, let’s say you implemented fold instead, and hey, why not left-fold also?

int fold_right(int (*f)(int, int), int init, const int * first, const int * last) {
  int result = init;
  while(first != last) {
    result = f(result, *first++);
  }
  return result;
}

int fold_left(int (*f)(int, int), int init, const int * first, const int * last) {
  int result = init;
  while(first != last) {
    result = f(*last--, result);
  }
  return result;
}

Now imagine a whole family of higher order functions like these… the two function signatures here are bad enough already. The syntax doesn’t communicate well – you actually have to work out wtf int (*f)(int, int) means, unless you write C in your sleep. Even then, there is a chance the next person to maintain your code will cry.

Let’s look out for our fellow coder and DRY those tears. C has syntax for typedefing function pointers. The result is much friendlier:

typedef int (*binary_int_op) (int, int);

int fold_right(binary_int_op f, int init, const int * first, const int * last) {
  int result = init;
  while(first != last) {
    result = f(result, *first++);
  }
  return result;
}

int fold_left(binary_int_op f, int init, const int * first, const int * last) {
  int result = init;
  while(first != last) {
    result = f(*last--, result);
  }
  return result;
}

That is still some weird ass syntax at first, but you write that once (per type/intent of function pointer) and then live a happier life. And if you choose the name intelligently, you can communicate the intent as fast as the reader can read English.

Or you could ditch C and use C++’s functional programming tools.

  • Dirk Willrodt

    Hi, good read, as always.
    But why does your DRY link point to wikipedias page for ‘Don’?

  • Aszarsha

    You might want to change *last– into *(–last) to respect the usual convention of one-past-end references. And, to be honest, I find the first version much clearer since binary_int_op is an obfuscation to anybody who know its tool. Or who knows its programming for the matter—fold should give all the information in the first place.

  • tommay

    I must write C in my sleep because hiding pointers behind typedefs or #defines creates gratuitous cognitive load for me and makes things less clear.