Alternativa padrão ao truque ## __ VA_ARGS__ do GCC?


151

Há um problema conhecido com argumentos vazios para macros variadas em C99.

exemplo:

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

FOO("this works fine");
BAR("this breaks!");

O uso BAR()acima é realmente incorreto de acordo com o padrão C99, uma vez que se expandirá para:

printf("this breaks!",);

Observe a vírgula à direita - não é viável.

Alguns compiladores (por exemplo: Visual Studio 2010) se livram silenciosamente dessa vírgula final para você. Outros compiladores (por exemplo: GCC) suportam colocar ##na frente __VA_ARGS__, assim:

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

Mas existe uma maneira compatível com os padrões para obter esse comportamento? Talvez usando várias macros?

No momento, a ##versão parece bastante bem suportada (pelo menos em minhas plataformas), mas eu prefiro usar uma solução compatível com os padrões.

Preemptivo: Eu sei que poderia escrever uma pequena função. Estou tentando fazer isso usando macros.

Edit : Aqui está um exemplo (embora simples) de por que eu gostaria de 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);

Isso adiciona automaticamente uma nova linha às minhas instruções de log BAR (), supondo que fmtsempre seja uma string C com aspas duplas. NÃO imprime a nova linha como um printf () separado, o que é vantajoso se o log estiver em buffer de linha e proveniente de várias fontes de forma assíncrona.


3
Por que usar em BARvez de, FOOem primeiro lugar?
GManNickG

@GMan: Eu adicionado um exemplo no final
jwd

5
@GMan: Leia a última frase (:
jwd 28/04

7
Esse recurso foi proposto para inclusão no C2x.
Leushenko

2
@zwol, a versão mais recente enviada ao WG14 se parece com isso , que usa uma nova sintaxe baseada na __VA_OPT__palavra - chave. Isso já foi "adotado" pelo C ++, então espero que o C siga o exemplo. (não sei se isso significa que ele foi acelerado em C ++ 17 ou se ele é definido para C ++ 20 embora)
Leushenko

Respostas:


66

É possível evitar o uso da ,##__VA_ARGS__extensão do GCC se você estiver disposto a aceitar algum limite superior codificado no número de argumentos que pode passar para sua macro variável, conforme descrito na resposta de Richard Hansen a esta pergunta . Se você não deseja ter esse limite, no entanto, pelo que sei, não é possível usar apenas recursos de pré-processador especificados em C99; você deve usar alguma extensão para o idioma. clang e icc adotaram essa extensão do GCC, mas a MSVC não.

Em 2001, escrevi a extensão do GCC para padronização (e a extensão relacionada que permite que você use um nome que não seja __VA_ARGS__o parâmetro restante) no documento N976 , mas que não recebeu nenhuma resposta do comitê; Eu nem sei se alguém leu. Em 2016, ela foi proposta novamente no N2023 e incentivo qualquer pessoa que saiba como essa proposta nos informará nos comentários.


2
A julgar pela minha deficiência para encontrar uma solução na web e a falta de respostas aqui, eu acho que você está certo):
jwd

2
É n976 o que você está se referindo? Eu procurei o resto do grupo C de trabalho 's documentos para uma resposta, mas nunca encontrou um. Não estava na agenda da reunião subsequente . O único outro impacto sobre esse tópico foi o comentário da Noruega no 4 no n868, antes de C99 ser ratificado (novamente sem discussão posterior).
Richard Hansen

4
Sim, especificamente a segunda metade disso. Pode ter havido discussão, comp.std.cmas não consegui encontrar nenhuma nos Grupos do Google agora. certamente nunca recebeu nenhuma atenção do comitê real (ou, se recebeu, ninguém nunca me falou sobre isso).
22412 zwol

1
Receio não ter uma prova, nem sou mais a pessoa certa para tentar pensar em uma. Escrevi metade do pré-processador do GCC, mas isso foi há mais de dez anos e nunca teria pensado no truque de contagem de argumentos abaixo.
Zwol 31/01

6
Esta extensão funciona com os compiladores clang e intel icc, bem como o gcc.
ACyclic

112

Há um truque de contagem de argumentos que você pode usar.

Aqui está uma maneira compatível com o padrão de implementar o segundo BAR()exemplo na pergunta do 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 mesmo truque é usado para:

Explicação

A estratégia é separar __VA_ARGS__o primeiro argumento e o restante (se houver). Isso possibilita a inserção de itens após o primeiro argumento, mas antes do segundo (se houver).

FIRST()

Essa macro simplesmente se expande para o primeiro argumento, descartando o restante.

A implementação é direta. O throwawayargumento garante que FIRST_HELPER()obtenha dois argumentos, o que é necessário porque as ...necessidades de pelo menos um. Com um argumento, ele se expande da seguinte forma:

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

Com dois ou mais, ele se expande da seguinte forma:

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

REST()

Essa macro se expande para tudo, exceto o primeiro argumento (incluindo a vírgula após o primeiro argumento, se houver mais de um argumento).

A implementação dessa macro é muito mais complicada. A estratégia geral é contar o número de argumentos (um ou mais de um) e depois expandir para um REST_HELPER_ONE()(se apenas um argumento for fornecido) ou REST_HELPER_TWOORMORE()(se dois ou mais argumentos forem fornecidos). REST_HELPER_ONE()simplesmente se expande para nada - não há argumentos após o primeiro; portanto, os argumentos restantes são o conjunto vazio. REST_HELPER_TWOORMORE()também é direto - se expande para uma vírgula seguida por tudo, exceto o primeiro argumento.

Os argumentos são contados usando a NUM()macro. Essa macro se expande para ONEse apenas um argumento for fornecido, TWOORMOREse entre dois e nove argumentos forem apresentados e interromper se for fornecido 10 ou mais argumentos (porque se expande para o 10º argumento).

A NUM()macro usa a SELECT_10TH()macro para determinar o número de argumentos. Como o próprio nome indica, SELECT_10TH()simplesmente se expande para o seu décimo argumento. Por causa das reticências, é SELECT_10TH()necessário passar pelo menos 11 argumentos (o padrão diz que deve haver pelo menos um argumento para as reticências). É por isso que NUM()passa throwawaycomo o último argumento (sem ele, passar um argumento para NUM()resultaria em apenas 10 argumentos sendo passados SELECT_10TH(), o que violaria o padrão).

A seleção de um REST_HELPER_ONE()ou REST_HELPER_TWOORMORE()é feita concatenando REST_HELPER_com a expansão de NUM(__VA_ARGS__)in REST_HELPER2(). Observe que o objetivo de REST_HELPER()é garantir que ele NUM(__VA_ARGS__)seja totalmente expandido antes de ser concatenado REST_HELPER_.

A expansão com um argumento é a seguinte:

  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. (vazio)

A expansão com dois ou mais argumentos é a seguinte:

  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
Note que este irá falhar se você chamar BAR com 10 ou mais argumentos, e embora seja relativamente fácil para estender a mais argumentos, ele sempre terá um limite superior sobre o número de argumentos que podem lidar com
Chris Dodd

2
@ChrisDodd: Correto. Infelizmente, não parece haver uma maneira de evitar um limite no número de argumentos sem depender de extensões específicas do compilador. Além disso, não conheço uma maneira de testar com segurança se há muitos argumentos (para que uma mensagem de erro útil do compilador possa ser impressa, em vez de uma falha estranha).
Richard Hansen

17

Não é uma solução geral, mas no caso de printf você pode acrescentar uma nova linha como:

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

Eu acredito que ignora quaisquer argumentos extras que não são referenciados na string de formato. Então você provavelmente poderia se dar bem com:

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

Não acredito que o C99 foi aprovado sem uma maneira padrão de fazer isso. AFAICT também existe o problema no C ++ 11.


o problema com esse 0 extra é que ele acabará no código se chamar a função vararg. Verifique a solução fornecida por Richard Hansen
Pavel P

@Pavel está correto sobre o segundo exemplo, mas o primeiro funciona muito bem. +1.
kirbyfan64sos

11

Existe uma maneira de lidar com esse caso específico usando algo como Boost.Preprocessor . Você pode usar BOOST_PP_VARIADIC_SIZE para verificar o tamanho da lista de argumentos e, em seguida, expandir condicionalmente para outra macro. A única desvantagem disso é que ele não pode distinguir entre 0 e 1 argumento, e a razão para isso fica clara quando você considera o seguinte:

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

A lista de argumentos de macro vazia na verdade consiste em um argumento que está vazio.

Nesse caso, temos sorte, já que a macro desejada sempre possui pelo menos 1 argumento, podemos implementá-la como duas macros de "sobrecarga":

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

E então outra macro para alternar entre eles, como:

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

ou

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

O que você achar mais legível (eu prefiro o primeiro, pois ele fornece uma forma geral de sobrecarregar macros no número de argumentos).

Também é possível fazer isso com uma única macro, acessando e alterando a lista de argumentos das variáveis, mas é muito menos legível e muito específica para esse 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__)))) \
    /**/

Além disso, por que não há BOOST_PP_ARRAY_ENUM_TRAILING? Isso tornaria essa solução muito menos horrível.

Edit: Tudo bem, aqui está um BOOST_PP_ARRAY_ENUM_TRAILING e uma versão que o utiliza (agora é minha solução 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
É bom aprender sobre o Boost.Preprocessor, +1. Observe que BOOST_PP_VARIADIC_SIZE()usa o mesmo truque de contagem de argumentos que documentei na minha resposta e tem a mesma limitação (será quebrada se você passar mais do que um certo número de argumentos).
21713 Richard Hansen

1
Sim, vi que sua abordagem era a mesma usada pelo Boost, mas a solução de reforço é muito bem mantida e possui muitos outros recursos realmente úteis para o desenvolvimento de macros mais sofisticadas. O material de recursão é particularmente interessante (e usado nos bastidores na última abordagem que usa BOOST_PP_ARRAY_ENUM).
DRayX

1
Uma resposta Boost que realmente se aplica à tag c ! Viva!
Justin

6

Uma macro muito simples que estou usando para impressão de depuração:

#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;
}

Não importa quantos argumentos são passados ​​para o DBG, não há aviso de c99.

O truque é __DBG_INTadicionar um parâmetro fictício para ...sempre ter pelo menos um argumento e c99 estiver satisfeito.


5

Encontrei recentemente um problema semelhante e acredito que há uma solução.

A idéia principal é que existe uma maneira de escrever uma macro NUM_ARGSpara contar o número de argumentos que uma macro variável é fornecida. Você pode usar uma variação de NUM_ARGSpara construir NUM_ARGS_CEILING2, o que pode indicar se uma macro variável recebe 1 argumento ou 2 ou mais argumentos. Em seguida, você pode escrever sua Barmacro para que ela use NUM_ARGS_CEILING2e CONCATenvie seus argumentos para uma das duas macros auxiliares: uma que espera exatamente 1 argumento e outra que espera um número variável de argumentos maior que 1.

Aqui está um exemplo em que eu uso esse truque para escrever a macro UNIMPLEMENTED, que é muito semelhante a BAR:

PASSO 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

PASSO 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)

Passo 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__)

ETAPA 3:

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

Onde o CONCAT é implementado da maneira usual. Como uma dica rápida, se o exposto acima parece confuso: o objetivo da CONCAT é expandir para outra "chamada" de macro.

Observe que NUM_ARGS em si não é usado. Eu apenas o incluí para ilustrar o truque básico aqui. Veja o blog P99 de Jens Gustedt para um bom tratamento.

Duas notas:

  • NUM_ARGS é limitado no número de argumentos que ele manipula. O meu só suporta até 20, embora o número seja totalmente arbitrário.

  • NUM_ARGS, como mostrado, tem uma armadilha em que retorna 1 quando recebe 0 argumentos. A essência disso é que NUM_ARGS conta tecnicamente [vírgulas + 1], e não args. Nesse caso em particular, ele realmente funciona em nossa vantagem. _UNIMPLEMENTED1 manipulará bem um token vazio e evita que seja necessário escrever _UNIMPLEMENTED0. Gustedt tem uma solução alternativa para isso também, embora eu não o tenha usado e não tenho certeza se funcionaria para o que estamos fazendo aqui.


+1 para trazer o truque argumento contagem, -1 para ser realmente difícil de seguir
Richard Hansen

Os comentários que você adicionou foram uma melhoria, mas ainda existem vários problemas: 1. Você discute e define, NUM_ARGSmas não o usa. 2. Qual é o propósito UNIMPLEMENTED? 3. Você nunca resolve o problema de exemplo na pergunta. 4. Percorrer a expansão, um passo de cada vez, ilustraria como funciona e explicaria o papel de cada macro auxiliar. 5. Discutir 0 argumentos é perturbador; o OP estava perguntando sobre a conformidade com os padrões e 0 argumentos é proibido (C99 6.10.3p4). 6. Etapa 1.5? Por que não o passo 2? 7. "Passos" implica ações que ocorrem sequencialmente; isso é apenas código.
Richard Hansen

8. Você vincula o blog inteiro, não a publicação relevante. Não consegui encontrar a postagem a que você estava se referindo. 9. O último parágrafo é estranho: este método é obscuro; é por isso que ninguém havia postado uma solução correta antes. Além disso, se funcionar e aderir ao padrão, a resposta de Zack deve estar errada. 10. Você deve definir CONCAT()- não assuma que os leitores sabem como isso funciona.
Richard Hansen

(Por favor, não interprete esse feedback como um ataque - eu realmente queria aprovar sua resposta, mas não me senti à vontade para fazê-lo, a menos que fosse mais fácil de entender. Se você puder melhorar a clareza de sua resposta, vote o seu e exclua o meu.)
Richard Hansen

2
Eu nunca teria pensado nessa abordagem e escrevi aproximadamente metade do atual pré-processador do GCC! Dito isto, ainda digo que "não há uma maneira padrão de obter esse efeito" porque as técnicas de você e de Richard impõem um limite superior ao número de argumentos para a macro.
Zwol

2

Esta é a versão simplificada que eu uso. É baseado nas grandes técnicas das outras respostas aqui, muitos adereços para elas:

#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;
}

É isso aí.

Como em outras soluções, isso é limitado ao número de argumentos da macro. Para dar suporte a mais, adicione mais parâmetros _SELECTe mais Nargumentos. Os nomes dos argumentos são contados para baixo (em vez de para cima) para servir como um lembrete de que o SUFFIXargumento baseado em contagem é fornecido na ordem inversa.

Esta solução trata 0 argumentos como se fosse 1 argumento. Então, BAR()nominalmente "funciona", porque expande para _SELECT(_BAR,,N,N,N,N,1)(), que expande para _BAR_1()(), que expande para printf("\n").

Se desejar, você pode ser criativo com o uso de _SELECTe fornecer macros diferentes para diferentes números de argumentos. Por exemplo, aqui temos uma macro LOG que usa um argumento 'level' antes do formato. Se o formato estiver ausente, ele registrará "(sem mensagem)"; se houver apenas 1 argumento, registrará através de "% s"; caso contrário, tratará o argumento de formato como uma string de formato printf para os 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
*/

Isso ainda dispara um aviso quando compilado com -pedantic.
PSKocik 11/1118

1

Na sua situação (pelo menos 1 argumento presente, nunca 0), você pode definir BARcomo BAR(...)usar os de Jens Gustedt HAS_COMMA(...) para detectar uma vírgula e depois despachar para BAR0(Fmt)ou de BAR1(Fmt,...)acordo.

Este:

#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 -pedanticsem 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);
}

Experimente online!

Assume:

  • Nenhum argumento contém vírgula ou colchete
  • Nenhum argumento contém A~ G(pode mudar o nome para hard_collide ones)

A no arg contain commalimitação pode ser contornada através da verificação de multi após mais alguns passes, mas no bracketainda está lá
l4m2

-2

A solução padrão é usar em FOOvez de BAR. Existem alguns casos estranhos de reorganização de argumentos que provavelmente não podem fazer por você (embora eu aposto que alguém pode inventar truques inteligentes para desmontar e remontar __VA_ARGS__condicionalmente com base no número de argumentos contidos nele!), Mas geralmente usando FOO"geralmente" simplesmente funciona.


1
A pergunta era "existe uma maneira compatível com os padrões para obter esse comportamento?"
Marsh Ray

2
E a questão incluiu uma justificativa para não usar o FOO há muito tempo.
Pavel Šimerda 7/11
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.