cfunction-pointerscomposite

Composite Predicate of function pointers in C


I am attempting to build a composite predicate using function pointers. I dont know if this is possible.

My predicates are of the form: int (*predicate)(int) Those work fine.

I want to make a function that takes 2 params of type int (*predicate)(int) and returns a function of type int (*predicate)(int), such that the new function returns the value of a(x) && b(x).

I want to make something that works like this:

int ( *composite(int (*a)(int), int (*b)(int)) )(int x) {
    return a(x) && b(x);
}

or:

int ( *negate(int (*a)(int)) )(int x) {
    return !a(x);
}

I understand that my attempts are returning a value, not a function, but if I try to make another function for them to return, I end up with the exact same problem.

If I do:

int composed(int (*a)(int), int (*b)(int), int x ) {
    return a(x) && b(x);
}

It compiles, but then it is no longer of type int (*predicate)(int) so I cannot use it the way that I want to.

How should I go about doing this?

Complete sample code attached for reference:

#include <stdio.h>

unsigned int count(const int* xs, unsigned int len, int (*predicate)(int)) {
  int c = 0;
  for(int i = 0; i < len; i++) {
    if(predicate(xs[i])) c++;
  }
  return c;
}

int isEven(int x) { return x % 2 == 0; }
int isOdd(int x) { return !isEven(x); }
int isPos(int x) { return x > 0; }
int isNeg(int x) { return x < 0; }

// int composed(int (*a)(int), int (*b)(int), int x ) {
//     return a(x) && b(x);
// }
// 
// int ( *composite(int (*a)(int), int (*b)(int)) )(int x) {
//     return &composed(a,b)(x)
// }

int main() {
  int xs[] = {-5,-4,-3,-2,-1,0,1,2,3,4,5};
  const int len = 11;

  printf("Even: %d\n", count(xs, len, &isEven));
  printf(" Odd: %d\n", count(xs, len, &isOdd));
  printf(" Pos: %d\n", count(xs, len, &isPos));
  printf(" Neg: %d\n", count(xs, len, &isNeg));

  // int (*compositePtr)(int) = composite(&isNeg, &isOdd);
  // printf("Odd & Neg: %d", count(xs, len, compositePtr));

}

Solution

  • You can't dynamically create closures or functions in C, so you need to do something else. The easiest is to use an extra level of indirection. If you change your predicates to

    int (**predicate)(void *, int)
    

    which you call as (**predicate)(predicate, x), then you can create new dynamic predicates quite easily:

    typedef int (*predicate_t)(void *, int);
    
    struct compose_fn_data {
        predicate_t  fn, *a, *b;
    };
    
    int and_fn(void *data_, int x) {
        struct compose_fn_data *data = data_;
        return (**data->a)(data->a, x) && (**data->b)(data->b, x);
    }
    
    int or_fn(void *data_, int x) {
        struct compose_fn_data *data = data_;
        return (**data->a)(data->a, x) || (**data->b)(data->b, x);
    }
    
    predicate_t *compose(predicate_t fn, predicate_t *a, predicate_t *b) {
        struct compose_fn_data *rv = malloc(sizeof *rv);
        rv->fn = fn;
        rv->a = a;
        rv->b = b;
        return &rv->fn;
    }
    
    predicate_t *and(predicate_t *a, predicate_t *b) { return compose(and_fn, a, b); }
    predicate_t *or(predicate_t *a, predicate_t *b) { return compose(or_fn, a, b); }
    

    On minor annoyance with this is that for simple functions, you need to define an additional one-word data structure to hold the function pointer, just to indirect through it. You usually do this with a global next to the function definition:

    int isEven_fn(void *data, int x) { (void)data; return x % 2 == 0; }
    predicate_t isEven = isEven_fn;
    int isOdd_fn(void *data, int x) { return !isEven_fn(data, x); }
    predicate_t isOdd = isOdd_fn;
    int isPos_fn(void *data, int x) { (void)data; return x > 0; }
    predicate_t isPos = isPos_fn;
    int isNeg_fn(void *data, int x) { (void)data; return x < 0; }
    predicate_t isNeg = isNeg_fn;
    

    Now you can do things like:

    printf("Even: %d\n", count(xs, len, &isEven));
    printf("Odd & Neg: %d", count(xs, len, and(&isNeg, &isOdd)));
    printf("%d", count(xs, len, or(and(&isNeg, &isOdd), &isEven)));
    

    though the latter do leak memory.