Generic code

C has no templates. A function that works on any type takes a void * and the size of the object, and a macro or a _Generic selection supplies the part that depends on the type. The compiler cannot check the type inside the function, so the size and the comparator are the caller's promise.

void * and a size

A void * converts to and from any object pointer without a cast, and memcpy moves a number of bytes. Together they swap values of any type whose size is passed in. The temporary buffer is a byte array, because the function does not know the element type.

#include <stdio.h> #include <string.h> static void swap(void *a, void *b, size_t size) { unsigned char temporary[64]; memcpy(temporary, a, size); memcpy(a, b, size); memcpy(b, temporary, size); } int main(void) { int x = 1; int y = 2; double p = 1.5; double q = 2.5; swap(&x, &y, sizeof x); swap(&p, &q, sizeof p); printf("ints: %d %d\n", x, y); printf("doubles: %.1f %.1f\n", p, q); return 0; }

gcc -Wall -Wextra swap.c -o swap
./swap

ints: 2 1
doubles: 2.5 1.5

The fixed 64-byte temporary is enough for the types here; a real generic function would allocate it or take a maximum size. Passing the wrong size is undefined behaviour, and the compiler cannot see the mistake because the call has no type information.

_Generic selects a type

_Generic chooses an expression by the type of its controlling argument. It is evaluated at compile time, so the selected string is just a constant, and the default association catches every type that has no explicit one. The macro below prints an int, a double or a string with the right conversion.

#include <stdio.h> #define SHOW(x) printf(_Generic((x), \ int: "%d\n", \ double: "%.2f\n", \ char *: "%s\n"), (x)) int main(void) { SHOW(42); SHOW(3.5); SHOW("text"); return 0; }

gcc -Wall -Wextra show.c -o show
./show

42
3.50
text

The controlling expression is not evaluated, only its type is used, so SHOW(i++) would not increment twice. The association list is fixed at compile time; a type that is not listed and no default is an error, which the last section shows.

A generic search

The search below walks an array of unknown element type: it advances by size bytes per element and calls a comparator for each one. This is the shape of bsearch from the standard library, which lesson 54 uses directly. The comparator is the same callback pattern as qsort in lesson 29.

#include <stdio.h> static const void *find(const void *base, size_t count, size_t size, const void *key, int (*compare)(const void *, const void *)) { const unsigned char *bytes = base; for (size_t i = 0; i < count; i++) { if (compare(bytes + i * size, key) == 0) { return bytes + i * size; } } return NULL; } static int compare_int(const void *left, const void *right) { int a = *(const int *) left; int b = *(const int *) right; return (a > b) - (a < b); } int main(void) { int values[5] = {10, 20, 30, 40, 50}; int key = 30; const int *found = find(values, 5, sizeof values[0], &key, compare_int); if (found == NULL) { printf("not found\n"); } else { printf("found at index %td\n", found - values); } return 0; }

gcc -Wall -Wextra find.c -o find
./find

found at index 2

The byte pointer does the arithmetic, and the result converts back to const int * because the function promises to return a pointer into the same array. The subtraction is done in int elements, which is why the index is 2 and not 8.

Type-safe macros

A macro can be generic without a void * when the array type is visible at the call. ARRAY_LEN divides the size of the array by the size of one element, and it fails to compile if the argument is a pointer, which is the safety net the sizeof version lacks.

#include <stdio.h> #define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0]) int main(void) { int values[7] = {0}; for (size_t i = 0; i < ARRAY_LEN(values); i++) { printf("%zu ", i); } printf("\nlength: %zu\n", ARRAY_LEN(values)); return 0; }

gcc -Wall -Wextra len.c -o len
./len

0 1 2 3 4 5 6
length: 7

Called with a pointer, the macro would divide the size of a pointer by the size of an int and report a plausible but wrong number. The protection is that a pointer has no [0] element to take the size of, so the expression does not compile.

Wrong size, no association

sizeof on a pointer gives the size of the pointer, not of the array it points at. A copy function that uses sizeof source therefore copies eight bytes instead of the string; GCC recognises the pattern and warns:

#include <stdio.h> #include <string.h> static void copy(char *target, const char *source) { memcpy(target, source, sizeof source); } int main(void) { char target[16] = {0}; copy(target, "hello"); printf("%s\n", target); return 0; }

gcc -Wall -Wextra badsize.c -o badsize

badsize.c: In function 'copy':
badsize.c:6:35: warning: argument to 'sizeof' in 'memcpy' call is the same expression as the source; did you mean to provide an explicit length? [-Wsizeof-pointer-memaccess]
6 | memcpy(target, source, sizeof source);
| ^~~~~~

The program is not run here: it copies the pointer's eight bytes, which happens to include the first eight characters of the literal but is not the copy that was intended. The fix is to pass the capacity like the other examples in this lesson.

A _Generic without a matching association and without a default is a compile error. The message names the controlling type and the macro expansion line:

#include <stdio.h> #define KIND(x) _Generic((x), int: 1, double: 2) int main(void) { printf("%d\n", KIND("text")); return 0; }

gcc -Wall -Wextra nomatch.c -o nomatch

nomatch.c: In function 'main':
nomatch.c:3:26: error: '_Generic' selector of type 'char *' is not compatible with any association
3 | #define KIND(x) _Generic((x), int: 1, double: 2)
| ^
nomatch.c:7:20: note: in expansion of macro 'KIND'
7 | printf("%d\n", KIND("text"));
| ^~~~

A string literal has type char * here, not the array type it would have in sizeof, which is why the association for char * is the one to add. Adding a default is the alternative when every type should be accepted.

Exercises

  1. Extend SHOW with associations for long and float and test both.
  2. Use the generic search on an array of doubles with a comparator that compares with an epsilon.
  3. Write a generic count_if that takes a byte array, an element size and a callback returning a boolean for one element.

Questions

A new set of questions in a new order every time. Choose one answer for each, then check.

  1. What must the caller supply to a void * generic function?

    Answer

    The element size and a matching comparator

    The function sees only bytes, so the size and the comparator are the promises that make the operation correct.

  2. When is a _Generic association selected?

    Answer

    At compile time, from the controlling type

    _Generic chooses by type during compilation and does not evaluate the controlling expression.

  3. How does a generic search move through an array of unknown type?

    Answer

    It advances by the element size in bytes

    A byte pointer plus i * size reaches each element, and the comparator interprets the bytes.

  4. Why is ARRAY_LEN safer than a function taking a pointer?

    Answer

    It fails to compile when given a pointer

    The macro uses sizeof on the array itself, and a pointer has no [0] element to size, so misuse does not compile.

  5. What does this program print? #include <stdio.h> #include <string.h> static void swap(void *a, void *b, size_t n){ unsigned char t[16]; memcpy(t,a,n); memcpy(a,b,n); memcpy(b,t,n); } int main(void){ int x=1,y=2; double p=1.5,q=2.5; swap(&x,&y,sizeof x); swap(&p,&q,sizeof p); printf("ints: %d %d\ndoubles: %.1f %.1f\n",x,y,p,q); return 0; }

    Answer

    ints: 2 1 / doubles: 2.5 1.5

    Both pairs are swapped by the same byte-copy function, so each pair reverses its order.

  6. What does the SHOW macro print for 42, 3.5 and "text"?

    Answer

    42, 3.50 and text

    The int association uses %d, the double one uses %.2f, and the string one uses %s.

  7. What does the generic search print for the key 30?

    Answer

    found at index 2

    The element at index 2 holds 30, and the pointer subtraction counts elements rather than bytes.

  8. What does this program print on its second line? #include <stdio.h> #define ARRAY_LEN(a) (sizeof (a) / sizeof (a)[0]) int main(void){ int values[7]={0}; printf("%zu\n", ARRAY_LEN(values)); return 0; }

    Answer

    7

    Seven ints occupy twenty-eight bytes, and dividing by the size of one int gives seven elements.

  9. What does GCC warn about when memcpy uses sizeof source on a pointer?

    Answer

    That the size is the pointer, not the array

    sizeof on a pointer gives eight bytes, and GCC recognises the suspicious pattern with -Wsizeof-pointer-memaccess.

  10. What happens when _Generic has no matching association and no default?

    Answer

    The compiler reports an incompatible selector

    The type of the controlling expression must match an association or a default, so the file does not compile.

The code on this page was run with gcc (Alpine 15.2.0) 15.2.0 on Alpine Linux 3.24.3 (x86_64) in a Docker container.

Article author: Arthur Isaev