¿Cómo concatenar dos veces con el preprocesador C y expandir una macro como en “arg ## _ ## MACRO”?

Estoy tratando de escribir un progtwig donde los nombres de algunas funciones dependen del valor de una determinada macrovariable con una macro como esta:

#define VARIABLE 3 #define NAME(fun) fun ## _ ## VARIABLE int NAME(some_function)(int a); 

Desafortunadamente, la macro NAME() convierte en

 int some_function_VARIABLE(int a); 

más bien que

 int some_function_3(int a); 

así que esta es claramente la forma incorrecta de hacerlo. Afortunadamente, el número de valores posibles diferentes para VARIABLE es pequeño, así que simplemente puedo hacer un #if VARIABLE == n y enumerar todos los casos por separado, pero me preguntaba si existe una forma inteligente de hacerlo.

Preprocesador estándar 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 "" # 1 "" # 1 "xx.c" extern void mine_3(char *x); $ 

Dos niveles de indirección

En un comentario a otra respuesta, Cade Roux preguntó por qué esto necesita dos niveles de indirección. La respuesta frívola es porque así es como el estándar requiere que funcione; Tiendes a pensar que necesitas el truco equivalente con el operador de torceado también.

La sección 6.10.3 de la norma C99 cubre ‘macro-reemplazo’, y 6.10.3.1 cubre ‘sustitución de argumento’.

Una vez identificados los argumentos para la invocación de una macro de tipo función, se produce la sustitución de los argumentos. Un parámetro en la lista de reemplazo, a menos que sea precedido por un token de preprocesamiento # o ## o seguido de un token de preprocesamiento ## (consulte a continuación), se reemplaza por el argumento correspondiente después de que se hayan expandido todas las macros contenidas en él. Antes de ser sustituidos, los tokens de preprocesamiento de cada argumento se reemplazan completamente macro como si formaran el rest del archivo de preprocesamiento; no hay otros tokens de preprocesamiento disponibles.

En la invocación NAME(mine) , el argumento es ‘mío’; está completamente expandido a ‘mío’; luego se sustituye en la cadena de reemplazo:

 EVALUATOR(mine, VARIABLE) 

Ahora se descubre el macro EVALUADOR y los argumentos se aíslan como ‘mío’ y ‘VARIABLE’; este último se expande por completo a ‘3’ y se sustituye por la cadena de reemplazo:

 PASTER(mine, 3) 

El funcionamiento de esto está cubierto por otras reglas (6.10.3.3 ‘El operador ##’):

Si, en la lista de reemplazo de una macro de tipo función, un parámetro es precedido o seguido inmediatamente por un token de preprocesamiento ## , el parámetro se reemplaza por la secuencia de token de preprocesamiento del argumento correspondiente; […]

Para las invocaciones de macros similares a objetos y funciones, antes de volver a examinar la lista de reemplazo para reemplazar más nombres de macro, se elimina cada instancia de un token de preprocesamiento ## en la lista de reemplazo (no de un argumento) y el token de preproceso anterior está concatenado con el siguiente token de preprocesamiento.

Entonces, la lista de reemplazo contiene x seguido de ## y también ## seguido de y ; entonces tenemos:

 mine ## _ ## 3 

y eliminar los ## tokens y concatenar los tokens en ambos lados combina ‘mío’ con ‘_’ y ‘3’ para obtener:

 mine_3 

Este es el resultado deseado.


Si miramos la pregunta original, el código fue (adaptado para usar ‘mío’ en lugar de ‘alguna_función’):

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

El argumento para NAME es claramente ‘mío’ y está completamente expandido.
Siguiendo las reglas de 6.10.3.3, encontramos:

 mine ## _ ## VARIABLE 

que, cuando se eliminan los ## operadores, se asigna a:

 mine_VARIABLE 

exactamente como se informó en la pregunta.


Preprocesador tradicional C

Robert Rüger pregunta :

¿Hay alguna forma de hacerlo con el preprocesador tradicional C que no tiene el operador de pegado de fichas ## ?

Tal vez, y tal vez no, depende del preprocesador. Una de las ventajas del preprocesador estándar es que tiene esta facilidad que funciona de manera confiable, mientras que hubo diferentes implementaciones para preprocesadores preestandarizados. Un requisito es que cuando el preprocesador reemplaza un comentario, no genere un espacio como se requiere que haga el preprocesador ANSI. El preprocesador GCC (6.3.0) C cumple este requisito; el preprocesador Clang de XCode 8.2.1 no lo hace.

Cuando funciona, hace el trabajo ( 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); 

Tenga en cuenta que no hay espacio entre fun, y VARIABLE , eso es importante porque si está presente, se copia a la salida y termina con mine_ 3 como el nombre, que no es sintácticamente válido, por supuesto. (Ahora, por favor ¿puedo recuperar mi cabello?)

Con GCC 6.3.0 (ejecutando cpp -traditional x-paste.c ), obtengo:

 # 1 "x-paste.c" # 1 "" # 1 "" # 1 "x-paste.c" extern void mine_3(char *x); 

Con Clang desde XCode 8.2.1, obtengo:

 # 1 "x-paste.c" # 1 "" 1 # 1 "" 3 # 329 "" 3 # 1 "" 1 # 1 "" 2 # 1 "x-paste.c" 2 extern void mine _ 3(char *x); 

Esos espacios lo estropean todo. Noto que ambos preprocesadores son correctos; diferentes preprocesadores preestablecidos exhibieron ambos comportamientos, lo que hizo que pegar token en un proceso extremadamente molesto y poco confiable al intentar codificar el puerto. El estándar con la notación ## simplifica radicalmente eso.

Puede haber otras formas de hacer esto. Sin embargo, esto no funciona:

 #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 "" # 1 "" # 1 "x-paste.c" extern void mine_VARIABLE(char *x); 

Cerca, pero no dados. YMMV, por supuesto, según el preprocesador preestablecido que esté utilizando. Francamente, si está atascado con un preprocesador que no coopera, probablemente sería más simple organizar el uso de un preprocesador C estándar en lugar del pre-estándar (generalmente hay una manera de configurar el comstackdor apropiadamente) que Pasar mucho tiempo tratando de encontrar la manera de hacer el trabajo.

 #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); 

Honestamente, no quieres saber por qué funciona esto. Si sabe por qué funciona, se convertirá en ese tipo en el trabajo que sabe este tipo de cosas, y todos vendrán a hacerle preguntas. =)

Edición: si realmente quieres saber por qué funciona, con mucho gusto publicaré una explicación, suponiendo que nadie me gana.