Hay un problema bien conocido con argumentos vacíos para macros variables en C99.
ejemplo:
#define FOO(...) printf(__VA_ARGS__)
#define BAR(fmt, ...) printf(fmt, __VA_ARGS__)
FOO("this works fine");
BAR("this breaks!");
El uso de lo BAR()
anterior es de hecho incorrecto según el estándar C99, ya que se expandirá a:
printf("this breaks!",);
Tenga en cuenta la coma final: no funciona.
Algunos compiladores (por ejemplo: Visual Studio 2010) se deshacerán silenciosamente de esa coma final por usted. Otros compiladores (p. Ej .: GCC) admiten poner ##
delante de este __VA_ARGS__
modo:
#define BAR(fmt, ...) printf(fmt, ##__VA_ARGS__)
Pero, ¿hay alguna forma que cumpla con los estándares para obtener este comportamiento? ¿Quizás usando múltiples macros?
En este momento, la ##
versión parece bastante compatible (al menos en mis plataformas), pero prefiero usar una solución que cumpla con los estándares.
Preventivo: Sé que podría escribir una pequeña función. Estoy tratando de hacer esto usando macros.
Editar : Aquí hay un ejemplo (aunque simple) de por qué me gustaría usar BAR ():
#define BAR(fmt, ...) printf(fmt "\n", ##__VA_ARGS__)
BAR("here is a log message");
BAR("here is a log message with a param: %d", 42);
Esto agrega automáticamente una nueva línea a mis declaraciones de registro BAR (), suponiendo fmt
que siempre sea una C-cadena entre comillas dobles. NO imprime la nueva línea como un printf () separado, lo cual es ventajoso si el registro está almacenado en línea y proviene de múltiples fuentes de forma asincrónica.
BAR
lugar deFOO
en primer lugar?__VA_OPT__
palabra clave. Esto ya ha sido "adoptado" por C ++, por lo que espero que C haga lo mismo. (no sé si eso significa que fue acelerado en C ++ 17 o si está configurado para C ++ 20)Respuestas:
Es posible evitar el uso de la
,##__VA_ARGS__
extensión de GCC si está dispuesto a aceptar algún límite superior codificado en el número de argumentos que puede pasar a su macro variadic, como se describe en la respuesta de Richard Hansen a esta pregunta . Sin embargo, si no desea tener dicho límite, que yo sepa, no es posible usar solo las funciones de preprocesador especificadas por C99; debes usar alguna extensión del idioma. clang e icc han adoptado esta extensión GCC, pero MSVC no.En 2001 escribí la extensión GCC para la estandarización (y la extensión relacionada que le permite usar un nombre que no sea
__VA_ARGS__
para el parámetro de reposo) en el documento N976 , pero que no recibió respuesta alguna del comité; Ni siquiera sé si alguien lo leyó. En 2016 se propuso nuevamente en N2023 , y animo a cualquiera que sepa cómo esa propuesta nos lo hará saber en los comentarios.fuente
comp.std.c
pero no pude encontrar ninguna en Grupos de Google en este momento; Ciertamente nunca recibió ninguna atención del comité real (o si lo hizo, nadie me lo contó).Hay un truco de conteo de argumentos que puedes usar.
Aquí hay una forma estándar de implementar el segundo
BAR()
ejemplo en la pregunta de jwd:Este mismo truco se usa para:
__VA_ARGS__
Explicación
La estrategia es separarse
__VA_ARGS__
en el primer argumento y el resto (si corresponde). Esto hace posible insertar cosas después del primer argumento pero antes del segundo (si está presente).FIRST()
Esta macro simplemente se expande al primer argumento, descartando el resto.
La implementación es sencilla. El
throwaway
argumento asegura queFIRST_HELPER()
obtiene dos argumentos, lo cual es necesario porque...
necesita al menos uno. Con un argumento, se expande de la siguiente manera:FIRST(firstarg)
FIRST_HELPER(firstarg, throwaway)
firstarg
Con dos o más, se expande de la siguiente manera:
FIRST(firstarg, secondarg, thirdarg)
FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
firstarg
REST()
Esta macro se expande a todo menos al primer argumento (incluida la coma después del primer argumento, si hay más de un argumento).
La implementación de esta macro es mucho más complicada. La estrategia general es contar el número de argumentos (uno o más de uno) y luego expandirse a
REST_HELPER_ONE()
(si solo se da un argumento) oREST_HELPER_TWOORMORE()
(si se dan dos o más argumentos).REST_HELPER_ONE()
simplemente se expande a nada: no hay argumentos después del primero, por lo que los argumentos restantes son el conjunto vacío.REST_HELPER_TWOORMORE()
también es sencillo: se expande a una coma seguida de todo excepto el primer argumento.Los argumentos se cuentan utilizando la
NUM()
macro. Esta macro se expandeONE
si solo se da un argumento,TWOORMORE
si se dan entre dos y nueve argumentos, y se rompe si se dan 10 o más argumentos (porque se expande al décimo argumento).La
NUM()
macro usa laSELECT_10TH()
macro para determinar el número de argumentos. Como su nombre lo indica,SELECT_10TH()
simplemente se expande a su décimo argumento. Debido a los puntos suspensivos, seSELECT_10TH()
deben pasar al menos 11 argumentos (el estándar dice que debe haber al menos un argumento para los puntos suspensivos). Esta es la razón por la que seNUM()
pasathrowaway
como el último argumento (sin él, pasar un argumento aNUM()
daría lugar a que solo se pasen 10 argumentos aSELECT_10TH()
, lo que violaría el estándar).La selección de
REST_HELPER_ONE()
oREST_HELPER_TWOORMORE()
se realiza concatenandoREST_HELPER_
con la expansión deNUM(__VA_ARGS__)
inREST_HELPER2()
. Tenga en cuenta que el propósito deREST_HELPER()
es garantizar queNUM(__VA_ARGS__)
se expanda completamente antes de concatenarse conREST_HELPER_
.La expansión con un argumento es la siguiente:
REST(firstarg)
REST_HELPER(NUM(firstarg), firstarg)
REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
REST_HELPER2(ONE, firstarg)
REST_HELPER_ONE(firstarg)
La expansión con dos o más argumentos es la siguiente:
REST(firstarg, secondarg, thirdarg)
REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
, secondarg, thirdarg
fuente
No es una solución general, pero en el caso de printf podría agregar una nueva línea como:
Creo que ignora cualquier argumento adicional que no esté referenciado en la cadena de formato. Así que probablemente incluso puedas salirte con la tuya:
No puedo creer que C99 haya sido aprobado sin una forma estándar de hacerlo. AFAICT el problema también existe en C ++ 11.
fuente
Hay una manera de manejar este caso específico usando algo como Boost.Preprocessor . Puede usar BOOST_PP_VARIADIC_SIZE para verificar el tamaño de la lista de argumentos y luego condicionalmente expandirse a otra macro. El único inconveniente de esto es que no puede distinguir entre 0 y 1 argumento, y la razón de esto se vuelve clara una vez que considera lo siguiente:
La lista de argumentos de macro vacía en realidad consiste en un argumento que está vacío.
En este caso, tenemos suerte ya que su macro deseada siempre tiene al menos 1 argumento, podemos implementarla como dos macros de "sobrecarga":
Y luego otra macro para cambiar entre ellos, como:
o
Cualquiera que sea más legible (prefiero el primero, ya que le da una forma general para sobrecargar macros en la cantidad de argumentos).
También es posible hacer esto con una sola macro accediendo y mutando la lista de argumentos variables, pero es mucho menos legible y es muy específico para este problema:
Además, ¿por qué no hay BOOST_PP_ARRAY_ENUM_TRAILING? Haría esta solución mucho menos horrible.
Editar: Muy bien, aquí hay un BOOST_PP_ARRAY_ENUM_TRAILING, y una versión que lo usa (esta es ahora mi solución favorita):
fuente
BOOST_PP_VARIADIC_SIZE()
utiliza el mismo truco de conteo de argumentos que documenté en mi respuesta y tiene la misma limitación (se romperá si pasa más de un cierto número de argumentos).Una macro muy simple que estoy usando para la impresión de depuración:
No importa cuántos argumentos se pasen a DBG, no hay advertencia c99.
El truco consiste en
__DBG_INT
agregar un parámetro ficticio, por...
lo que siempre tendrá al menos un argumento y c99 estará satisfecho.fuente
Me encontré con un problema similar recientemente, y creo que hay una solución.
La idea clave es que hay una manera de escribir una macro
NUM_ARGS
para contar el número de argumentos que se le da a una macro variada. Puede usar una variación deNUM_ARGS
para construirNUM_ARGS_CEILING2
, que le puede decir si a una macro variada se le dan 1 argumento o 2 o más argumentos. Luego, puede escribir suBar
macro para que utiliceNUM_ARGS_CEILING2
yCONCAT
envíe sus argumentos a una de las dos macros auxiliares: una que espera exactamente 1 argumento y otra que espera un número variable de argumentos mayor que 1.Aquí hay un ejemplo en el que uso este truco para escribir la macro
UNIMPLEMENTED
, que es muy similar aBAR
:PASO 1:
PASO 1.5:
Paso 2:
PASO 3:
Donde CONCAT se implementa de la manera habitual. Como pista rápida, si lo anterior parece confuso: el objetivo de CONCAT es expandirse a otra macro "llamada".
Tenga en cuenta que NUM_ARGS en sí no se usa. Lo acabo de incluir para ilustrar el truco básico aquí. Vea el blog P99 de Jens Gustedt para un buen tratamiento.
Dos notas:
NUM_ARGS está limitado en la cantidad de argumentos que maneja. El mío solo puede manejar hasta 20, aunque el número es totalmente arbitrario.
NUM_ARGS, como se muestra, tiene una trampa en el sentido de que devuelve 1 cuando se le dan 0 argumentos. La esencia de esto es que NUM_ARGS está contando técnicamente [comas + 1], y no argumentos. En este caso particular, en realidad funciona para nuestra ventaja. _UNIMPLEMENTED1 manejará un token vacío muy bien y nos ahorra tener que escribir _UNIMPLEMENTED0. Gustedt también tiene una solución alternativa para eso, aunque no lo he usado y no estoy seguro de si funcionaría para lo que estamos haciendo aquí.
fuente
NUM_ARGS
pero no lo usa. 2. ¿Cuál es el propósito deUNIMPLEMENTED
? 3. Nunca resuelve el problema de ejemplo en la pregunta. 4. Caminar por la expansión paso a paso ilustraría cómo funciona y explicaría el papel de cada macro auxiliar. 5. Discutir 0 argumentos es una distracción; el OP estaba preguntando sobre el cumplimiento de las normas, y 0 argumentos están prohibidos (C99 6.10.3p4). 6. Paso 1.5? ¿Por qué no el paso 2? 7. "Pasos" implica acciones que ocurren secuencialmente; Esto es solo código.CONCAT()
, no asumas que los lectores saben cómo funciona.Esta es la versión simplificada que uso. Se basa en las excelentes técnicas de las otras respuestas aquí, muchos accesorios para ellos:
Eso es.
Al igual que con otras soluciones, esto se limita al número de argumentos de la macro. Para admitir más, agregue más parámetros
_SELECT
y másN
argumentos. Los nombres de los argumentos cuentan hacia atrás (en lugar de hacia arriba) para servir como recordatorio de que elSUFFIX
argumento basado en el conteo se proporciona en orden inverso.Esta solución trata 0 argumentos como si fuera 1 argumento. Así
BAR()
nominalmente "obras", porque se expande a_SELECT(_BAR,,N,N,N,N,1)()
, que se expande a_BAR_1()()
, que se expande aprintf("\n")
.Si lo desea, puede ser creativo con el uso de
_SELECT
y proporcionar diferentes macros para diferentes números de argumentos. Por ejemplo, aquí tenemos una macro LOG que toma un argumento de 'nivel' antes del formato. Si falta el formato, registra "(sin mensaje)", si solo hay 1 argumento, lo registrará a través de "% s", de lo contrario, tratará el argumento de formato como una cadena de formato printf para los argumentos restantes.fuente
En su situación (al menos 1 argumento presente, no 0), se puede definir
BAR
comoBAR(...)
, utilizar Jens Gustedt deHAS_COMMA(...)
detectar una coma y luego enviar aBAR0(Fmt)
oBAR1(Fmt,...)
consecuencia.Esta:
compila
-pedantic
sin previo aviso.fuente
C (gcc) , 762 bytes
Pruébalo en línea!
Asume:
A
~G
(puede cambiar el nombre a hard_collide)fuente
no arg contain comma
limitación puede pasarse por alto al verificar múltiples después de algunos pases más, perono bracket
sigue ahíLa solución estándar es usar en
FOO
lugar deBAR
. Hay algunos casos extraños de reordenamiento de argumentos que probablemente no pueda hacer por usted (aunque apuesto a que alguien puede idear trucos inteligentes para desmontar y volver a armar__VA_ARGS__
condicionalmente en función del número de argumentos en él), pero en general usandoFOO
"generalmente" solo funcionafuente