I'm implementing a layout algorithm in C and want to let the user specify a callback to compute the height based on the width.
Using function pointers, we can provide the callback:
typedef struct { float (*measure)(float width); } layout_node_t; void layout(layout_node_t *node) { float width = 10; float height = node->measure(width); } |
It works well if we have a function measure that only uses global variables:
float measure(float width) { return width * 2; } int main() { layout_node_t node; node.measure = measure; layout(&node); } |
However, I would like my measure function to take some dynamic input. For example in order to measure an image, you need to take its aspect ratio into account. In JavaScript, I would write the following:
var aspect_ratio = 1.5; node.measure = function mesure_image(width) { return width * aspect_ratio; } |
Unfortunately, C doesn't support closures. I haven't been able to find a way to get a function pointer alone somehow hold some state. The best trade-off I found was to have a void *
metadata in the struct and pass it along with the function call. (Thanks Scott and Felix for the help!)
typedef struct { float (*measure)(void *context, float width); void *measure_context; } layout_node_t; void layout(layout_node_t *node) { float width = 10; float height = node->measure(node->measure_context, width); } |
The void *
value lets us put anything we want in it. So, with some casting we are able to simulate a closure and write our measure_image
function 🙂
float measure_image(void *context, float width) { float aspect_ratio = *(float *)context; return width / aspect_ratio; } int main() { layout_node_t node; node.measure = measure_image; float aspect_ratio = 1.5; node.measure_context = (void *)&aspect_ratio; layout(&node); } |
To compute the height of the image we use a float
, but in order to handle text, we can pass a const char *
instead. It works as well!
float measure_text(void *content, float width) { const char *text = (const char *)content; float line_height = 11; return ceil(strlen(text) / width) * line_height; } int main() { layout_node_t node; node.measure = measure_text; node.measure_context = (void *)"this is some super long text"; layout(&node); } |
This solves the use case pretty well, which is remarkable since C doesn't support closure. The downside is that we are losing all the type information, have to do a lot of type casting and renaming.