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!!!