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:
baseis avoid *: the address of the first element, type unknown.sizesays how many bytes one element takes.- Element
itherefore starts at byte offseti * size.
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.