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

152

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

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

int NAME(some_function)(int a);

Desafortunadamente, la macro NAME()convierte eso 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 diferentes valores posibles para VARIABLE es pequeño, así que simplemente puedo hacer #if VARIABLE == ny enumerar todos los casos por separado, pero me preguntaba si hay una manera inteligente de hacerlo.

JJ
fuente
3
¿Estás seguro de que no quieres utilizar punteros de función en su lugar?
György Andrasek
8
@Jurily: los punteros de función funcionan en tiempo de ejecución, el preprocesador funciona en (antes) del tiempo de compilación. Hay una diferencia, incluso si ambos se pueden usar para la misma tarea.
Chris Lutz
1
El punto es que lo que se usa es una biblioteca de geometría computacional rápida ... que está cableada para una determinada dimensión. Sin embargo, a veces alguien querría poder usarlo con algunas dimensiones diferentes (por ejemplo, 2 y 3) y, por lo tanto, uno necesitaría una forma fácil de generar código con funciones y nombres de tipo dependientes de la dimensión. Además, el código está escrito en ANSI C, por lo que el funky material de C ++ con plantillas y especialización no es aplicable aquí.
JJ.
2
Votar para volver a abrir porque esta pregunta es específica sobre la expansión recursiva de macros y stackoverflow.com/questions/216875/using-in-macros es un genérico "para qué sirve". El título de esta pregunta debería hacerse más preciso.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
Desearía que este ejemplo se hubiera minimizado: lo mismo sucede #define A 0 \n #define M a ## A: tener dos ##no es la clave.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

Respuestas:

223

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 "<built-in>"
# 1 "<command-line>"
# 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 impertinente es porque así es como el estándar requiere que funcione; tiende a encontrar que necesita el truco equivalente con el operador de encordado también.

La Sección 6.10.3 de la norma C99 cubre el 'reemplazo de macros' y 6.10.3.1 cubre la 'sustitución de argumentos'.

Una vez que se han identificado los argumentos para la invocación de una macro similar a una función, tiene lugar la sustitución de argumentos. Un parámetro de la lista de sustitución, a menos precedida por una #o ##preprocesamiento ficha o seguido de un ##procesamiento previo de contadores (véase abajo), se sustituye por el argumento correspondiente después de todos macros contenidas en el mismo se han ampliado. Antes de ser sustituidos, los tokens de preprocesamiento de cada argumento se reemplazan completamente por macro como si formaran el resto 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 EVALUATOR, y los argumentos se aíslan como 'mío' y 'VARIABLE'; este último se expande completamente a '3' y se sustituye en 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 similar a una función, un parámetro está precedido o seguido inmediatamente por un ##token de preprocesamiento, el parámetro se reemplaza por la secuencia de tokens de preprocesamiento del argumento correspondiente; [...]

Para las invocaciones de macro tanto de objeto como de función, antes de volver a examinar la lista de reemplazo para reemplazar más nombres de macro, cada instancia de un ##token de preprocesamiento en la lista de reemplazo (no de un argumento) se elimina y el token de preprocesamiento anterior se concatena con el siguiente token de preprocesamiento.

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

mine ## _ ## 3

y eliminar las ##fichas y concatenar las fichas en ambos lados combina 'mina' con '_' y '3' para obtener:

mine_3

Este es el resultado deseado.


Si miramos la pregunta original, el código fue (adaptado para usar 'mine' en lugar de 'some_function'):

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

NAME(mine)

El argumento para NAME es claramente 'mío' y eso se expande completamente.
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 informa en la pregunta.


Preprocesador C tradicional

Robert Rüger pregunta :

¿Hay alguna manera de hacer esto con el preprocesador C tradicional que no tiene el operador de pegado de tokens ##?

Tal vez, y tal vez no, depende del preprocesador. Una de las ventajas del preprocesador estándar es que tiene esta instalación que funciona de manera confiable, mientras que hubo diferentes implementaciones para preprocesadores pre-estándar. Un requisito es que cuando el preprocesador reemplaza un comentario, no genera un espacio como debe hacer el preprocesador ANSI. El preprocesador GCC (6.3.0) C cumple con este requisito; el preprocesador Clang de XCode 8.2.1 no lo hace.

Cuando funciona, esto 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 un espacio entre fun,y VARIABLE, eso es importante porque, si está presente, se copia en la salida, y termina con mine_ 3el nombre, que no es sintácticamente válido, por supuesto. (Ahora, por favor, ¿puedo tener mi cabello hacia atrás?)

Con GCC 6.3.0 (en ejecución cpp -traditional x-paste.c), obtengo:

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





extern void mine_3(char *x);

Con Clang de XCode 8.2.1, obtengo:

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

Esos espacios lo estropean todo. Observo que ambos preprocesadores son correctos; diferentes preprocesadores pre-estándar exhibieron ambos comportamientos, lo que hizo que el pegado de tokens fuera un proceso extremadamente molesto y poco confiable al intentar portar código. 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 "<built-in>"
# 1 "<command-line>"
# 1 "x-paste.c"





extern void mine_VARIABLE(char *x);

Cerca, pero sin dados. YMMV, por supuesto, dependiendo del preprocesador pre-estándar que esté usando. Francamente, si está atascado con un preprocesador que no está cooperando, probablemente sería más sencillo organizar el uso de un preprocesador C estándar en lugar del preprocesador (generalmente hay una manera de configurar el compilador de manera adecuada) que Pasar mucho tiempo tratando de encontrar una manera de hacer el trabajo.

Jonathan Leffler
fuente
1
Sí, esto resuelve el problema. Conocía el truco con dos niveles de recursión: tuve que jugar con la stringificación al menos una vez, pero no sabía cómo hacerlo.
JJ.
¿Hay alguna forma de hacerlo con el preprocesador C tradicional que no tiene el operador de pegado de tokens ##?
Robert Rüger
1
@ RobertRüger: duplica la longitud de la respuesta, pero he agregado información para cubrir cpp -traditional. Tenga en cuenta que no hay una respuesta definitiva, depende del preprocesador que tenga.
Jonathan Leffler
Muchas gracias por la respuesta. ¡Esto es totalmente genial! Mientras tanto, también encontré otra solución ligeramente diferente. Ver aquí . Sin embargo, también tiene el problema de que no funciona con el sonido metálico. Afortunadamente, eso no es un problema para mi aplicación ...
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);

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

Editar: si realmente quieres saber por qué funciona, felizmente publicaré una explicación, suponiendo que nadie me gane.

Stephen Canon
fuente
¿Podría explicar por qué necesita dos niveles de indirección? Tenía una respuesta con un nivel de redireccionamiento, pero eliminé la respuesta porque tenía que instalar C ++ en mi Visual Studio y luego no funcionaría.
Cade Roux