Posted on Jan 11, 2025
Language C11, features #define _POSIX_C_SOURCE 200809L
. Let’s say we have a data structure:
typedef struct pill_t pill_t; struct pill_t { int N; };
we have some methods:
void pill_print (pill_t P) { dprintf(1, "pill.N = %d\n", P.N); }
we can use the data structure in two ways:
void pill_use_for_weight_loss (pill_t P) { dprintf(1, "weight loss pill.N = %d\n", P.N); } void pill_use_for_lower_blood_sugar (pill_t P) { dprintf(1, "lower blood sugar pill.N = %d\n", P.N); }
so let’s use it:
int main (void) { pill_t P1 = { .N = 123 }; pill_t P2 = { .N = 456 }; dprintf(1, "just a pill_t\n"); pill_print(P1); pill_print(P2); pill_use_for_weight_loss(P1); pill_use_for_lower_blood_sugar(P2); exit(EXIT_SUCCESS); }
it is just one type: the pill_t
.
Now let’s say we start thinking, twisting, craving for light from the Secret Fire, but there is Darkness everywhere, and it corrupts… it torments… it festers; how cool would it be if we could specify the use of the pill in its variable declaration? Like this:
int main (void) { weight_loss_pill_t P1 = { .P.N = 123 }; lower_blood_sugar_pill_t P2 = { .P.N = 456 }; dprintf(1, "subtypes of pill_t\n"); pill_print(P1); pill_print(P2); pill_use(P1); pill_use(P2); exit(EXIT_SUCCESS); }
instead of selecting the use in the name of the function, we select it in the name of the data type; we are not really changing the internal representation of a pill, we are just changing what we write to use pills. Is this new code better? More readable? Can we understand it faster when we read it again after 5 years of not seeing it?
Well… but can we actually do it? Of course:
typedef struct pill_t pill_t; typedef struct lower_blood_sugar_pill_t lower_blood_sugar_pill_t; typedef struct weight_loss_pill_t weight_loss_pill_t; struct pill_t { int N; }; struct lower_blood_sugar_pill_t { pill_t P; }; struct weight_loss_pill_t { pill_t P; }; void pill_p_print (pill_t P) { dprintf(1, "pill.N = %d\n", P.N); } void lower_blood_sugar_pill_print (lower_blood_sugar_pill_t P) { pill_p_print(P.P); } void weight_loss_pill_print (weight_loss_pill_t P) { pill_p_print(P.P); } #define pill_print(PILL) \ (_Generic((PILL), \ lower_blood_sugar_pill_t: lower_blood_sugar_pill_print, \ weight_loss_pill_t: weight_loss_pill_print)(PILL)) void pill_use_for_weight_loss (weight_loss_pill_t P) { dprintf(1, "weight loss pill.N = %d\n", P.P.N); } void pill_use_for_lower_blood_sugar (lower_blood_sugar_pill_t P) { dprintf(1, "lower blood sugar pill.N = %d\n", P.P.N); } #define pill_use(PILL) \ (_Generic((PILL), \ lower_blood_sugar_pill_t: pill_use_for_lower_blood_sugar, \ weight_loss_pill_t: pill_use_for_weight_loss)(PILL))
notice how much more code there is; but all this code is hidden in some pill.h/pill.c module, so we do not see it; we just have to write it and forget it.
I have let code of the second type enter my project’s repository; I’m torn between light and darkness; but… which one is light… which one is darkness…
Am I a Slave of Morgoth?
No… No… That cannot be!!!