wake-up-neo.com

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

Wie funktioniert das ? Wie kann ein C99/C++ 11-Variadic-Makro implementiert werden, um auf der Grundlage der Anzahl der Argumente auf verschiedene Dinge zu erweitern?

40
Potatoswatter

(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.

Einschränkung: Dieses System kann den Unterschied zwischen null und einem Argument nicht erkennen, da ist kein Unterschied zwischen keinem Argument und einem einzelnen leeren Argument. Sie sehen beide wie MACRO() aus.


Um zwischen Implementierungen zu wählen, verwenden Sie den Makro-Verkettungsoperator mit einer Reihe funktionsartiger 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 der ##-Operator die Makroerweiterung seiner Argumente unterdrückt, ist es besser, ihn in ein anderes Makro zu packen.

#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 (dies ist der clevere 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__)

Verwendungszweck:

#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

Ich würde dies als Kommentar zu Potatoswatter's Post posten, aber es ist zu lang und erfordert eine Code-Auflistung. 

Hier ist ein bisschen Perl-Code zum Erzeugen eines Satzes von Makros, die überlastete Makros sein sollen. 

$ 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 Erzeugen von FOR_EACH (aka FE) -Makros verwendet werden, die ein WHAT-Makro optional mit einer beliebigen Anzahl von konstanten Argumenten (A1, A2...) zusätzlich zu einem eine beliebige Anzahl von Argumenten in einer Liste zusammen mit einem Index in der richtigen Reihenfolge (eine naive Implementierung ohne etwas wie SELECT zum Überladen würde umgekehrte Indizes ergeben). 

Als Beispiel sieht der verbleibende Abschnitt (der unregelmäßige "Basisfall" -Abschnitt 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 das Gegenteil - ein foreach-Konstrukt) macht das selbe Ding zu allen variadischen Argumenten ...), aber ich dachte einfach, dass die Technik ziemlich interessant ist (und in gewisser Weise auch äußerst schrecklich ist) und die Verwendung des Präprozessors und des Willens ziemlich viel Ausdruckskraft erlaubt Auf diese Weise kann sehr effizienter Maschinencode erzeugt werden. Ich denke, es ist auch ein anschauliches Beispiel dafür, warum ich persönlich der Meinung bin, dass der C-Präprozessor noch Verbesserungsbedarf hat. 

Womit ich meine, dass der C-Präprozessor ein absolutes Gräuel ist und wir sollten es wahrscheinlich ausrangieren und von vorne anfangen :)

4
Steven Lu

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

Kurz gesagt, wenn __VA_ARGS__ leer ist, wird EXPAND __VA_ARGS__ () in VA_SIZE macro zu EXPAND () und wird durch 6 Kommas ersetzt. Aus VA_SIZE... wird also COMPOSE( GET_COUNT, (,,,,,, , 0, 6, 5, 4, 3, 2, 1) ) und das wird GET_COUNT (,,,,,, , 0, 6, 5, 4, 3, 2, 1) und gibt 0 zurück.

Wenn __VA_ARGS__ z. B. int, 5 ist, wird EXPAND __VA_ARGS__ ()EXPAND int, 5 (). Aus VA_SIZE... wird also COMPOSE( GET_COUNT, (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) ), die zu GET_COUNT (EXPAND int, 5 (), 0, 6, 5, 4, 3, 2, 1) wird und 2 zurückgibt, wie in Potatoswatter's Antwort beschrieben.

Ich habe die EXPAND-Idee von Jason Dangs Antwort bekommen.

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

Verwendungszweck:

#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

Obwohl es bereits beantwortet wurde, habe ich eine sehr kurze Version davon vorbereitet. Ich hoffe es kann helfen.

Implementierung

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

Anpassung

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

Verwendungszweck

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

Ich habe die Lösung von Potatowater erweitert, um das Problem iso c99 requires rest arguments to be used zu vermeiden, wenn der Compiler-Switch -pedantic von gcc verwendet wird.

Bibliothek

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

Anpassung

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

Verwendungszweck

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