Wie erstellt das Lambda-Makro ein Lambda?

20

Ich habe diesen Code auf GitHub gefunden, ihn aber nicht ganz verstanden:

#define lambda(ret_type, _body) ({ ret_type _ _body _; })

Dann:

int (*max)(int, int) = lambda(int,
                             (int x, int y) {
                                 return x > y ? x : y;
                             });

int max_value = max(1, 2);
// max_value is 2

Was machen die Unterstriche im #defineund wie gibt es einen Funktionszeiger zurück?

Kish
quelle
7
Haben Sie versucht, das Makro (z. B. mit gcc -E) zu erweitern, um zu sehen, was es tut?
Nutzlos
5
Bitte schauen Sie sich die Erweiterung godbolt.org/z/C5TLWj an. Das Ergebnis ist jedoch nicht leicht zu verstehen
Eugene Sh.
2
Ich gehe davon aus, dass Sie dies anhand der Kommentare an der Stelle wissen, an der Sie diesen Code erhalten haben, dies hängt jedoch von GCC-Erweiterungen für verschachtelte Funktionen ab.
Thomas Jäger
4
@ EugeneSh. Es initialisiert einen Funktionszeiger mithilfe der verschachtelten Funktionen von GCC. Der Originalcode stammt von hier . Dieses Projekt wurde heute in den Hacker News geteilt.
Thomas Jäger
4
@ EugeneSh. Es ist eine Kombination aus zwei GCC-Erweiterungen: verschachtelte Funktionen und zusammengesetzte Anweisungen in Ausdrücken . Die verschachtelte Funktion wird in der zusammengesetzten Anweisung angezeigt.
Interjay

Antworten:

10

Mit diesem Makro

int (*max)(int, int) = lambda(int,
                             (int x, int y) {
                                 return x > y ? x : y;
                             });

erweitert sich zu:

int (*max)(int, int) = ({
    int _ (int x, int y) { return x > y ? x : y; }
    _;
});

In geschweiften Klammern werden dabei die verschachtelten Funktionen von GCC verwendet , um eine Funktion zu erstellen, die die gewünschte Operation ausführt. Im inneren Bereich hat es den Namen _.

Dann werden, wie von interjay festgestellt, die Statement-Ausdrücke von GCC verwendet. Tatsächlich wird die Funktion _dem Zeiger zugewiesenmax .

Wenn ein solches Makro nicht verwendet wird, kann dies anders geschrieben und wie folgt verwendet werden:

int val1 = 4;
int val2 = -30;

int perform_operation(int (*op)(int, int)) {
    int new_val = op(val1, val2);
    val1 = val2;
    val2 = new_val;
    return new_val;
}

int enclosing_function (void) {
    // Create max "lambda"
    int (*max)(int, int);
    {
        // Curly braces limit the scope of _
        int _ (int x, int y) { return x > y ? x : y; }
        max = _;
    }

    return perform_operation(max);
}

Die drei Methoden können in diesem Codebeispiel verglichen werden .

Thomas Jäger
quelle
Das Makro tut nichts, da es in gcc
P__J__
@P__J__ Es kompiliert ideone.com/T5FLXb
Eugene Sh.
@P__J__ Ich habe am Ende meiner Antwort ein Beispiel hinzugefügt, das auch die Verwendung dieses Makros zeigt.
Thomas Jäger
Warum kannst du nicht max(4, -30);statt apply_binary_op(max, 4, -30);?
SS Anne
1
"Zusammengesetzte Anweisungen in Ausdrücken" werden als "Anweisungsausdrücke" bezeichnet. Anweisungen, die einen Wert haben, der (z. B.) etwas zugewiesen werden kann.
Peter Cordes
7

Dies wird als Anweisungsausdruck bezeichnet und erzeugt ein "Lambda" (oder eine verschachtelte Funktion) ) und gibt einen Zeiger darauf zurück. Es ist GNU C-spezifisch.

Das Makro wird erweitert zu:

int (*max)(int, int) = ({ int _ (int x, int y) { return x > y ? x : y; } _; })

Das _am Ende ist wie ein return.

Der Unterstrich ist eigentlich der Name der Funktion, die erstellt und "zurückgegeben" wird. Es wird verwendet, weil es ein selten verwendeter Bezeichner ist (aus gutem Grund; _möglicherweise der am wenigsten beschreibende Bezeichner).

Der Grund, warum der Anweisungsausdruck verwendet wird, _wird nicht definiert, nachdem der Gültigkeitsbereich des Anweisungsausdrucks beendet wurde.

Gehen Sie also das Makro durch:

#define lambda(ret_type, _body) ({ ret_type _ _body _; })

ret_typeist der Rückgabetyp des "Lambda". _ist der Name der darin verwendeten Funktion, da es sich um einen ungewöhnlichen Bezeichnernamen handelt. _bodybesteht aus den Argumenten und dem Hauptteil der Funktion. Das nachfolgende _"gibt" das "Lambda" zurück.

Dieser Code befindet sich unter Let's Destroy C (ein geeigneter Name). Du solltest es nicht benutzen. Ihr Code funktioniert nur auf Compilern, die GNU C-Erweiterungen unterstützen. Schreiben Sie stattdessen einfach eine Funktion oder ein Makro.

Wenn Sie solche Konstrukte häufig verwenden oder mehr Funktionen wünschen, empfehle ich die Verwendung von C ++. Mit C ++ können Sie etwas Ähnliches tun und über tragbaren Code verfügen.

SS Anne
quelle