Eroxl's Notes
Generic Programming

Generic programming is a programming paradigm where algorithms and data structures are written in terms of types that are specified later, allowing the same code to work with different data types. Generic code increases code reuse and reduces duplication.

In C Using Function Pointers

C achieves generic programming through function pointers and void* pointers, allowing functions to operate on any data type:

Generic Iteration

void map(int (*fn)(int, int), int n, int *s0, int *s1, int *dest) {
    for (int i = 0; i < n; i++)
        dest[i] = fn(s0[i], s1[i]);
}

int add(int a, int b) { return a + b; }
int mul(int a, int b) { return a * b; }

map(add, 3, array1, array2, result);  // Add arrays
map(mul, 3, array1, array2, result);  // Multiply arrays

Generic Sorting

The C standard library's qsort function is fully generic:

void qsort(void *base, size_t nel, size_t width,
           int (*compare)(const void*, const void*));

Usage examples:

// Sort integers
int cmpInt(const void *a, const void *b) {
    int ia = *(int*)a;
    int ib = *(int*)b;
    return ia - ib;
}
qsort(intArray, n, sizeof(int), cmpInt);

// Sort strings
int cmpString(const void *a, const void *b) {
    char *sa = *(char**)a;
    char *sb = *(char**)b;
    return strcmp(sa, sb);
}
qsort(strArray, n, sizeof(char*), cmpString);

Using typedef for Clarity

The typedef statement creates type aliases that make generic code more readable:

typedef void* element_t;

int partition(element_t* array, int left, int right, 
              int (*cmp)(element_t, element_t)) {
    element_t pivotValue, t;
    // ... implementation
}

Without typedef, this would use confusing void** and void* throughout.

Generic Data Structure Operations

Function pointers enable generic operations on data structures:

struct node {
    int key;
    int value;
    struct node *next;
};

void traverse_list(struct node *list, void (*fn)(int, int)) {
    while (list) {
        fn(list->key, list->value);
        list = list->next;
    }
}

void print_element(int key, int value) {
    printf("%d: %d\n", key, value);
}

void sum_values(int key, int value) {
    total += value;
}

traverse_list(myList, print_element);  // Print all
traverse_list(myList, sum_values);     // Sum all

Trade-offs

Advantages

  • Code reuse: Write once, use with many types
  • Maintainability: Changes to algorithm affect all uses
  • Abstraction: Separates algorithm from data types

Disadvantages

  • Type safety: void* loses compile-time type checking
  • Complexity: More difficult to read and understand
  • Debugging: Harder to trace through function pointer calls
  • Performance: Indirect calls have slight overhead

In Other Languages

Other languages provide better generic programming support:

  • C++: Templates with compile-time type checking
  • Java: Generics with type erasure
  • Racket: Higher-order functions like map, foldl, filter

Relationship to Polymorphism

Generic programming is related to polymorphism but distinct:

  • Generic programming: Same code for different types (parametric)
  • Polymorphism: Different code for different types (subtype-based)

Both use function pointers in C but serve different purposes.