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


151

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 fmtque 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.


3
¿Por qué usar en BARlugar de FOOen primer lugar?
GManNickG

@GMan: Agregué un ejemplo al final
jwd

55
@GMan: Lea la última oración (:
jwd

77
Esta característica se ha propuesto para su inclusión en C2x.
Leushenko

2
@zwol la última versión enviada a WG14 tiene este aspecto , que utiliza una nueva sintaxis basada en la __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)
Leushenko

Respuestas:


66

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.


2
A juzgar por mi discapacidad para encontrar una solución en la web y la falta de respuestas aquí, supongo que tienes razón):
jwd

2
¿Es n976 a lo que te refieres? Busqué el resto del grupo de trabajo C 's documentos para una respuesta pero nunca encontré uno. Ni siquiera estaba en la agenda de la reunión posterior . El único otro éxito en este tema fue el comentario # 4 de Noruega en n868 antes de que se ratificara C99 (nuevamente sin discusión de seguimiento).
Richard Hansen

44
Sí, específicamente la segunda mitad de eso. Es posible que haya habido una discusión, comp.std.cpero 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ó).
zwol

1
Me temo que no tengo una prueba, ni soy la persona adecuada para tratar de pensar en una. Escribí la mitad del preprocesador de GCC, pero eso fue hace más de diez años, y nunca hubiera pensado en el truco de contar argumentos a continuación, incluso entonces.
zwol

66
Esta extensión funciona con compiladores clang e intel icc, así como con gcc.
ACyclic

112

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:

#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:

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 throwawayargumento asegura que FIRST_HELPER()obtiene 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 expandirse 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 excepto el primer argumento.

Los argumentos se cuentan utilizando la NUM()macro. Esta macro se expande ONEsi solo se da un argumento, TWOORMOREsi 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 la SELECT_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, se SELECT_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 se NUM()pasa throwawaycomo el último argumento (sin él, pasar un argumento a NUM()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()o REST_HELPER_TWOORMORE()se realiza concatenando REST_HELPER_con la expansión de NUM(__VA_ARGS__)in REST_HELPER2(). Tenga en cuenta que el propósito de REST_HELPER()es garantizar que NUM(__VA_ARGS__)se expanda completamente 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

1
Tenga en cuenta que esto fallará si llama a BAR con 10 o más argumentos, y aunque es relativamente fácil extenderlo a más argumentos, siempre tendrá un límite superior en la cantidad de argumentos con los que puede lidiar
Chris Dodd,

2
@ChrisDodd: Correcto. Desafortunadamente, no parece haber una manera de evitar un límite en el número de argumentos sin depender de extensiones específicas del compilador. Además, no conozco una forma de probar de manera confiable si hay demasiados argumentos (para que se pueda imprimir un mensaje de error útil del compilador, en lugar de un error extraño).
Richard Hansen el

17

No es una solución general, pero en el caso de printf podría agregar 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. Así que probablemente incluso 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 también existe en C ++ 11.


El problema con este 0 adicional es que realmente terminará en el código si llama a la función vararg. Compruebe la solución provista por Richard Hansen
Pavel P

@Pavel tiene razón sobre el segundo ejemplo, pero el primero funciona muy bien. +1.
kirbyfan64sos

11

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:

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 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":

#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:

#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__) \
    /**/

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:

#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: Muy 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__)))) \
    /**/

1
Es bueno aprender sobre Boost. Preprocesador, +1. Tenga en cuenta que 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).
Richard Hansen el

1
Sí, vi que su enfoque era el mismo que utilizaba Boost, pero la solución de refuerzo está muy bien mantenida y tiene muchas otras funciones realmente útiles para desarrollar macros más sofisticadas. El material de recursión es particularmente genial (y se usa detrás de escena en el último enfoque que usa BOOST_PP_ARRAY_ENUM).
DRayX

1
¡Una respuesta Boost que realmente se aplica a la etiqueta c ! ¡Hurra!
Justin

6

Una macro muy simple que estoy usando para la impresión de depuración:

#define __DBG_INT(fmt, ...) printf(fmt "%s", __VA_ARGS__);
#define DBG(...) __DBG_INT(__VA_ARGS__, "\n")

int main() {
        DBG("No warning here");
        DBG("and we can add as many arguments as needed. %s", "nice!");
        return 0;
}

No importa cuántos argumentos se pasen a DBG, no hay advertencia c99.

El truco consiste en __DBG_INTagregar un parámetro ficticio, por ...lo que siempre tendrá al menos un argumento y c99 estará satisfecho.


5

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_ARGSpara contar el número de argumentos que se le da a una macro variada. Puede usar una variación de NUM_ARGSpara construir NUM_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 su Barmacro para que utilice NUM_ARGS_CEILING2y CONCATenví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 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 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í.


+1 por mencionar el truco de contar argumentos, -1 por ser realmente difícil de seguir
Richard Hansen

Los comentarios que agregó fueron una mejora, pero todavía hay una serie de problemas: 1. Usted discute y define, NUM_ARGSpero no lo usa. 2. ¿Cuál es el propósito de UNIMPLEMENTED? 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.
Richard Hansen

8. Enlace a todo el blog, no a la publicación relevante. No pude encontrar la publicación a la que te referías. 9. El último párrafo es incómodo: este método es oscuro; Es por eso que nadie más había publicado una solución correcta antes. Además, si funciona y se adhiere al estándar, la respuesta de Zack debe ser incorrecta. 10. Debes definir CONCAT(), no asumas que los lectores saben cómo funciona.
Richard Hansen

(Por favor, no interprete este comentario como un ataque; realmente quería votar su respuesta, pero no me sentí cómodo haciéndolo a menos que fuera más fácil de entender. Si puede mejorar la claridad de su respuesta, lo haré vota el tuyo y elimina el mío.)
Richard Hansen

2
¡Nunca hubiera pensado en este enfoque, y escribí aproximadamente la mitad del preprocesador actual de GCC! Dicho esto, todavía digo que "no hay una forma estándar de obtener este efecto" porque las técnicas tuyas y de Richard imponen un límite superior en el número de argumentos para la macro.
zwol

2

Esta es la versión simplificada que uso. Se basa en las excelentes 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 _SELECTy más Nargumentos. Los nombres de los argumentos cuentan hacia atrás (en lugar de hacia arriba) para servir como recordatorio de que el SUFFIXargumento 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 a printf("\n").

Si lo desea, puede ser creativo con el uso de _SELECTy 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.

#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
*/

Esto aún activa una advertencia cuando se compila con -pedantic.
PSkocik

1

En su situación (al menos 1 argumento presente, no 0), se puede definir BARcomo BAR(...), utilizar Jens Gustedt de HAS_COMMA(...) detectar una coma y luego enviar a BAR0(Fmt)o BAR1(Fmt,...)consecuencia.

Esta:

#define HAS_COMMA(...) HAS_COMMA_16__(__VA_ARGS__, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0)
#define HAS_COMMA_16__(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, ...) _15
#define CAT_(X,Y) X##Y
#define CAT(X,Y) CAT_(X,Y)
#define BAR(.../*All*/) CAT(BAR,HAS_COMMA(__VA_ARGS__))(__VA_ARGS__)
#define BAR0(X) printf(X "\n")
#define BAR1(X,...) printf(X "\n",__VA_ARGS__)


#include <stdio.h>
int main()
{
    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

compila -pedanticsin previo aviso.


0

C (gcc) , 762 bytes

#define EMPTYFIRST(x,...) A x (B)
#define A(x) x()
#define B() ,

#define EMPTY(...) C(EMPTYFIRST(__VA_ARGS__) SINGLE(__VA_ARGS__))
#define C(...) D(__VA_ARGS__)
#define D(x,...) __VA_ARGS__

#define SINGLE(...) E(__VA_ARGS__, B)
#define E(x,y,...) C(y(),)

#define NONEMPTY(...) F(EMPTY(__VA_ARGS__) D, B)
#define F(...) G(__VA_ARGS__)
#define G(x,y,...) y()

#define STRINGIFY(...) STRINGIFY2(__VA_ARGS__)
#define STRINGIFY2(...) #__VA_ARGS__

#define BAR(fmt, ...) printf(fmt "\n" NONEMPTY(__VA_ARGS__) __VA_ARGS__)

int main() {
    puts(STRINGIFY(NONEMPTY()));
    puts(STRINGIFY(NONEMPTY(1)));
    puts(STRINGIFY(NONEMPTY(,2)));
    puts(STRINGIFY(NONEMPTY(1,2)));

    BAR("here is a log message");
    BAR("here is a log message with a param: %d", 42);
}

Pruébalo en línea!

Asume:

  • Ningún argumento contiene coma o corchete
  • Ningún argumento contiene A~ G(puede cambiar el nombre a hard_collide)

La no arg contain commalimitación puede pasarse por alto al verificar múltiples después de algunos pases más, pero no bracketsigue ahí
l4m2 el

-2

La solución estándar es usar en FOOlugar de BAR. 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 usando FOO"generalmente" solo funciona


1
La pregunta era "¿hay alguna forma que cumpla con los estándares para obtener este comportamiento?"
Marsh Ray

2
Y la pregunta ha incluido una justificación para no usar FOO por mucho tiempo.
Pavel Šimerda
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.