Können Makros durch die Anzahl der Argumente überladen werden?

Können Makros durch die Anzahl der Argumente überladen werden?

(Bearbeiten:Siehe Ende für eine fertige Lösung.)

Um ein überladenes Makro zu erhalten, benötigen wir zunächst ein Makro, das zwischen mehreren Implementierungen auswählt. Dieser Teil verwendet kein variadisches Makro. Dann erzeugt ein variadisches Makro, das seine Argumente generisch zählt, einen Selektor. Das Einfügen des Arguments count in einen Dispatcher erzeugt ein überladenes Makro.

Vorsicht: Dieses System kann den Unterschied zwischen Null- und Eins-Argumenten nicht erkennen, weil es einen gibt kein Unterschied zwischen keinem Argument und einem einzelnen leeren Argument. Sie sehen beide aus wie MACRO() .

Um zwischen Implementierungen auszuwählen, verwenden Sie den Makroverkettungsoperator mit einer Reihe von funktionsähnlichen Makros.

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

Da die ## Operator die Makroerweiterung seiner Argumente unterdrückt, ist es besser, ihn in ein anderes Makro einzuschließen.

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

Um Argumente zu zählen, verwenden Sie __VA_ARGS__ um Argumente so zu verschieben (das ist der schlaue Teil):

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

Bibliothekscode:

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

Verwendung:

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

Es folgt eine Verbesserung der Antwort von Potatoswatter, die kann zwischen null und einem Argument unterscheiden.

Kurz gesagt, wenn __VA_ARGS__ ist leer, EXPAND __VA_ARGS__ () innerhalb von VA_SIZE Makro wird zu EXPAND () und wird durch 6 Kommas ersetzt. Also VA_SIZE... wird zu COMPOSE( GET_COUNT, (,,,,,, , 0, 6, 5, 4, 3, 2, 1) ) , und daraus wird GET_COUNT (,,,,,, , 0, 6, 5, 4, 3, 2, 1) und gibt 0 zurück.

Andererseits, wenn __VA_ARGS__ ist zB int, 5 , EXPAND __VA_ARGS__ () wird zu EXPAND int, 5 () . Also VA_SIZE... wird zu COMPOSE( GET_COUNT, (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) ) , was zu GET_COUNT (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) wird und gibt 2 zurück, wie in Potatoswatters Antwort beschrieben.

Ich habe den EXPAND Idee aus Jason Dangs Antwort.

Bibliothekscode:

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

Verwendung:

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

Ich würde dies als Kommentar zu Potatoswatters Beitrag posten, aber es ist zu lang und erfordert eine Code-Auflistung.

Hier ist ein bisschen Perl-Code zum Generieren einer Reihe von Makros, die als überladene Makros gedacht sind.

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

Hier ist die Ausgabe des Skripts:

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

Dies sind die (regelmäßig strukturierten Abschnitte von) Gruppen von Makroüberladungen, die zum Generieren von FOR_EACH verwendet werden (alias FE )-Makros, die einen WHAT senden können Makro wahlweise mit beliebig vielen konstanten Argumenten (A1 , A2 ...) zusätzlich zu einer beliebigen Anzahl von Argumenten in einer Liste, zusammen mit einem Index in der richtigen Reihenfolge (eine naive Implementierung ohne Verwendung von so etwas wie SELECT denn Überladen würde umgekehrte Indizes ergeben).

Als Beispiel sieht der verbleibende Abschnitt (der nicht reguläre "Basisfall"-Teil des zweiten Blocks) wie folgt aus:

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

Die Nützlichkeit davon kann vielleicht in Frage gestellt werden (ich habe es gebaut, weil ich eine Verwendung dafür gesehen habe ...), und dies beantwortet auch nicht direkt die Frage des OP (tatsächlich macht es irgendwie das Gegenteil - ein Foreach-Konstrukt tut das dasselbe Sache zu allen unterschiedlichen Argumenten ...), aber ich dachte nur, dass die Technik ziemlich interessant ist (und in gewisser Weise auch absolut erschreckend) und mit dem Präprozessor eine ziemliche Ausdruckskraft ermöglicht und es möglich sein wird, eine sehr effiziente Maschine zu erzeugen Code auf diese Weise. Ich denke, es ist auch ein ergreifendes Beispiel dafür, warum ich persönlich denke, dass der C-Präprozessor noch Raum für Verbesserungen hat.

Damit meine ich, dass der C-Präprozessor ein absoluter Greuel ist und wir ihn wahrscheinlich verschrotten und von vorne anfangen sollten :)