Come concatenare due volte con il preprocessore C ed espandere una macro come in "arg ## _ ## MACRO"?


152

Sto cercando di scrivere un programma in cui i nomi di alcune funzioni dipendono dal valore di una determinata variabile macro con una macro come questa:

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

int NAME(some_function)(int a);

Sfortunatamente, la macro lo NAME()trasforma in

int some_function_VARIABLE(int a);

piuttosto che

int some_function_3(int a);

quindi questo è chiaramente il modo sbagliato di farlo. Fortunatamente, il numero di diversi possibili valori per VARIABLE è piccolo, quindi posso semplicemente fare un #if VARIABLE == nelenco di tutti i casi separatamente, ma mi chiedevo se esiste un modo intelligente per farlo.


3
Sei sicuro di non voler utilizzare invece i puntatori a funzione?
György Andrasek,

8
@Jurily - I puntatori a funzione funzionano in fase di esecuzione, il preprocessore funziona al momento della compilazione (prima). C'è una differenza, anche se entrambi possono essere utilizzati per la stessa attività.
Chris Lutz,

1
Il punto è che ciò che viene utilizzato è una libreria di geometria computazionale veloce ... che è cablata per una certa dimensione. Tuttavia, a volte qualcuno vorrebbe essere in grado di usarlo con alcune dimensioni diverse (diciamo, 2 e 3) e quindi si avrebbe bisogno di un modo semplice per generare codice con funzione dipendente dalla dimensione e nomi di tipo. Inoltre, il codice è scritto in ANSI C, quindi la roba funky C ++ con modelli e specializzazione non è applicabile qui.
JJ.

2
Votare per riaprire perché questa domanda è specifica sull'espansione macro ricorsiva e stackoverflow.com/questions/216875/using-in-macros è un generico "a cosa serve". Il titolo di questa domanda dovrebbe essere reso più preciso.
Ciro Santilli 31 冠状 病 六四 事件 法轮功

Vorrei che questo esempio fosse stato ridotto al minimo: lo stesso accade su #define A 0 \n #define M a ## A: avere due ##non è la chiave.
Ciro Santilli 21 冠状 病 六四 事件 法轮功

Risposte:


223

Preprocessore standard C.

$ cat xx.c
#define VARIABLE 3
#define PASTER(x,y) x ## _ ## y
#define EVALUATOR(x,y)  PASTER(x,y)
#define NAME(fun) EVALUATOR(fun, VARIABLE)

extern void NAME(mine)(char *x);
$ gcc -E xx.c
# 1 "xx.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "xx.c"





extern void mine_3(char *x);
$

Due livelli di indiretta

In un commento ad un'altra risposta, Cade Roux ha chiesto perché questo abbia bisogno di due livelli di riferimento indiretto. La risposta impercettibile è perché è così che lo standard richiede che funzioni; tendi a scoprire che hai bisogno del trucco equivalente anche con l'operatore di stringere.

La sezione 6.10.3 della norma C99 riguarda la "sostituzione di macro" e la 6.10.3.1 riguarda la "sostituzione di argomenti".

Dopo aver identificato gli argomenti per l'invocazione di una macro simile a una funzione, ha luogo la sostituzione degli argomenti. Un parametro nell'elenco sostituzione, salvo preceduta da una #o ##pre-elaborazione gettone o seguita da una ##preelaborazione gettone (vedi sotto), è sostituito dal parametro corrispondente dopo tutte le macro in essa contenute sono state espanse. Prima di essere sostituito, i token di preelaborazione di ogni argomento sono completamente sostituiti da macro come se formassero il resto del file di preelaborazione; non sono disponibili altri token di preelaborazione.

Nell'invocazione NAME(mine), l'argomento è "mio"; è completamente esteso a "mio"; viene quindi sostituito nella stringa di sostituzione:

EVALUATOR(mine, VARIABLE)

Ora viene scoperto il macro VALUTATORE e gli argomenti sono isolati come "mio" e "VARIABILE"; quest'ultimo viene quindi completamente espanso a "3" e sostituito nella stringa di sostituzione:

PASTER(mine, 3)

Il funzionamento di questo è coperto da altre regole (6.10.3.3 'L'operatore ##'):

Se, nell'elenco di sostituzione di una macro simile a una funzione, un parametro viene immediatamente preceduto o seguito da un ##token di preelaborazione, il parametro viene sostituito dalla sequenza di token di preelaborazione dell'argomento corrispondente; [...]

Per invocazioni di macro sia simili a oggetti sia simili a funzioni, prima che l'elenco di sostituzione venga riesaminato per la sostituzione di più nomi di macro, ogni istanza di un ##token di preelaborazione nell'elenco di sostituzione (non da un argomento) viene eliminata e il token di preelaborazione precedente viene concatenato con il seguente token di preelaborazione.

Quindi, l'elenco di sostituzione contiene xseguito ##e ##seguito anche da y; quindi abbiamo:

mine ## _ ## 3

ed eliminando i ##token e concatenando i token su entrambi i lati combina "mio" con "_" e "3" per produrre:

mine_3

Questo è il risultato desiderato.


Se guardiamo alla domanda originale, il codice era (adattato per usare 'mio' invece di 'some_function'):

#define VARIABLE 3
#define NAME(fun) fun ## _ ## VARIABLE

NAME(mine)

L'argomento di NAME è chiaramente "mio" e questo è completamente ampliato.
Seguendo le regole del 6.10.3.3, troviamo:

mine ## _ ## VARIABLE

che, quando gli ##operatori vengono eliminati, esegue il mapping a:

mine_VARIABLE

esattamente come riportato nella domanda.


Preprocessore tradizionale C.

Robert Rüger chiede :

Esiste un modo per farlo con il preprocessore C tradizionale che non ha l'operatore di incollaggio token ##?

Forse, e forse no - dipende dal preprocessore. Uno dei vantaggi del preprocessore standard è che ha questa funzione che funziona in modo affidabile, mentre c'erano diverse implementazioni per i preprocessori pre-standard. Un requisito è che quando il preprocessore sostituisce un commento, non genera uno spazio come il preprocessore ANSI è tenuto a fare. Il preprocessore GCC (6.3.0) C soddisfa questo requisito; il preprocessore Clang da XCode 8.2.1 no.

Quando funziona, questo fa il lavoro ( x-paste.c):

#define VARIABLE 3
#define PASTE2(x,y) x/**/y
#define EVALUATOR(x,y) PASTE2(PASTE2(x,_),y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

Nota che non c'è uno spazio tra fun,e VARIABLE- questo è importante perché, se presente, viene copiato nell'output e finisci con mine_ 3il nome, che ovviamente non è sintatticamente valido. (Ora, per favore, posso riavere i miei capelli?)

Con GCC 6.3.0 (in esecuzione cpp -traditional x-paste.c), ottengo:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_3(char *x);

Con Clang di XCode 8.2.1, ottengo:

# 1 "x-paste.c"
# 1 "<built-in>" 1
# 1 "<built-in>" 3
# 329 "<built-in>" 3
# 1 "<command line>" 1
# 1 "<built-in>" 2
# 1 "x-paste.c" 2





extern void mine _ 3(char *x);

Quegli spazi rovinano tutto. Prendo atto che entrambi i preprocessori sono corretti; diversi preprocessori pre-standard presentavano entrambi i comportamenti, il che rendeva incollare token un processo estremamente fastidioso e inaffidabile quando si provava a eseguire il port code. Lo standard con la ##notazione semplifica radicalmente questo.

Potrebbero esserci altri modi per farlo. Tuttavia, questo non funziona:

#define VARIABLE 3
#define PASTER(x,y) x/**/_/**/y
#define EVALUATOR(x,y) PASTER(x,y)
#define NAME(fun) EVALUATOR(fun,VARIABLE)

extern void NAME(mine)(char *x);

GCC genera:

# 1 "x-paste.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Chiudi, ma senza dadi. YMMV, ovviamente, a seconda del preprocessore pre-standard che stai utilizzando. Francamente, se sei bloccato con un preprocessore che non collabora, probabilmente sarebbe più semplice disporre di un preprocessore C standard al posto di quello pre-standard (di solito c'è un modo per configurare il compilatore in modo appropriato) che passare molto tempo a cercare di trovare un modo per fare il lavoro.


1
Sì, questo risolve il problema. Conoscevo il trucco con due livelli di ricorsione - dovevo giocare con la stringificazione almeno una volta - ma non sapevo come farlo.
JJ.

Esiste un modo per farlo con il tradizionale preprocessore C che non ha l'operatore di incollaggio token ##?
Robert Rüger,

1
@ RobertRüger: raddoppia la lunghezza della risposta, ma ho aggiunto informazioni per la copertura cpp -traditional. Nota che non esiste una risposta definitiva, dipende dal preprocessore che hai.
Jonathan Leffler,

Grazie mille per la risposta. Questo è assolutamente fantastico! Nel frattempo ho anche trovato un'altra soluzione leggermente diversa. Vedi qui . Ha anche il problema che non funziona con clang però. Fortunatamente non è un problema per la mia applicazione ...
Robert Rüger,

32
#define VARIABLE 3
#define NAME2(fun,suffix) fun ## _ ## suffix
#define NAME1(fun,suffix) NAME2(fun,suffix)
#define NAME(fun) NAME1(fun,VARIABLE)

int NAME(some_function)(int a);

Onestamente, non vuoi sapere perché funziona. Se sai perché funziona, diventerai quel tipo al lavoro che conosce questo genere di cose e tutti verranno a farti delle domande. =)

Modifica: se vuoi davvero sapere perché funziona, posterò felicemente una spiegazione, supponendo che nessuno mi picchi.


Potresti spiegare perché ha bisogno di due livelli di riferimento indiretto. Ho avuto una risposta con un livello di reindirizzamento ma ho eliminato la risposta perché dovevo installare C ++ nel mio Visual Studio e quindi non avrebbe funzionato.
Cade Roux,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.