Existe alguma maneira de obter parâmetros opcionais com macros C ++? Algum tipo de sobrecarga também seria bom.
No you can't
Existe alguma maneira de obter parâmetros opcionais com macros C ++? Algum tipo de sobrecarga também seria bom.
No you can't
Respostas:
Aqui está uma maneira de fazer isso. Ele usa a lista de argumentos duas vezes, primeiro para formar o nome da macro auxiliar e, em seguida, para passar os argumentos para essa macro auxiliar. Ele usa um truque padrão para contar o número de argumentos para uma macro.
enum
{
plain = 0,
bold = 1,
italic = 2
};
void PrintString(const char* message, int size, int style)
{
}
#define PRINT_STRING_1_ARGS(message) PrintString(message, 0, 0)
#define PRINT_STRING_2_ARGS(message, size) PrintString(message, size, 0)
#define PRINT_STRING_3_ARGS(message, size, style) PrintString(message, size, style)
#define GET_4TH_ARG(arg1, arg2, arg3, arg4, ...) arg4
#define PRINT_STRING_MACRO_CHOOSER(...) \
GET_4TH_ARG(__VA_ARGS__, PRINT_STRING_3_ARGS, \
PRINT_STRING_2_ARGS, PRINT_STRING_1_ARGS, )
#define PRINT_STRING(...) PRINT_STRING_MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
int main(int argc, char * const argv[])
{
PRINT_STRING("Hello, World!");
PRINT_STRING("Hello, World!", 18);
PRINT_STRING("Hello, World!", 18, bold);
return 0;
}
Isso torna mais fácil para o chamador da macro, mas não para o gravador.
PRINT_STRING_MACRO_CHOOSER
mesmo necessário? Posso substituir seu corpo interno diretamente e chamar essa coisa toda de (__VA_ARGS__)
?
Com grande respeito a Derek Ledbetter por sua resposta - e com desculpas por reavivar uma velha questão.
Compreender o que estava fazendo e pegar em outro lugar a capacidade de preceder o __VA_ARGS__
com ##
me permitiu chegar a uma variação ...
// The multiple macros that you would need anyway [as per: Crazy Eddie]
#define XXX_0() <code for no arguments>
#define XXX_1(A) <code for one argument>
#define XXX_2(A,B) <code for two arguments>
#define XXX_3(A,B,C) <code for three arguments>
#define XXX_4(A,B,C,D) <code for four arguments>
// The interim macro that simply strips the excess and ends up with the required macro
#define XXX_X(x,A,B,C,D,FUNC, ...) FUNC
// The macro that the programmer uses
#define XXX(...) XXX_X(,##__VA_ARGS__,\
XXX_4(__VA_ARGS__),\
XXX_3(__VA_ARGS__),\
XXX_2(__VA_ARGS__),\
XXX_1(__VA_ARGS__),\
XXX_0(__VA_ARGS__)\
)
Para não especialistas como eu, que encontram a resposta, mas não conseguem ver como funciona, examinarei o processamento real, começando com o código a seguir ...
XXX();
XXX(1);
XXX(1,2);
XXX(1,2,3);
XXX(1,2,3,4);
XXX(1,2,3,4,5); // Not actually valid, but included to show the process
Torna-se...
XXX_X(, XXX_4(), XXX_3(), XXX_2(), XXX_1(), XXX_0() );
XXX_X(, 1, XXX_4(1), XXX_3(1), XXX_2(1), XXX_1(1), XXX_0(1) );
XXX_X(, 1, 2, XXX_4(1,2), XXX_3(1,2), XXX_2(1,2), XXX_1(1,2), XXX_0(1,2) );
XXX_X(, 1, 2, 3, XXX_4(1,2,3), XXX_3(1,2,3), XXX_2(1,2,3), XXX_1(1,2,3), XXX_0(1,2,3) );
XXX_X(, 1, 2, 3, 4, XXX_4(1,2,3,4), XXX_3(1,2,3,4), XXX_2(1,2,3,4), XXX_1(1,2,3,4), XXX_0(1,2,3,4) );
XXX_X(, 1, 2, 3, 4, 5, XXX_4(1,2,3,4,5), XXX_3(1,2,3,4,5), XXX_2(1,2,3,4,5), XXX_1(1,2,3,4,5), XXX_0(1,2,3,4,5) );
Que se torna apenas o sexto argumento ...
XXX_0();
XXX_1(1);
XXX_2(1,2);
XXX_3(1,2,3);
XXX_4(1,2,3,4);
5;
PS: Remova o #define para XXX_0 para obter um erro de compilação [ie: se uma opção sem argumento não for permitida].
PPS: Seria bom que as situações inválidas (por exemplo: 5) fossem algo que fornecesse um erro de compilação mais claro para o programador!
PPPS: Não sou especialista, por isso fico muito feliz em ouvir comentários (bons, ruins ou outros)!
XXX_X(,##__VA_ARGS__,` ...
XXX_X (, XXX_4 (), XXX_3 (), XXX_2 (), XXX_1 (), XXX_0 ()); `
As macros C ++ não mudaram de C. Uma vez que C não tinha sobrecarga e argumentos padrão para funções, certamente não os tinha para macros. Portanto, para responder à sua pergunta: não, esses recursos não existem para macros. Sua única opção é definir várias macros com nomes diferentes (ou não usar macros).
Como nota: em C ++, geralmente é considerado uma boa prática afastar-se das macros o máximo possível. Se você precisa de recursos como este, há uma boa chance de estar usando macros em excesso.
__FILE__
e __LINE__
e tal ...
Com o maior respeito a Derek Ledbetter , David Sorkovsky , Syphorlate por suas respostas, junto com o método engenhoso para detectar macro argumentos vazios por Jens Gustedt em
https://gustedt.wordpress.com/2010/06/08/detect-empty-macro-arguments/
finalmente, saio com algo que incorpora todos os truques, para que a solução
, ##__VA_ARGS__
para GCC / CLANG e deglutição implícita por ##__VA_ARGS__
para MSVC). Então fique à vontade para passar pelo que falta--std=c99
para o seu compilador se desejar =)Funciona razoavelmente em várias plataformas , pelo menos testado para
Para os preguiçosos, basta pular para o último deste post para copiar a fonte. Abaixo está a explicação detalhada, o que esperançosamente ajuda e inspira todas as pessoas que procuram o__VA_ARGS__
soluções como eu. =)
É assim que funciona. Primeiro defina a "função" visível ao usuário sobrecarregado, I nomeou-o create
, e a definição da função reais relacionados realCreate
, e as definições de macros com diferente número de argumentos CREATE_2
, CREATE_1
, CREATE_0
, como mostrado abaixo:
#define create(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
void realCreate(int x, int y)
{
printf("(%d, %d)\n", x, y);
}
#define CREATE_2(x, y) realCreate(x, y)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_0() CREATE_1(0)
A MACRO_CHOOSER(__VA_ARGS__)
parte em última análise resolve para os nomes de definição de macro e a segunda (__VA_ARGS__)
parte compreende suas listas de parâmetros. Assim, a chamada de um usuário para create(10)
resolve para CREATE_1(10)
, a CREATE_1
parte vem MACRO_CHOOSER(__VA_ARGS__)
e o(10)
parte vem do segundo (__VA_ARGS__)
.
O MACRO_CHOOSER
usa o truque de que, se __VA_ARGS__
estiver vazio, a seguinte expressão será concatenada em uma chamada de macro válida pelo pré-processador:
NO_ARG_EXPANDER __VA_ARGS__ () // simply shrinks to NO_ARG_EXPANDER()
Engenhosamente, podemos definir esta chamada de macro resultante como
#define NO_ARG_EXPANDER() ,,CREATE_0
Observe as duas vírgulas, elas serão explicadas em breve. A próxima macro útil é
#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER __VA_ARGS__ ())
então as chamadas de
create();
create(10);
create(20, 20);
são realmente expandidos para
CHOOSE_FROM_ARG_COUNT(,,CREATE_0)();
CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER 10 ())(10);
CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER 20, 20 ())(20, 20);
Como o nome da macro sugere, devemos contar o número de argumentos posteriormente. Aí vem outro truque: o pré-processador só faz uma simples substituição de texto. Ele infere o número de argumentos de uma chamada de macro apenas a partir do número de vírgulas que vê entre parênteses. Os "argumentos" reais separados por vírgulas não precisam ter sintaxe válida. Eles podem ser qualquer texto. Ou seja, no exemplo acima, NO_ARG_EXPANDER 10 ()
é contado como 1 argumento para a chamada do meio. NO_ARG_EXPANDER 20
e 20 ()
são contados como 2 argumentos para a chamada inferior, respectivamente.
Se usarmos as seguintes macros auxiliares para expandi-los ainda mais
##define CHOOSE_FROM_ARG_COUNT(...) \
FUNC_RECOMPOSER((__VA_ARGS__, CREATE_2, CREATE_1, ))
#define FUNC_RECOMPOSER(argsWithParentheses) \
FUNC_CHOOSER argsWithParentheses
A fuga ,
depois CREATE_1
é uma forma de contornar GCC / CLANG, suprimindo um erro (falsos positivos) dizendo que ISO C99 requires rest arguments to be used
quando passar -pedantic
para o seu compilador. O FUNC_RECOMPOSER
é uma solução alternativa para o MSVC ou não pode contar o número de argumentos (ou seja, vírgulas) entre parênteses de chamadas de macro corretamente. Os resultados são posteriormente resolvidos para
FUNC_CHOOSER (,,CREATE_0, CREATE_2, CREATE_1, )();
FUNC_CHOOSER (NO_ARG_EXPANDER 10 (), CREATE_2, CREATE_1, )(10);
FUNC_CHOOSER (NO_ARG_EXPANDER 20, 20 (), CREATE_2, CREATE_1, )(20, 20);
Como você deve ter percebido, a última e única etapa de que precisamos é empregar um truque de contagem de argumentos padrão para finalmente escolher os nomes de versão de macro desejados:
#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3
que resolve os resultados para
CREATE_0();
CREATE_1(10);
CREATE_2(20, 20);
e certamente nos dá as chamadas de função reais desejadas:
realCreate(0, 0);
realCreate(10, 10);
realCreate(20, 20);
Juntando tudo, com algum rearranjo de declarações para melhor legibilidade, toda a fonte do exemplo de 2 argumentos está aqui:
#include <stdio.h>
void realCreate(int x, int y)
{
printf("(%d, %d)\n", x, y);
}
#define CREATE_2(x, y) realCreate(x, y)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_0() CREATE_1(0)
#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
#define CHOOSE_FROM_ARG_COUNT(...) FUNC_RECOMPOSER((__VA_ARGS__, CREATE_2, CREATE_1, ))
#define NO_ARG_EXPANDER() ,,CREATE_0
#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER __VA_ARGS__ ())
#define create(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
int main()
{
create();
create(10);
create(20, 20);
//create(30, 30, 30); // Compilation error
return 0;
}
Embora complicado, feio, sobrecarregando o desenvolvedor de API, surge uma solução para sobrecarregar e definir parâmetros opcionais de funções C / C ++ para nós, loucos. O uso das APIs sobrecarregadas que estão chegando se tornou muito agradável e agradável. =)
Se houver qualquer outra simplificação possível dessa abordagem, por favor, me avise em
https://github.com/jason-deng/C99FunctionOverload
Mais uma vez, um agradecimento especial a todas as pessoas brilhantes que me inspiraram e levaram a concretizar este trabalho! =)
Para qualquer pessoa que pesquise com dificuldade alguma solução VA_NARGS que funcione com Visual C ++. A macro a seguir funcionou perfeitamente para mim (também com parâmetros zero!) No visual c ++ express 2010:
#define VA_NUM_ARGS_IMPL(_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,N,...) N
#define VA_NUM_ARGS_IMPL_(tuple) VA_NUM_ARGS_IMPL tuple
#define VA_NARGS(...) bool(#__VA_ARGS__) ? (VA_NUM_ARGS_IMPL_((__VA_ARGS__, 24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1))) : 0
Se você quiser uma macro com parâmetros opcionais, pode fazer:
//macro selection(vc++)
#define SELMACRO_IMPL(_1,_2,_3, N,...) N
#define SELMACRO_IMPL_(tuple) SELMACRO_IMPL tuple
#define mymacro1(var1) var1
#define mymacro2(var1,var2) var2*var1
#define mymacro3(var1,var2,var3) var1*var2*var3
#define mymacro(...) SELMACRO_IMPL_((__VA_ARGS__, mymacro3(__VA_ARGS__), mymacro2(__VA_ARGS__), mymacro1(__VA_ARGS__)))
Isso funcionou para mim também em vc. Mas não funciona para parâmetros zero.
int x=99;
x=mymacro(2);//2
x=mymacro(2,2);//4
x=mymacro(2,2,2);//8
unresolved external symbol _bool referenced in function _main
gcc
/ g++
suporta macros varargs, mas não acho que seja padrão, então use-o por sua própria conta e risco.
#include <stdio.h>
#define PP_NARG(...) \
PP_NARG_(__VA_ARGS__,PP_RSEQ_N())
#define PP_NARG_(...) \
PP_ARG_N(__VA_ARGS__)
#define PP_ARG_N( \
_1, _2, _3, _4, _5, _6, _7, _8, _9,_10, \
_11,_12,_13,_14,_15,_16,_17,_18,_19,_20, \
_21,_22,_23,_24,_25,_26,_27,_28,_29,_30, \
_31,_32,_33,_34,_35,_36,_37,_38,_39,_40, \
_41,_42,_43,_44,_45,_46,_47,_48,_49,_50, \
_51,_52,_53,_54,_55,_56,_57,_58,_59,_60, \
_61,_62,_63,N,...) N
#define PP_RSEQ_N() \
63,62,61,60, \
59,58,57,56,55,54,53,52,51,50, \
49,48,47,46,45,44,43,42,41,40, \
39,38,37,36,35,34,33,32,31,30, \
29,28,27,26,25,24,23,22,21,20, \
19,18,17,16,15,14,13,12,11,10, \
9,8,7,6,5,4,3,2,1,0
#define PP_CONCAT(a,b) PP_CONCAT_(a,b)
#define PP_CONCAT_(a,b) a ## b
#define THINK(...) PP_CONCAT(THINK_, PP_NARG(__VA_ARGS__))(__VA_ARGS__)
#define THINK_0() THINK_1("sector zz9 plural z alpha")
#define THINK_1(location) THINK_2(location, 42)
#define THINK_2(location,answer) THINK_3(location, answer, "deep thought")
#define THINK_3(location,answer,computer) \
printf ("The answer is %d. This was calculated by %s, and a computer to figure out what this"
" actually means will be build in %s\n", (answer), (computer), (location))
int
main (int argc, char *argv[])
{
THINK (); /* On compilers other than GCC you have to call with least one non-default argument */
}
AVISO LEGAL: Principalmente inofensivo.
:%s/MY_MACRO_/THINK_/g
:)
i686-apple-darwin10-g++-4.2.1 (GCC) 4.2.1 (Apple Inc. build 5664)
Não é exatamente para isso que o pré-processador foi projetado.
Dito isso, se você quiser entrar na área de um desafio sério de programação de macro com um mínimo de legibilidade, você deve dar uma olhada na biblioteca de pré-processador Boost . Afinal, não seria C ++ se não houvesse três níveis de programação totalmente compatíveis com Turing (pré-processador, metaprogramação de modelo e nível básico C ++)!
#define MY_MACRO_3(X,Y,Z) ...
#define MY_MACRO_2(X,Y) MY_MACRO(X,Y,5)
#define MY_MACRO_1(X) MY_MACRO(X,42,5)
Você sabe no momento da chamada quantos args vai passar, então não há realmente necessidade de sobrecarga.
Versão mais concisa do código de Derek Ledbetter:
enum
{
plain = 0,
bold = 1,
italic = 2
};
void PrintString(const char* message = NULL, int size = 0, int style = 0)
{
}
#define PRINT_STRING(...) PrintString(__VA_ARGS__)
int main(int argc, char * const argv[])
{
PRINT_STRING("Hello, World!");
PRINT_STRING("Hello, World!", 18);
PRINT_STRING("Hello, World!", 18, bold);
return 0;
}
Como um grande fã de monstros macro horríveis, eu queria expandir a resposta de Jason Deng e torná-la realmente utilizável. (Para o bem ou para o mal.) O original não é muito agradável de usar porque você precisa modificar a grande sopa de letrinhas toda vez que quiser fazer uma nova macro e é ainda pior se você precisar de diferentes quantidades de argumentos.
Então fiz uma versão com estes recursos:
Atualmente, acabei de criar no máximo 16 argumentos, mas se precisar de mais (realmente agora? Você está ficando bobo ...), pode editar FUNC_CHOOSER e CHOOSE_FROM_ARG_COUNT e adicionar algumas vírgulas a NO_ARG_EXPANDER.
Consulte a excelente resposta de Jason Deng para obter mais detalhes sobre a implementação, mas apenas colocarei o código aqui:
#include <stdio.h>
void realCreate(int x, int y)
{
printf("(%d, %d)\n", x, y);
}
// This part you put in some library header:
#define FUNC_CHOOSER(_f0, _f1, _f2, _f3, _f4, _f5, _f6, _f7, _f8, _f9, _f10, _f11, _f12, _f13, _f14, _f15, _f16, ...) _f16
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
#define CHOOSE_FROM_ARG_COUNT(F, ...) FUNC_RECOMPOSER((__VA_ARGS__, \
F##_16, F##_15, F##_14, F##_13, F##_12, F##_11, F##_10, F##_9, F##_8,\
F##_7, F##_6, F##_5, F##_4, F##_3, F##_2, F##_1, ))
#define NO_ARG_EXPANDER(FUNC) ,,,,,,,,,,,,,,,,FUNC ## _0
#define MACRO_CHOOSER(FUNC, ...) CHOOSE_FROM_ARG_COUNT(FUNC, NO_ARG_EXPANDER __VA_ARGS__ (FUNC))
#define MULTI_MACRO(FUNC, ...) MACRO_CHOOSER(FUNC, __VA_ARGS__)(__VA_ARGS__)
// When you need to make a macro with default arguments, use this:
#define create(...) MULTI_MACRO(CREATE, __VA_ARGS__)
#define CREATE_0() CREATE_1(0)
#define CREATE_1(x) CREATE_2(x, 0)
#define CREATE_2(x, y) \
do { \
/* put whatever code you want in the last macro */ \
realCreate(x, y); \
} while(0)
int main()
{
create();
create(10);
create(20, 20);
//create(30, 30, 30); // Compilation error
return 0;
}
Você pode usar BOOST_PP_OVERLOAD
de umboost
biblioteca.
Exemplo do documento de reforço oficial :
#include <boost/preprocessor/facilities/overload.hpp>
#include <boost/preprocessor/cat.hpp>
#include <boost/preprocessor/facilities/empty.hpp>
#include <boost/preprocessor/arithmetic/add.hpp>
#define MACRO_1(number) MACRO_2(number,10)
#define MACRO_2(number1,number2) BOOST_PP_ADD(number1,number2)
#if !BOOST_PP_VARIADICS_MSVC
#define MACRO_ADD_NUMBERS(...) BOOST_PP_OVERLOAD(MACRO_,__VA_ARGS__)(__VA_ARGS__)
#else
// or for Visual C++
#define MACRO_ADD_NUMBERS(...) \
BOOST_PP_CAT(BOOST_PP_OVERLOAD(MACRO_,__VA_ARGS__)(__VA_ARGS__),BOOST_PP_EMPTY())
#endif
MACRO_ADD_NUMBERS(5) // output is 15
MACRO_ADD_NUMBERS(3,6) // output is 9
Nenhum dos exemplos acima (de Derek Ledbetter, David Sorkovsky e Joe D) para contar argumentos com macros funcionou para mim usando o Microsoft VCC 10. O __VA_ARGS__
argumento é sempre considerado como um único argumento (token-izing com ##
ou não), então a mudança de argumento na qual esses exemplos se baseiam não funciona.
Portanto, uma resposta curta, como afirmado por muitos outros acima: não, você não pode sobrecarregar macros ou usar argumentos opcionais nelas.