Coma en macro C / C ++


103

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<int, int>, map_var); // error: macro "FOO" passed 3 arguments, but takes just 2

Por supuesto que podríamos hacer:

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

que no es muy ergonómico. Deben tratarse las incompatibilidades de tipo plus. ¿Alguna idea de cómo resolver esto con macro?


Supongo que debes escapar de los caracteres con un significado para convertirlos en literales.
Jite

Al menos en C ++, puedes poner una typedef en cualquier lugar, así que no estoy seguro de por qué dices que tiene que ser "de antemano".
Vaughn Cato

Respuestas:


108

Debido corchetes angulares también pueden representar (o producirse en) los operadores de comparación <, >, <=y >=, la expansión macro no puede ignorar comas dentro de corchetes angulares como lo hace dentro de paréntesis. (Esto también es un problema para los corchetes y llaves, aunque estos suelen aparecer como pares equilibrados). Puede encerrar el argumento macro entre paréntesis:

FOO((std::map<int, int>), map_var);

El problema es entonces que el parámetro permanece entre paréntesis dentro de la expansión de la macro, lo que evita 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 utilizando un tipo de función:

template<typename T> struct argument_type;
template<typename T, typename U> struct argument_type<T(U)> { typedef U type; };
#define FOO(t,name) argument_type<void(t)>::type name
FOO((std::map<int, int>), map_var);

Debido a que la formación de tipos de función ignora 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 tipo no pueden contener comas fuera de paréntesis. Entonces, para una macro en varios idiomas, puede escribir:

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

Esto es asombroso. Pero, ¿cómo se enteró de esto? He estado probando toneladas de trucos y nunca pensé que un tipo de función solucionaría el problema.
Will Custode el

@WilliamCustode, según recuerdo, había estado estudiando la gramática de los tipos de funciones y las declaraciones de funciones con referencia al problema de análisis más molesto, por lo que fue fortuito que fuera consciente de que se podían aplicar paréntesis redundantes a un tipo en ese contexto.
ecatmur

Encontré un problema con este método al trabajar con plantillas. Digamos que el código que quería era este: template<class KeyType, class ValueType> void SomeFunc(FOO(std::map<KeyType, ValueType>) element) {}si aplico esta solución aquí, las estructuras detrás de la macro se convierten en tipos dependientes y ahora se requiere el prefijo del nombre de tipo en el tipo. Puede agregarlo, pero la deducción de tipo se ha roto, por lo que ahora debe enumerar manualmente los argumentos de tipo para llamar a la función. Terminé usando el método de temple de definir una macro para la coma. Puede que no se vea tan bonito, pero funcionó perfectamente.
Roger Sanders

Un pequeño problema en la respuesta: establece que las comas se ignoran en el interior [] y {}, no lo son, solo funciona con ()tristeza. Ver: Sin embargo, no hay ningún requisito de corchetes o llaves para equilibrar ...
VinGarcia

Desafortunadamente, esto no funciona en MSVC : godbolt.org/z/WPjYW8 . Parece que MSVC no permite agregar varios parientes y no puede analizarlo. Una solución que no es tan elegante pero más rápido (menos instanciaciones de plantillas) es envolver el argumento comas ed en una macro envoltura: #define PROTECT(...) argument_type<void(__VA_ARGS__)>::type. Ahora es posible pasar argumentos fácilmente incluso a través de múltiples macros y para tipos simples puede omitir PROTECT. Sin embargo, los tipos de función se convierten en punteros de función cuando se evalúan de esta manera
Flamefire

119

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

#define COMMA ,

FOO(std::map<int COMMA int>, map_var);

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

#include <cstdio>
#include <map>
#include <typeinfo>

#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<int COMMA int>, std::printf);
}

que imprime std::map<int , int> has typeid name "St3mapIiiSt4lessIiESaISt4pairIKiiEEE".


16
#define COMMA wow, me acabas de ahorrar HORAS de trabajo ... ¿por qué no pensé en esto hace años? Gracias por compartir esta idea. Esto incluso me permite crear macros que configuran funciones con diferentes recuentos de argumentos.
Moliad

28
Más 1 para el horror
namezero

1
@kiw Si usted #define STRVX(...) STRV(__VA_ARGS__)y #define STRV(...) # __VA_ARGS__, entonces std::cout << STRV(type<A COMMA B>) << std::endl;se imprimirá type<A COMMA B>y std::cout << STRVX(type<A COMMA B>) << std::endl;se imprimirá type<A , B>. ( STRVes para "variadic stringify" y STRVXes para "expandido variadic stringify".)
not-a-user

1
@ not-a-user sí, pero con macros variadic no necesita la COMMAmacro en primer lugar. Eso es con lo que terminé.
Kiw

Nunca usaría eso, pero +1 por ser gracioso.
Rafael Baptista

58

Si su preprocesador admite macros variadic:

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

FOO(SINGLE_ARG(std::map<int, int>), 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<int, int>), map_var);

Oh, Dios ... ¿Por qué? ¿Por qué no poner entre paréntesis?

15
@VladLazarenko: Porque no siempre puedes poner piezas arbitrarias de código entre paréntesis. En particular, no puede poner paréntesis alrededor del nombre del tipo en un declarador, que es exactamente en lo que se convierte este argumento.
Mike Seymour

2
... y también porque es posible que solo pueda modificar la definición de macro y no todos los lugares que la llaman (que pueden no estar bajo su control, o pueden estar distribuidos en miles de archivos, etc.). Esto ocurre, por ejemplo, cuando se agrega una macro para asumir las tareas de una función con el mismo nombre.
BeeOnRope

32

Solo define FOOcomo

#define UNPACK( ... ) __VA_ARGS__

#define FOO( type, name ) UNPACK type name

Luego invocalo siempre entre paréntesis alrededor del argumento de tipo, por ejemplo

FOO( (std::map<int, int>), map_var );

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


No estoy seguro de por qué esto está tan abajo, es una solución mucho mejor que Mike Seymours. Es rápido y simple y está completamente oculto al usuario.
iFreilicht

3
@iFreilicht: Se publicó poco más de un año después. ;-)
Saludos y hth. - Alf

5
Y porque también es difícil entender cómo y por qué funciona
VinGarcia

@VinGarcia, ¿puedes explicar por qué / cómo funciona? ¿Por qué se requieren los paréntesis al llamarlo? ¿Qué UNPACKhago cuando se usa así ) UNPACK type name? ¿Por qué typeobtiene el tipo correctamente cuando se usa ) UNPACK type name? ¿Qué diablos está pasando aquí?
usuario

No @user, tal vez Cheers y hth puedan responderte
VinGarcia

4

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.

En segundo lugar, puede poner paréntesis alrededor del argumento:

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

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


Para la primera solución, cada macro deberá tener un nombre diferente, ya que las macros no se sobrecargan. Y para el segundo, si está pasando un nombre de tipo, es muy probable que se use para declarar una variable (o un typedef), por lo que los paréntesis causarán problemas.
James Kanze

4

Esto es posible con P99 :

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

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


3

La respuesta simple es que no puedes. Este es un efecto secundario de la elección de <...>argumentos de plantilla; el <y >también aparecen en contextos desequilibradas por lo que el mecanismo de macro no podría extenderse a manejarlos como se maneja paréntesis. (Algunos de los miembros del comité habían abogado por una ficha diferente, por ejemplo (^...^), pero no pudieron convencer a la mayoría de los problemas con el uso <...>).


2
(^...^)esta es una cara feliz :)
CygnusX1
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.