web-dev-qa-db-fra.com

Les macros peuvent-elles être surchargées par le nombre d'arguments?

Comment fonctionne this ? Comment une macro variadique C99/C++ 11 peut-elle être implémentée pour s’étendre à différentes choses sur la seule base du nombre d’arguments qui lui sont donnés?

40
Potatoswatter

(Edit: Voir la fin pour une solution prête à l'emploi.)

Pour obtenir une macro surchargée, nous avons d’abord besoin d’une macro permettant de choisir entre plusieurs implémentations. Cette partie n'utilise pas de macro variadique. Ensuite, une macro variadique qui compte de manière générique ses arguments produit un sélecteur. Brancher le nombre d'arguments dans un répartiteur génère une macro surchargée.

Avertissement: Ce système ne peut pas faire la différence entre zéro et un argument car il est aucune différence entre aucun argument et un seul argument vide. Ils ressemblent tous les deux à MACRO().


Pour sélectionner une implémentation, utilisez l'opérateur de caténation de macros avec une série de macros de type fonction.

#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()

Étant donné que l'opérateur ## supprime l'expansion des macros de ses arguments, il est préférable de l'envelopper dans une autre macro.

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

Pour compter les arguments, utilisez __VA_ARGS__ pour décaler les arguments comme suit (c'est la partie la plus intelligente):

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

Code de la bibliothèque:

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

Usage:

#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

Je voudrais poster ceci en tant que commentaire sur le post de Potatoswatter, mais c'est trop long et nécessite une liste de code. 

Voici un peu de code Perl pour générer un ensemble de macros destinées à être surchargées. 

$ 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'

Voici la sortie du 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)

Ce sont les sections de groupes de surcharges de macros (régulièrement structurées) utilisées pour générer des macros FOR_EACH (également appelé FE) pouvant éventuellement envoyer une macro WHAT avec un nombre arbitraire d'arguments constants (A1, A2...) un nombre arbitraire d'arguments dans une liste, ainsi qu'un index dans le bon ordre (une implémentation naïve sans utiliser quelque chose comme SELECT pour la surcharge produirait des index inversés). 

À titre d’exemple, la section restante (la partie «cas de base» non régulière du deuxième bloc) se présente comme suit: 

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

L’utilité de ceci peut peut-être être remise en question (je l’ai construite parce que j’en ai vu un usage ...), et cela ne répond pas non plus directement à la question du PO (en fait, c’est plutôt le contraire: une construction foreach fait le idem chose à tous les arguments variadiques ...), mais je pensais juste que la technique était assez intéressante (ainsi que totalement horrible à certains égards) et permettait à un certain pouvoir d’expression en utilisant le pré-processeur et il sera possible de générer un code machine très efficace de cette manière. Je pense que cela constitue également un exemple poignant de la raison pour laquelle je pense personnellement que le pré-processeur C peut encore être amélioré. 

Je veux dire par là que le pré-processeur C est une abomination absolue et que nous devrions probablement le supprimer et recommencer à zéro :)

4
Steven Lu

Voici une amélioration par rapport à la réponse de Potatoswatter , qui peut différencier un argument de zéro.

En un mot, lorsque __VA_ARGS__ est vide, EXPAND __VA_ARGS__ () dans la macro VA_SIZE devient EXPAND () et est remplacé par 6 virgules. Donc, VA_SIZE... devient COMPOSE( GET_COUNT, (,,,,,, , 0, 6, 5, 4, 3, 2, 1) ), et cela devient GET_COUNT (,,,,,, , 0, 6, 5, 4, 3, 2, 1) et renvoie 0.

D'autre part, lorsque __VA_ARGS__ est par exemple, int, 5, EXPAND __VA_ARGS__ () devient EXPAND int, 5 (). Donc, VA_SIZE... devient COMPOSE( GET_COUNT, (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) ), qui devient GET_COUNT (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) et renvoie 2, comme décrit dans la réponse de Potatoswatter.

J'ai eu l'idée EXPAND de la réponse de Jason Dang .

Code de la bibliothèque:

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

Usage:

#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

Bien que cela ait déjà été répondu, j'en ai préparé une version très courte. J'espère que cela peut aider.

La mise en oeuvre

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

Personnalisation

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

Usage

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

J'ai étendu la solution de Potatowater pour éviter le problème iso c99 requires rest arguments to be used lorsque le commutateur de compilation de gcc -pedantic est utilisé.

Bibliothèque

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

Personnalisation

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

Utilisation

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