Quantcast
Channel: Vjeux » C++
Viewing all articles
Browse latest Browse all 2

Simulate Closure in C

$
0
0

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.


Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles



Latest Images