P99: Un aiuto per la macro programming - STB1019/SkullOfSummer GitHub Wiki
Introduzione
La macro programming può essere vista come un modo per effettuare "code generation", ossia programmi che creano codice sorgente, anziché eseguibile. Per esempio, la macro:
#define _CAT(x, y) x ## y
#define CAT(x,y) _CAT(x, y)
#define GENERATE_LIST_CELL(type) \
struct CAT(type, _cell) { \
type payload; \
struct CAT(type, _cell)* next; \
}
permette di generate strutture che possono essere utilizzate in una "forward list":
GENERATE_LIST_CELL(int)
struct int_cell {
int payload;
struct int_cell* next;
}
GENERATE_LIST_CELL(long)
struct long_cell {
long payload;
struct long_cell* next;
}
La programmazione macro è potente, ma a volte mentre la si usa si vorrebbe avere a disposizione delle funzioni che normalmente non si sa. Un esempio è ottenere il numero di argomenti all'interno di una macro variadica:
FOO(1,2,3,4) # return 4
FOO(a, b, c, d, e) # return 5
FOO() # return 0
P99 è un progetto opensource essenziale se si vuole utilizzare al massimo la macro programming. Il progetto è fatto completamente di header ed è un eccellente modo per imparare un uso spintissimo di macro programming. Lo sviluppatore però può semplicemente sfruttare il progetto.
P99
P99 è un progetto disponibile:
- su github
- la cui documentazione è disponibile presso documentazione p99
P99 è un progetto enorme, ma noi discuteremo soltanto di alcuni funzioni particolarmente utili.
Gestione delle macro variadiche
Uno dei principali usi di P99 è quello di aiutare il programmatore mentre scrive in macro programming.
Tra le funzioni importanti possiamo citare P99_NARG
, P99_IF_EQ
, P99_FOR
P99_NARG(...)
è una macro che ti permette di generare il numero di argomenti variadici passati. Per esempio:
P99_NARG(10, 20, 30, 40) //4
P99_NARGS() // 0
P99_FOR
Sicuramente una tra le macro più utili in tutto P99. La macro consente di generare codice sorgente a partire da una sequenza di numeri. Il suo uso è il seguente:
#define _REDUCE(NAME, I, REC, RES) REC + RES
#define _MAP(NAME, X, I) X
P99_FOR(A, 4, _REDUCE, _MAP, 5, 6, 7, 9);
--> 5 + 6 + 7 +9
Come funziona? Possiamo dividere P99_FOR
in 3 parti:
P99_FOR(
//prima parte
A, 4,
//seconda parte
_REDUCE, _MAP,
// terza parte
5, 6, 7, 9
);
- la prima parte contiene una variabile (in questo caso
A
) che verrà inviata a tutte le chiamate di_REDUCE
e_MAP
. Pensa a questa variabile come una sorta di contesto accessibile ad ogni chiamata; In questa parte vengono anche dati in ingresso il numero di elemento su cui ciclare; - la seconda parte contiene le 2 macro,
_MAP
e_REDUCE
:_MAP
contiene il codice da generate per ogni elemento della lista. Reduce invece è il codice da generare quando si vuole unire quello finora generato con un nuovo elemento di_MAP
; - la terza parte contiene invece la sequenza di elementi su cui ciclare.
Dall'esempio di prima:
#define _REDUCE(NAME, I, REC, RES) REC + RES
#define _MAP(NAME, X, I) (X)
P99_FOR(PIPPO, 4, _REDUCE, _MAP, 5, 6, 7, 9);
Ecco le chiamate di _MAP
:
NAME | X | I | _MAP(NAME, X, I) |
---|---|---|---|
PIPPO | 5 | 0 | (5) |
PIPPO | 6 | 1 | (6) |
PIPPO | 7 | 2 | (7) |
PIPPO | 8 | 3 | (8) |
Non vi piace di P99_FOR
richieda il numero di argomenti? Nessun problema! Usiamo P99
per risolvere questo piccolo inghippo:
#define FOREACH(CONTEXT, _MAP, _REDUCE, ...) P99_FOR(CONTEXT, P99_NARG(__VA_ARGS__), _REDUCE, _MAP, ## __VA_ARGS__)
P99_IF_EQ
è una macro che ti permette di testare se 2 parole (non valori, parole: il preprocessore non ha accesso ad alcuna struttura dati né valori del compilatore) sono uguali. Per esempio
P99_IF_EQ(0, X)(hello)(world)
Se #define X 0
allora la macro verrà espansa nel contenuto della seconda parentesi:
P99_IF_EQ(0, X)(hello)(world) --> hello
Se invece X
viene espanso in qualcosa d'altro, allora la macro verrà espansa nel contenuto della terza parentesi:
P99_IF_EQ(0, X)(hello)(world) --> world
Cosa può fare un uso spinto di P99?
P99
può essere incredibilmente utile per:
- implementare un framework per la reflection in C (notare che di base C non possiede modi per avere la reflection);
- simulare deallocazione automatica dei puntatori;