c c99 c-preprocessor variadic-macros

¿Alternativa estándar al truco de GCC##__ VA_ARGS__?



c99 c-preprocessor (7)

Existe un problem well-known con argumentos vacíos para macros variadas 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 BAR() arriba es de hecho incorrecto de acuerdo con el estándar C99, ya que se expandirá a:

printf("this breaks!",);

Tenga en cuenta la coma final - no viable.

Algunos compiladores (por ejemplo, Visual Studio 2010) se desharán silenciosamente de esa coma final para usted. Otros compiladores (p. Ej .: GCC) admiten poner ## delante de __VA_ARGS__ , así:

#define BAR(fmt, ...) printf(fmt, ##__VA_ARGS__)

Pero, ¿hay una forma compatible con los estándares para obtener este comportamiento? Tal vez 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.

Preferencial: 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 que fmt siempre es una cadena en doble citada. NO imprime la nueva línea como printf () por separado, lo cual es una ventaja si el registro está almacenado en búfer de línea y proviene de varias fuentes de forma asíncrona.


Es posible evitar el uso de la extensión GCC ,##__VA_ARGS__ si está dispuesto a aceptar algún límite superior codificado en la cantidad de argumentos que puede pasar a su macro variable, como se describe en la respuesta de Richard Hansen a esta pregunta . Sin embargo, si no desea tener dicho límite, según mi leal saber y entender, no es posible utilizar solo las características del preprocesador especificadas por C99; debe usar alguna extensión para el idioma. clang y icc han adoptado esta extensión de GCC, pero MSVC no.

En 2001 escribí la extensión de GCC para estandarización (y la extensión relacionada que le permite usar un nombre distinto de __VA_ARGS__ para el parámetro de descanso) 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 aliento a todos los que saben cómo esa propuesta nos va a dejar saber en los comentarios.


Esta es la versión simplificada que uso. Se basa en las grandes técnicas de las otras respuestas aquí, muchos accesorios para ellos:

#define _SELECT(PREFIX,_5,_4,_3,_2,_1,SUFFIX,...) PREFIX ## _ ## SUFFIX #define _BAR_1(fmt) printf(fmt "/n") #define _BAR_N(fmt, ...) printf(fmt "/n", __VA_ARGS__); #define BAR(...) _SELECT(_BAR,__VA_ARGS__,N,N,N,N,1)(__VA_ARGS__) int main(int argc, char *argv[]) { BAR("here is a log message"); BAR("here is a log message with a param: %d", 42); return 0; }

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 a _SELECT y más N argumentos. Los nombres de argumento cuentan hacia atrás (en lugar de hacia arriba) para servir como un recordatorio de que el argumento SUFFIX basado en SUFFIX se proporciona en orden inverso.

Esta solución trata 0 argumentos como si fuera 1 argumento. Así que BAR() nominalmente "funciona", porque se expande a _SELECT(_BAR,,N,N,N,N,1)() , que se expande a _BAR_1()() , que se expande a printf("/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 ''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.

#define _LOG_1(lvl) printf("[%s] (no message)/n", #lvl) #define _LOG_2(lvl,fmt) printf("[%s] %s/n", #lvl, fmt) #define _LOG_N(lvl,fmt, ...) printf("[%s] " fmt "/n", #lvl, __VA_ARGS__) #define LOG(...) _SELECT(_LOG,__VA_ARGS__,N,N,N,2,1)(__VA_ARGS__) int main(int argc, char *argv[]) { LOG(INFO); LOG(DEBUG, "here is a log message"); LOG(WARN, "here is a log message with param: %d", 42); return 0; } /* outputs: [INFO] (no message) [DEBUG] here is a log message [WARN] here is a log message with param: 42 */


Hay un argumento que cuenta el truco que puedes usar.

Aquí hay una forma estándar para implementar el segundo ejemplo de BAR() en la pregunta de jwd:

#include <stdio.h> #define BAR(...) printf(FIRST(__VA_ARGS__) "/n" REST(__VA_ARGS__)) /* expands to the first argument */ #define FIRST(...) FIRST_HELPER(__VA_ARGS__, throwaway) #define FIRST_HELPER(first, ...) first /* * if there''s only one argument, expands to nothing. if there is more * than one argument, expands to a comma followed by everything but * the first argument. only supports up to 9 arguments but can be * trivially expanded. */ #define REST(...) REST_HELPER(NUM(__VA_ARGS__), __VA_ARGS__) #define REST_HELPER(qty, ...) REST_HELPER2(qty, __VA_ARGS__) #define REST_HELPER2(qty, ...) REST_HELPER_##qty(__VA_ARGS__) #define REST_HELPER_ONE(first) #define REST_HELPER_TWOORMORE(first, ...) , __VA_ARGS__ #define NUM(...) / SELECT_10TH(__VA_ARGS__, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE,/ TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway) #define SELECT_10TH(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, ...) a10 int main(int argc, char *argv[]) { BAR("first test"); BAR("second test: %s", "a string"); return 0; }

Este mismo truco se usa para:

  • contar la cantidad de argumentos
  • expandirse de manera diferente según la cantidad de argumentos
  • añadir a __VA_ARGS__

Explicación

La estrategia es separar __VA_ARGS__ en el primer argumento y el resto (si hay alguno). 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 directa. El argumento de FIRST_HELPER() garantiza que FIRST_HELPER() obtenga dos argumentos, lo cual es necesario porque ... necesita al menos uno. Con un argumento, se expande de la siguiente manera:

  1. FIRST(firstarg)
  2. FIRST_HELPER(firstarg, throwaway)
  3. firstarg

Con dos o más, se expande de la siguiente manera:

  1. FIRST(firstarg, secondarg, thirdarg)
  2. FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
  3. 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 expandir a REST_HELPER_ONE() (si solo se da un argumento) o REST_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 menos del primer argumento.

Los argumentos se cuentan usando la macro NUM() . Esta macro se expande a ONE si solo se da un argumento, TWOORMORE si se TWOORMORE entre dos y nueve argumentos, y se rompe si se dan 10 o más argumentos (porque se expande al décimo argumento).

La macro NUM() usa la macro SELECT_10TH() para determinar la cantidad de argumentos. Como su nombre lo indica, SELECT_10TH() simplemente se expande a su décimo argumento. Debido a la elipsis, SELECT_10TH() necesita pasar al menos 11 argumentos (el estándar dice que debe haber al menos un argumento para la elipsis). Esta es la razón por la que NUM() pasa como un throwaway como el último argumento (sin él, pasar un argumento a NUM() daría como resultado que solo 10 argumentos pasen a SELECT_10TH() , lo que violaría el estándar).

La selección de REST_HELPER_ONE() o REST_HELPER_TWOORMORE() se realiza concatenando REST_HELPER_ con la expansión de NUM(__VA_ARGS__) en REST_HELPER2() . Tenga en cuenta que el objetivo de REST_HELPER() es garantizar que NUM(__VA_ARGS__) se expanda por completo antes de concatenarse con REST_HELPER_ .

La expansión con un argumento es la siguiente:

  1. REST(firstarg)
  2. REST_HELPER(NUM(firstarg), firstarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg)
  4. REST_HELPER2(ONE, firstarg)
  5. REST_HELPER_ONE(firstarg)
  6. (vacío)

La expansión con dos o más argumentos es la siguiente:

  1. REST(firstarg, secondarg, thirdarg)
  2. REST_HELPER(NUM(firstarg, secondarg, thirdarg), firstarg, secondarg, thirdarg)
  3. REST_HELPER2(SELECT_10TH(firstarg, secondarg, thirdarg, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, TWOORMORE, ONE, throwaway), firstarg, secondarg, thirdarg)
  4. REST_HELPER2(TWOORMORE, firstarg, secondarg, thirdarg)
  5. REST_HELPER_TWOORMORE(firstarg, secondarg, thirdarg)
  6. , secondarg, thirdarg

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, a continuación, expandirla condicionalmente a otra macro. El único inconveniente de esto es que no puede distinguir entre 0 y 1 argumento, y la razón para esto se vuelve clara una vez que considere lo siguiente:

BOOST_PP_VARIADIC_SIZE() // expands to 1 BOOST_PP_VARIADIC_SIZE(,) // expands to 2 BOOST_PP_VARIADIC_SIZE(,,) // expands to 3 BOOST_PP_VARIADIC_SIZE(a) // expands to 1 BOOST_PP_VARIADIC_SIZE(a,) // expands to 2 BOOST_PP_VARIADIC_SIZE(,b) // expands to 2 BOOST_PP_VARIADIC_SIZE(a,b) // expands to 2 BOOST_PP_VARIADIC_SIZE(a, ,c) // expands to 3

La lista de macro argumentos vacíos en realidad consiste en un argumento que pasa a estar vacío.

En este caso, tenemos suerte ya que su macro deseada siempre tiene al menos 1 argumento, podemos implementarlo como dos macros de "sobrecarga":

#define BAR_0(fmt) printf(fmt "/n") #define BAR_1(fmt, ...) printf(fmt "/n", __VA_ARGS__)

Y luego otra macro para cambiar entre ellos, como por ejemplo:

#define BAR(...) / BOOST_PP_CAT(BAR_, BOOST_PP_GREATER( BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1))(__VA_ARGS__) / /**/

o

#define BAR(...) BOOST_PP_IIF( / BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), / BAR_1, BAR_0)(__VA_ARGS__) / /**/

Lo que encuentre 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:

#define BAR(...) printf( / BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "/n" / BOOST_PP_COMMA_IF( / BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1)) / BOOST_PP_ARRAY_ENUM(BOOST_PP_ARRAY_POP_FRONT( / BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) / /**/

Además, ¿por qué no hay BOOST_PP_ARRAY_ENUM_TRAILING? Haría esta solución mucho menos horrible.

Editar: Bien, aquí hay un BOOST_PP_ARRAY_ENUM_TRAILING, y una versión que lo usa (esta es ahora mi solución favorita):

#define BOOST_PP_ARRAY_ENUM_TRAILING(array) / BOOST_PP_COMMA_IF(BOOST_PP_ARRAY_SIZE(array)) BOOST_PP_ARRAY_ENUM(array) / /**/ #define BAR(...) printf( / BOOST_PP_VARIADIC_ELEM(0, __VA_ARGS__) "/n" / BOOST_PP_ARRAY_ENUM_TRAILING(BOOST_PP_ARRAY_POP_FRONT( / BOOST_PP_VARIADIC_TO_ARRAY(__VA_ARGS__)))) / /**/


La solución estándar es usar FOO lugar de BAR . Hay algunos casos raros de argumentos reordenados que probablemente no pueda hacer por usted (aunque apuesto a que alguien puede inventar __VA_ARGS__ inteligentes para desensamblar y volver a ensamblar __VA_ARGS__ condicional en función de la cantidad de argumentos en él!) Pero en general usando FOO " usualmente "solo funciona".


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 variadica. Puede usar una variación de NUM_ARGS para compilar NUM_ARGS_CEILING2 , que le puede indicar si a una macro variadica se le da 1 argumento o 2 o más argumentos. Luego puede escribir su macro Bar para que use NUM_ARGS_CEILING2 y CONCAT para enviar 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 utilizo este truco para escribir la macro UNIMPLEMENTED , que es muy similar a BAR :

PASO 1:

/** * A variadic macro which counts the number of arguments which it is * passed. Or, more precisely, it counts the number of commas which it is * passed, plus one. * * Danger: It can''t count higher than 20. If it''s given 0 arguments, then it * will evaluate to 1, rather than to 0. */ #define NUM_ARGS(...) / NUM_ARGS_COUNTER(__VA_ARGS__, 20, 19, 18, 17, 16, 15, 14, 13, / 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1) #define NUM_ARGS_COUNTER(a1, a2, a3, a4, a5, a6, a7, / a8, a9, a10, a11, a12, a13, / a14, a15, a16, a17, a18, a19, a20, / N, ...) / N

PASO 1.5:

/* * A variant of NUM_ARGS that evaluates to 1 if given 1 or 0 args, or * evaluates to 2 if given more than 1 arg. Behavior is nasty and undefined if * it''s given more than 20 args. */ #define NUM_ARGS_CEIL2(...) / NUM_ARGS_COUNTER(__VA_ARGS__, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, / 2, 2, 2, 2, 2, 2, 2, 1)

Paso 2:

#define _UNIMPLEMENTED1(msg) / log("My creator has forsaken me. %s:%s:%d." msg, __FILE__, / __func__, __LINE__) #define _UNIMPLEMENTED2(msg, ...) / log("My creator has forsaken me. %s:%s:%d." msg, __FILE__, / __func__, __LINE__, __VA_ARGS__)

PASO 3:

#define UNIMPLEMENTED(...) / CONCAT(_UNIMPLEMENTED, NUM_ARGS_CEIL2(__VA_ARGS__))(__VA_ARGS__)

Donde CONCAT se implementa de la manera habitual. Como una pista rápida, si lo anterior parece confuso: el objetivo de CONCAT es expandirse a otra "llamada" macro.

Tenga en cuenta que NUM_ARGS no se usa. Simplemente lo incluí 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 dificultad porque devuelve 1 cuando se le dan 0 argumentos. La esencia de esto es que NUM_ARGS está técnicamente contando [comas + 1], y no args. En este caso particular, en realidad funciona a nuestro favor. _UNIMPLEMENTED1 manejará un token vacío muy bien y nos ahorra tener que escribir _UNIMPLEMENTED0. Gustedt también tiene una solución para eso, aunque yo no lo he usado y no estoy seguro si funcionaría para lo que estamos haciendo aquí.


No es una solución general, pero en el caso de printf podría añadir una nueva línea como:

#define BAR_HELPER(fmt, ...) printf(fmt "/n%s", __VA_ARGS__) #define BAR(...) BAR_HELPER(__VA_ARGS__, "")

Creo que ignora cualquier argumento adicional que no esté referenciado en la cadena de formato. Entonces, probablemente puedas salirte con la tuya:

#define BAR_HELPER(fmt, ...) printf(fmt "/n", __VA_ARGS__) #define BAR(...) BAR_HELPER(__VA_ARGS__, 0)

No puedo creer que C99 haya sido aprobado sin una forma estándar de hacerlo. AFAICT, el problema existe también en C ++ 11.