C/C++ Arena

Step 5 of 6

Generic code with void *

How can qsort work with any element type when C has no generics? It treats the array as raw bytes:

To do that byte arithmetic you convert to char *, because char is exactly one byte and C doesn't allow arithmetic on void * in standard code.

#include <stdio.h>
#include <stddef.h>

size_t count_matching(const void *base, size_t n, size_t size, int (*pred)(const void *)) {
    const char *bytes = base;
    size_t count = 0;
    for (size_t i = 0; i < n; i++) {
        if (pred(bytes + i * size)) {
            count++;
        }
    }
    return count;
}

static int is_negative_double(const void *p) {
    return *(const double *)p < 0;
}

static int starts_with_a(const void *p) {
    const char *const *s = p;
    return (*s)[0] == 'a';
}

int main(void) {
    double d[] = {1.5, -2.0, -0.5, 3.0};
    const char *words[] = {"apple", "pear", "avocado"};
    printf("%zu %zu\n", count_matching(d, 4, sizeof d[0], is_negative_double),
           count_matching(words, 3, sizeof words[0], starts_with_a));
    return 0;
}
2 2

Who knows the type?

The generic function only moves bytes around. The callback knows the real type and casts the const void * back: *(const double *)p. For the array of strings, each element is a const char *, so the callback receives a pointer to a pointer and must dereference twice. Getting this cast wrong compiles fine and reads garbage, which is the price of void * genericity. C++ templates, later in the course, solve the same problem with full type checking.

Returning a pointer to an element

A generic search returns a const void * pointing into the array (or NULL). The caller casts it back to the element type.

Your turn: write find_first, which returns a pointer to the first element for which pred returns nonzero, or NULL.

Previous: Sorting with qsort Next: Challenge: an event bus