ti-enxame.com

As macros podem ser sobrecarregadas pelo número de argumentos?

Como o this work? Como uma macro variadica C99/C++ 11 pode ser implementada para expandir para coisas diferentes com base única em quantos argumentos são dados a ela?

40
Potatoswatter

(Edit: Veja o final para uma solução pronta).

Para obter uma macro sobrecarregada, primeiro precisamos de uma macro que selecione entre várias implementações. Esta parte não usa uma macro variadica. Em seguida, uma macro variadica que genericamente conta seus argumentos produz um seletor. Conectar a contagem de argumentos a um dispatcher produz uma macro sobrecarregada.

Advertência: Este sistema não pode dizer a diferença entre zero e um argumento porque lá é nenhuma diferença entre nenhum argumento, e um único argumento vazio. Ambos se parecem com MACRO().


Para selecionar entre implementações, use o operador de catenação de macro com uma série de macros de função.

#define select( selector, ... ) impl ## _ ## selector( __VA_ARGS__ )
#define impl_1() meh
#define impl_2( abc, xyz ) # abc "wizza" xyz()
//etc

// usage: select( 1 ) => impl_1() => meh
//        select( 2, huz, bar ) => impl_2( huzza, bar ) => "huz" "wizza" bar()

Como o operador ## suprime a expansão de macro de seus argumentos, é melhor envolvê-lo em outra macro.

#define CAT( A, B ) A ## B
#define SELECT( NAME, NUM ) CAT( NAME ## _, NUM )

Para contar argumentos, use __VA_ARGS__ para mudar argumentos como este (esta é a parte inteligente):

#define GET_COUNT( _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT
#define VA_SIZE( ... ) GET_COUNT( __VA_ARGS__, 6, 5, 4, 3, 2, 1 )

Código da biblioteca:

#define CAT( A, B ) A ## B
#define SELECT( NAME, NUM ) CAT( NAME ## _, NUM )

#define GET_COUNT( _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT
#define VA_SIZE( ... ) GET_COUNT( __VA_ARGS__, 6, 5, 4, 3, 2, 1 )

#define VA_SELECT( NAME, ... ) SELECT( NAME, VA_SIZE(__VA_ARGS__) )(__VA_ARGS__)

Uso:

#define MY_OVERLOADED( ... ) VA_SELECT( MY_OVERLOADED, __VA_ARGS__ )
#define MY_OVERLOADED_1( X ) foo< X >
#define MY_OVERLOADED_2( X, Y ) bar< X >( Y )
#define MY_OVERLOADED_3( X, Y, Z ) bang_ ## X< Y >.Z()
57
Potatoswatter

Eu colocaria isso como um comentário para o post do Potatoswatter, mas é muito longo e requer uma listagem de código. 

Aqui está um pouco de código Perl para gerar um conjunto de macros que devem ser macros sobrecarregadas. 

$ Perl -le 'map{
        $arity = $_; map {
                $ar = 2 + $arity + $_; $arm = $ar - 1; $arlist = join("", map{"A$_, "} 1..$arity); $warlist = "WHAT, $arlist";
                @li = map {"_$_"} 0..$_; $lis = join(", ", @li); $lim = pop @li; $lims = join(", ", @li);
                print "#define FEI_${arity}A_$ar($warlist$lis) FEI_${arity}A_$arm($warlist$lims) WHAT($_, $arlist$lim)"
        } 1..3; print ""
} 0..4'

Aqui está a saída do script:

#define FEI_0A_3(WHAT, _0, _1) FEI_0A_2(WHAT, _0) WHAT(1, _1)
#define FEI_0A_4(WHAT, _0, _1, _2) FEI_0A_3(WHAT, _0, _1) WHAT(2, _2)
#define FEI_0A_5(WHAT, _0, _1, _2, _3) FEI_0A_4(WHAT, _0, _1, _2) WHAT(3, _3)

#define FEI_1A_4(WHAT, A1, _0, _1) FEI_1A_3(WHAT, A1, _0) WHAT(1, A1, _1)
#define FEI_1A_5(WHAT, A1, _0, _1, _2) FEI_1A_4(WHAT, A1, _0, _1) WHAT(2, A1, _2)
#define FEI_1A_6(WHAT, A1, _0, _1, _2, _3) FEI_1A_5(WHAT, A1, _0, _1, _2) WHAT(3, A1, _3)

#define FEI_2A_5(WHAT, A1, A2, _0, _1) FEI_2A_4(WHAT, A1, A2, _0) WHAT(1, A1, A2, _1)
#define FEI_2A_6(WHAT, A1, A2, _0, _1, _2) FEI_2A_5(WHAT, A1, A2, _0, _1) WHAT(2, A1, A2, _2)
#define FEI_2A_7(WHAT, A1, A2, _0, _1, _2, _3) FEI_2A_6(WHAT, A1, A2, _0, _1, _2) WHAT(3, A1, A2, _3)

#define FEI_3A_6(WHAT, A1, A2, A3, _0, _1) FEI_3A_5(WHAT, A1, A2, A3, _0) WHAT(1, A1, A2, A3, _1)
#define FEI_3A_7(WHAT, A1, A2, A3, _0, _1, _2) FEI_3A_6(WHAT, A1, A2, A3, _0, _1) WHAT(2, A1, A2, A3, _2)
#define FEI_3A_8(WHAT, A1, A2, A3, _0, _1, _2, _3) FEI_3A_7(WHAT, A1, A2, A3, _0, _1, _2) WHAT(3, A1, A2, A3, _3)

#define FEI_4A_7(WHAT, A1, A2, A3, A4, _0, _1) FEI_4A_6(WHAT, A1, A2, A3, A4, _0) WHAT(1, A1, A2, A3, A4, _1)
#define FEI_4A_8(WHAT, A1, A2, A3, A4, _0, _1, _2) FEI_4A_7(WHAT, A1, A2, A3, A4, _0, _1) WHAT(2, A1, A2, A3, A4, _2)
#define FEI_4A_9(WHAT, A1, A2, A3, A4, _0, _1, _2, _3) FEI_4A_8(WHAT, A1, A2, A3, A4, _0, _1, _2) WHAT(3, A1, A2, A3, A4, _3)

Estas são as (seções estruturadas regularmente) grupos de macro sobrecargas que são usadas para gerar macros FOR_EACH (também conhecidas como FE) que podem despachar uma macro WHAT opcionalmente com um número arbitrário de argumentos constantes (A1, A2...) além de um número arbitrário de argumentos em uma lista, junto com um índice na ordenação adequada (uma implementação ingênua sem usar algo como SELECT para sobrecarga produziria índices invertidos). 

Como exemplo, a seção restante (a parte não-básica do "segundo caso" do segundo bloco) se parece com o seguinte: 

#define FE_INDEXED_1ARG(...) VA_SELECT(FEI_1A, __VA_ARGS__)
#define FEI_1A_3(WHAT, A1, _0) WHAT(0, A1, _0)

A utilidade disso talvez possa ser questionada (construí-lo porque vi um uso para ele ...), e isso também não responde diretamente à pergunta do OP (de fato, faz o oposto - um construto foreach faz o same thing para todos os argumentos variadicos ...), mas eu apenas pensei que a técnica é bem interessante (assim como completamente horripilante em alguns aspectos) e permite bastante algum poder expressivo usando o pré-processador e É possível gerar um código de máquina muito eficiente dessa maneira. Eu acho que também serve como um exemplo pungente do porquê eu pessoalmente acho que o pré-processador C ainda tem espaço para melhorias. 

Com isso eu quero dizer que o pré-processador C é uma abominação absoluta e provavelmente devemos descartá-lo e começar do zero :)

4
Steven Lu

A seguir, há uma melhoria sobre resposta de Potatoswatter , que can diferencia entre zero e um argumento.

Em suma, quando __VA_ARGS__ está vazio, EXPAND __VA_ARGS__ () dentro de VA_SIZE macro torna EXPAND () e é substituído por 6 vírgulas. Então, VA_SIZE... se torna COMPOSE( GET_COUNT, (,,,,,, , 0, 6, 5, 4, 3, 2, 1) ), e isso se torna GET_COUNT (,,,,,, , 0, 6, 5, 4, 3, 2, 1) e retorna 0.

Por outro lado, quando __VA_ARGS__ é, por exemplo, int, 5, EXPAND __VA_ARGS__ () se torna EXPAND int, 5 (). Então, VA_SIZE... torna-se COMPOSE( GET_COUNT, (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) ), que se torna GET_COUNT (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) e retorna 2, conforme descrito na resposta de Potatoswatter.

Eu obtive a idéia EXPAND de resposta de Jason Dang .

Código da biblioteca:

#define CAT( A, B ) A ## B
#define SELECT( NAME, NUM ) CAT( NAME ## _, NUM )
#define COMPOSE( NAME, ARGS ) NAME ARGS

#define GET_COUNT( _0, _1, _2, _3, _4, _5, _6 /* ad nauseam */, COUNT, ... ) COUNT
#define EXPAND() ,,,,,, // 6 commas (or 7 empty tokens)
#define VA_SIZE( ... ) COMPOSE( GET_COUNT, (EXPAND __VA_ARGS__ (), 0, 6, 5, 4, 3, 2, 1) )

#define VA_SELECT( NAME, ... ) SELECT( NAME, VA_SIZE(__VA_ARGS__) )(__VA_ARGS__)

Uso:

#define MY_OVERLOADED( ... ) VA_SELECT( MY_OVERLOADED, __VA_ARGS__ )

#define MY_OVERLOADED_0( ) meh()
#define MY_OVERLOADED_1( X ) foo< X >
#define MY_OVERLOADED_2( X, Y ) bar< X >( Y )
#define MY_OVERLOADED_3( X, Y, Z ) bang_ ## X< Y >.Z()

MY_OVERLOADED()                // meh()
MY_OVERLOADED(bool)            // foo< bool >
MY_OVERLOADED(int, 5)          // bar< int >( 5 )
MY_OVERLOADED(me, double, now) // bang_me< double >.now()
4
Innocent Bystander

Embora já tenha sido respondida, preparei uma versão bem resumida. Espero que isso ajude.

Implementação

// Variable Argument Macro (VA_MACRO) upto 6 arguments
#define NUM_ARGS_(_1, _2, _3, _4, _5, _6, TOTAL, ...) TOTAL
#define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 6, 5, 4, 3, 2, 1)

#define CONCATE_(X, Y) X##Y  // Fixed the double '_' from previous code
#define CONCATE(MACRO, NUMBER) CONCATE_(MACRO, NUMBER)
#define VA_MACRO(MACRO, ...) CONCATE(MACRO, NUM_ARGS(__VA_ARGS__))(__VA_ARGS__)

Costumização

// This is how user may define own set of variadic macros
#define MY_MACRO(...) VA_MACRO(MY_MACRO, __VA_ARGS__)
#define MY_MACRO1(_1) "One"
#define MY_MACRO2(_1, _2) "Two"
#define MY_MACRO3(_1, _2, _3) "Three"

Uso

// While using those, user needs to use only the main macro
int main ()
{
  auto one = MY_MACRO(1);
  auto two = MY_MACRO(1, 2); 
  auto three = MY_MACRO(1, 2, 3); 
}
2
iammilind

Eu estendi a solução da Potatowater para evitar o problema iso c99 requires rest arguments to be used quando o switch -pedantic do compilador do gcc está em uso.

Biblioteca

#define NUM_ARGS_(_1, _2, _3, _4, _5, _6, _7, _8, TOTAL, ...) TOTAL
#define NUM_ARGS(...) NUM_ARGS_(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
#define CONCATE_(X, Y) X##Y
#define CONCATE(MACRO, NUMBER) CONCATE_(MACRO, NUMBER)
#define VA_MACRO(MACRO, ...) CONCATE(MACRO, NUM_ARGS (__VA_ARGS__))(__VA_ARGS__)

Costumização

#define MY_OVERLOADED(...) VA_MACRO(MY_OVERLOADED, void, void, __VA_ARGS__)
#define MY_OVERLOADED0(s, t) MacroTest()
#define MY_OVERLOADED1(s, t, a) MacroTest( a)
#define MY_OVERLOADED2(s, t, a, b) MacroTest(a, b)
#define MY_OVERLOADED3(s, t, a, b, c) MacroTest(a, b, c)

uso

MY_OVERLOADED();
MY_OVERLOADED(1);
MY_OVERLOADED(11, 22);
MY_OVERLOADED(111, 222, 333);
0
Th. Thielemann