Coma en macro C / C ++

Digamos que tenemos una macro como esta

#define FOO(type,name) type name 

Que podríamos usar como

 FOO(int, int_var); 

Pero no siempre tan simple como eso:

 FOO(std::map, map_var); // error: macro "FOO" passed 3 arguments, but takes just 2 

Por supuesto que podríamos hacer:

  typedef std::map map_int_int_t; FOO(map_int_int_t, map_var); // OK 

que no es muy ergonómico. Además, las incompatibilidades de tipo tienen que ser tratadas. ¿Alguna idea de cómo resolver esto con macro?

Debido a que los corchetes angulares también pueden representar (u ocurrir en) los operadores de comparación < , > , < = y >= , la macroexpansión no puede ignorar las comas dentro de los corchetes angulares, como lo hace entre paréntesis. (Esto también es un problema para corchetes y llaves, aunque normalmente se presentan como pares equilibrados). Puede encerrar el argumento entre paréntesis:

 FOO((std::map), map_var); 

El problema es que el parámetro permanece entre paréntesis dentro de la expansión de la macro, lo que impide que se lea como un tipo en la mayoría de los contextos.

Un buen truco para solucionar esto es que en C ++, puede extraer un nombre de tipo de un nombre de tipo entre paréntesis usando un tipo de función:

 template struct argument_type; template struct argument_type { typedef U type; }; #define FOO(t,name) argument_type::type name FOO((std::map), map_var); 

Debido a que los tipos de funciones de funciones ignoran los paréntesis adicionales, puede usar esta macro con o sin paréntesis donde el nombre del tipo no incluye una coma:

 FOO((int), int_var); FOO(int, int_var2); 

En C, por supuesto, esto no es necesario porque los nombres de tipos no pueden contener comas fuera de paréntesis. Entonces, para una macro de lenguaje cruzado, puede escribir:

 #ifdef __cplusplus__ template struct argument_type; template struct argument_type { typedef U type; }; #define FOO(t,name) argument_type::type name #else #define FOO(t,name) t name #endif 

Si no puede usar paréntesis y no le gusta la solución SINGLE_ARG de Mike, simplemente defina un COMMA:

 #define COMMA , FOO(std::map, map_var); 

Esto también ayuda si desea stringificar algunos de los argumentos de macro, como en

 #include  #include  #include  #define STRV(...) #__VA_ARGS__ #define COMMA , #define FOO(type, bar) bar(STRV(type) \ " has typeid name \"%s\"", typeid(type).name()) int main() { FOO(std::map, std::printf); } 

que imprime std::map has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE" .

Si su preprocesador admite macros variadic:

 #define SINGLE_ARG(...) __VA_ARGS__ #define FOO(type,name) type name FOO(SINGLE_ARG(std::map), map_var); 

De lo contrario, es un poco más tedioso:

 #define SINGLE_ARG2(A,B) A,B #define SINGLE_ARG3(A,B,C) A,B,C // as many as you'll need FOO(SINGLE_ARG2(std::map), map_var); 

Simplemente defina FOO como

 #define UNPACK( ... ) __VA_ARGS__ #define FOO( type, name ) UNPACK type name 

A continuación, inícielo siempre con paréntesis alrededor del argumento de tipo, por ejemplo

 FOO( (std::map), map_var ); 

Por supuesto, puede ser una buena idea ejemplificar las invocaciones en un comentario sobre la definición de macro.

Hay al menos dos formas de hacer esto. Primero, puede definir una macro que tome múltiples argumentos:

 #define FOO2(type1, type2, name) type1, type2, name 

Si lo hace, es posible que termine definiendo más macros para manejar más argumentos.

Segundo, puedes poner paréntesis alrededor del argumento:

 #define FOO(type, name) type name F00((std::map) map_var; 

Si lo hace, puede encontrar que los paréntesis adicionales arruinan la syntax del resultado.

Esto es posible con P99 :

 #include "p99/p99.h" #define FOO(...) P99_ALLBUTLAST(__VA_ARGS__) P99_LAST(__VA_ARGS__) FOO() 

El código anterior efectivamente elimina solo la última coma en la lista de argumentos. Verifique con clang -E (P99 requiere un comstackdor C99).

La respuesta simple es que no puedes. Este es un efecto secundario de la elección de < ...> para argumentos de plantilla; los < y > también aparecen en contextos desequilibrados, por lo que el mecanismo de macro no podría extenderse para manejarlos como si manejara paréntesis. (Algunos de los miembros del comité habían argumentado a favor de un token diferente, digamos (^...^) , pero no pudieron convencer a la mayoría de los problemas usando < ...> ).