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 fmt
sempre 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.
BAR
vez de,FOO
em primeiro lugar?__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)Respostas:
É 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.fonte
comp.std.c
mas 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).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:Este mesmo truque é usado para:
__VA_ARGS__
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
throwaway
argumento garante queFIRST_HELPER()
obtenha dois argumentos, o que é necessário porque as...
necessidades de pelo menos um. Com um argumento, ele se expande da seguinte forma:FIRST(firstarg)
FIRST_HELPER(firstarg, throwaway)
firstarg
Com dois ou mais, ele se expande da seguinte forma:
FIRST(firstarg, secondarg, thirdarg)
FIRST_HELPER(firstarg, secondarg, thirdarg, throwaway)
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) ouREST_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 paraONE
se apenas um argumento for fornecido,TWOORMORE
se 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 aSELECT_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 queNUM()
passathrowaway
como o último argumento (sem ele, passar um argumento paraNUM()
resultaria em apenas 10 argumentos sendo passadosSELECT_10TH()
, o que violaria o padrão).A seleção de um
REST_HELPER_ONE()
ouREST_HELPER_TWOORMORE()
é feita concatenandoREST_HELPER_
com a expansão deNUM(__VA_ARGS__)
inREST_HELPER2()
. Observe que o objetivo deREST_HELPER()
é garantir que eleNUM(__VA_ARGS__)
seja totalmente expandido antes de ser concatenadoREST_HELPER_
.A expansão com um argumento é a seguinte:
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)
A expansão com dois ou mais argumentos é a seguinte:
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
fonte
Não é uma solução geral, mas no caso de printf você pode acrescentar uma nova linha como:
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:
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.
fonte
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:
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":
E então outra macro para alternar entre eles, como:
ou
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:
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):
fonte
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).Uma macro muito simples que estou usando para impressão de depuração:
Não importa quantos argumentos são passados para o DBG, não há aviso de c99.
O truque é
__DBG_INT
adicionar um parâmetro fictício para...
sempre ter pelo menos um argumento e c99 estiver satisfeito.fonte
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_ARGS
para contar o número de argumentos que uma macro variável é fornecida. Você pode usar uma variação deNUM_ARGS
para construirNUM_ARGS_CEILING2
, o que pode indicar se uma macro variável recebe 1 argumento ou 2 ou mais argumentos. Em seguida, você pode escrever suaBar
macro para que ela useNUM_ARGS_CEILING2
eCONCAT
envie 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 aBAR
:PASSO 1:
PASSO 1.5:
Passo 2:
ETAPA 3:
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.
fonte
NUM_ARGS
mas não o usa. 2. Qual é o propósitoUNIMPLEMENTED
? 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.CONCAT()
- não assuma que os leitores sabem como isso funciona.Esta é a versão simplificada que eu uso. É baseado nas grandes técnicas das outras respostas aqui, muitos adereços para elas:
É 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
_SELECT
e maisN
argumentos. Os nomes dos argumentos são contados para baixo (em vez de para cima) para servir como um lembrete de que oSUFFIX
argumento 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 paraprintf("\n")
.Se desejar, você pode ser criativo com o uso de
_SELECT
e 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.fonte
Na sua situação (pelo menos 1 argumento presente, nunca 0), você pode definir
BAR
comoBAR(...)
usar os de Jens GustedtHAS_COMMA(...)
para detectar uma vírgula e depois despachar paraBAR0(Fmt)
ou deBAR1(Fmt,...)
acordo.Este:
compila
-pedantic
sem aviso.fonte
C (gcc) , 762 bytes
Experimente online!
Assume:
A
~G
(pode mudar o nome para hard_collide ones)fonte
no arg contain comma
limitação pode ser contornada através da verificação de multi após mais alguns passes, masno bracket
ainda está láA solução padrão é usar em
FOO
vez deBAR
. 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 usandoFOO
"geralmente" simplesmente funciona.fonte