Wie erhalte ich die Adresse einer C ++ - Lambda-Funktion im Lambda selbst?

53

Ich versuche herauszufinden, wie man die Adresse einer Lambda-Funktion in sich selbst erhält. Hier ist ein Beispielcode:

[]() {
    std::cout << "Address of this lambda function is => " << ????
}();

Ich weiß, dass ich das Lambda in einer Variablen erfassen und die Adresse drucken kann, aber ich möchte es an Ort und Stelle tun, wenn diese anonyme Funktion ausgeführt wird.

Gibt es einen einfacheren Weg, dies zu tun?

Daksh
quelle
24
Ist dies nur aus Neugier oder gibt es ein zugrunde liegendes Problem, das Sie lösen müssen? Wenn es ein zugrunde liegendes Problem gibt, fragen Sie bitte direkt danach, anstatt nach einer einzigen möglichen Lösung für ein (für uns) unbekanntes Problem.
Einige Programmierer Typ
41
... XY-Problem effektiv bestätigen.
ildjarn
8
Sie können das Lambda durch eine manuell geschriebene Funktorklasse ersetzen und dann verwenden this.
HolyBlackCat
28
"Die Adresse einer Lamba-Funktion in sich aufnehmen" ist die Lösung , auf die Sie sich eng konzentrieren. Es könnte andere Lösungen geben, die besser sein könnten. Aber wir können Ihnen dabei nicht helfen, da wir nicht wissen, was das eigentliche Problem ist. Wir wissen nicht einmal, wofür Sie die Adresse verwenden werden. Ich versuche nur, Ihnen bei Ihrem eigentlichen Problem zu helfen.
Einige Programmierer Typ
8
@Someprogrammerdude Während das meiste, was Sie sagen, sinnvoll ist, sehe ich kein Problem mit der Frage "Wie kann X gemacht werden?". X hier ist "die Adresse eines Lambda aus sich heraus bekommen". Es spielt keine Rolle, dass Sie nicht wissen, wofür die Adresse verwendet wird, und es spielt keine Rolle, dass es nach Meinung eines anderen möglicherweise "bessere" Lösungen gibt, die in einer unbekannten Codebasis möglich oder nicht möglich sind ( zu uns). Eine bessere Idee ist es, sich einfach auf das angegebene Problem zu konzentrieren. Dies ist entweder machbar oder nicht. Wenn ja, wie dann ? Wenn nicht, dann erwähne es nicht und etwas anderes kann vorgeschlagen werden, IMHO.
code_dredd

Antworten:

32

Es ist nicht direkt möglich.

Lambda-Captures sind jedoch Klassen, und die Adresse eines Objekts stimmt mit der Adresse seines ersten Mitglieds überein. Wenn Sie also ein Objekt als erste Erfassung nach Wert erfassen, entspricht die Adresse der ersten Erfassung der Adresse des Lambda-Objekts:

int main() {
    int i = 0;
    auto f = [i]() { printf("%p\n", &i); };
    f();
    printf("%p\n", &f);
}

Ausgänge:

0x7ffe8b80d820
0x7ffe8b80d820

Alternativ können Sie ein Dekorationsmuster Lambda erstellen , das den Verweis auf die Lambda-Erfassung an seinen Anrufoperator übergibt:

template<class F>
auto decorate(F f) {
    return [f](auto&&... args) mutable {
        f(f, std::forward<decltype(args)>(args)...);
    };
}

int main() {
    auto f = decorate([](auto& that) { printf("%p\n", &that); });
    f();
}
Maxim Egorushkin
quelle
15
"Die Adresse eines Objekts stimmt mit der Adresse seines ersten Mitglieds überein." Wird irgendwo angegeben, dass die Erfassungen angeordnet sind oder dass keine unsichtbaren Mitglieder vorhanden sind?
n. 'Pronomen' m.
35
@ n.'pronomen'm. Nein, dies ist eine nicht tragbare Lösung. Eine Capture-Implementierung kann die Mitglieder möglicherweise vom größten zum kleinsten ordnen, um das Auffüllen zu minimieren. Der Standard sieht dies ausdrücklich vor.
Maxim Egorushkin
14
Betreff: "Dies ist eine nicht tragbare Lösung." Das ist ein anderer Name für undefiniertes Verhalten.
Solomon Slow
1
@ruohola Schwer zu sagen. Die "Adresse eines Objekts stimmt mit der Adresse seines ersten Mitglieds überein" gilt für Standardlayouttypen . Wenn Sie getestet haben, ob der Lambda-Typ ein Standardlayout ist, ohne UB aufzurufen, können Sie dies tun, ohne UB zu verursachen. Der resultierende Code hätte ein implementierungsabhängiges Verhalten. Den Trick einfach zu machen, ohne vorher seine Legalität zu testen, ist jedoch UB.
Ben Voigt
4
Ich glaube, es ist nicht spezifiziert gemäß § 8.1.5.2, 15: Wenn der Lambda-Ausdruck ausgewertet wird, werden die Entitäten, die durch Kopieren erfasst werden, verwendet, um jedes entsprechende nicht statische Datenelement des resultierenden Abschlussobjekts direkt zu initialisieren, und Die nicht statischen Datenelemente, die den Init-Captures entsprechen, werden wie vom entsprechenden Initialisierer (...) angegeben initialisiert. (Bei Array-Mitgliedern werden die Array-Elemente in aufsteigender Reihenfolge direkt initialisiert.) Diese Initialisierungen werden in der ( nicht angegebenen ) Reihenfolge durchgeführt, in der die nicht statischen Datenelemente deklariert werden.
Erbureth sagt Reinstate Monica
51

Es gibt keine Möglichkeit, die Adresse eines Lambda-Objekts innerhalb eines Lambda direkt abzurufen.

Nun, wie es passiert, ist dies ziemlich oft nützlich. Die häufigste Verwendung ist, um zu rekursieren.

Das y_combinatorkommt aus Sprachen, in denen Sie nicht über sich selbst sprechen konnten, bis Sie definiert wurden. Es kann ziemlich einfach in implementiert werden :

template<class F>
struct y_combinator {
  F f;
  template<class...Args>
  decltype(auto) operator()(Args&&...args) const {
    return f( f, std::forward<Args>(args)... );
  }
  template<class...Args>
  decltype(auto) operator()(Args&&...args) {
    return f( f, std::forward<Args>(args)... );
  }
};

Jetzt können Sie dies tun:

y_combinator{ [](auto& self) {
  std::cout<<"Address of this lambda function is => "<< &self;
} }();

Eine Variation davon kann umfassen:

template<class F>
struct y_combinator {
  F f;
  template<class...Args>
  decltype(auto) operator()(Args&&...args) const {
    return f( *this, std::forward<Args>(args)... );
  }
  template<class...Args>
  decltype(auto) operator()(Args&&...args) {
    return f( *this, std::forward<Args>(args)... );
  }
};

wobei das selfübergebene aufgerufen werden kann, ohne selfals erstes Argument übergeben zu werden.

Der zweite entspricht dem Real-Y-Kombinator (auch bekannt als Fixpunkt-Kombinator), glaube ich. Was Sie wollen, hängt davon ab, was Sie unter "Lambda-Adresse" verstehen.

Yakk - Adam Nevraumont
quelle
3
Wow, Y-Kombinatoren sind schwer genug, um sich in dynamisch typisierten Sprachen wie Lisp / Javascript / Python zurechtzufinden. Ich hätte nie gedacht, dass ich einen in C ++ sehen würde.
Jason S
13
Ich denke, wenn Sie dies in C ++ tun, verdienen Sie es, verhaftet zu werden
user541686
3
@ MSalters Unsicher. Wenn Fes sich nicht um ein Standardlayout handelt, y_combinatorist dies nicht der Fall, sodass keine vernünftigen Garantien gegeben werden.
Yakk - Adam Nevraumont
2
@carto Die Top-Antwort dort funktioniert nur, wenn Ihr Lambda im Gültigkeitsbereich lebt und es Ihnen nichts ausmacht, den Overhead für das Löschen einzugeben. Die dritte Antwort ist der y-Kombinator. Die 2. Antwort ist ein manueller Ykombinator.
Yakk - Adam Nevraumont
2
@ kaz C ++ 17 Funktion. In 11/14 würden Sie eine make-Funktion schreiben, die F ableiten würde; in 17 können Sie mit Vorlagennamen (und manchmal Abzug Führer) ableiten
Yakk - Adam Nevraumont
25

Eine Möglichkeit, dies zu lösen, besteht darin, das Lambda durch eine handgeschriebene Funktorklasse zu ersetzen. Es ist auch das, was das Lambda im Wesentlichen unter der Haube ist.

Dann können Sie die Adresse erhalten this, auch ohne den Funktor jemals einer Variablen zuzuweisen:

#include <iostream>

class Functor
{
public:
    void operator()() {
        std::cout << "Address of this functor is => " << this;
    }
};

int main()
{
    Functor()();
    return 0;
}

Ausgabe:

Address of this functor is => 0x7ffd4cd3a4df

Dies hat den Vorteil, dass dies zu 100% portabel ist und extrem einfach zu verstehen und zu verstehen ist.

Ruohola
quelle
9
Der Funktor kann sogar wie ein Lambda deklariert werden:struct { void operator()() { std::cout << "Address of this functor is => " << this << '\n'; } } f;
Fehlerhafter
-1

Nehmen Sie das Lambda auf:

std::function<void ()> fn = [&fn]() {
  std::cout << "My lambda is " << &fn << std::endl;
}
Vincent Fourmond
quelle
1
Die Flexibilität von a std::functionwird hier jedoch nicht benötigt und ist mit erheblichen Kosten verbunden. Wenn Sie dieses Objekt kopieren / verschieben, wird es beschädigt.
Deduplikator
@Deduplicator warum wird es nicht benötigt, da dies die einzige Antwort ist, die standardkonform ist? Bitte geben Sie eine Antwort, die funktioniert und dann keine std :: -Funktion benötigt.
Vincent Fourmond
Das scheint eine bessere und klarere Lösung zu sein, es sei denn, der einzige Punkt ist, die Adresse des Lambda zu erhalten (was an sich nicht viel Sinn macht). Ein häufiger Anwendungsfall wäre der Zugriff auf das Lambla in sich selbst, zu Rekursionszwecken, z. B. siehe: stackoverflow.com/questions/2067988/…, wobei die deklarative Option als Funktion als Lösung weithin akzeptiert wurde :)
Abs
-6

Dies ist möglich, hängt jedoch stark von der Plattform- und Compileroptimierung ab.

Auf den meisten mir bekannten Architekturen gibt es ein Register, das als Befehlszeiger bezeichnet wird. Der Sinn dieser Lösung besteht darin, sie zu extrahieren, wenn wir uns innerhalb der Funktion befinden.

Auf amd64 Der folgende Code sollte Ihnen Adressen in der Nähe der Funktion 1 geben.

#include <iostream>

void* foo() {
    void* n;
    asm volatile("lea 0(%%rip), %%rax"
      : "=a" (n));
    return n;
}

auto boo = [](){
    void* n;
    asm volatile("lea 0(%%rip), %%rax"
       : "=a" (n));
    return n;
};

int main() {
    std::cout<<"foo"<<'\n'<<((void*)&foo)<<'\n'<<foo()<<std::endl;  
    std::cout<<"boo"<<'\n'<<((void*)&boo)<<'\n'<<boo()<<std::endl;
}

Aber zum Beispiel auf gcc kann https://godbolt.org/z/dQXmHm mit -O3Optimierungsstufenfunktion inline sein.

Majkrzak
quelle
2
Ich würde gerne upvoten, aber ich bin nicht sehr in asm und verstehe nicht, was hier passiert. Eine Erklärung des Mechanismus, wie es funktioniert, wäre wirklich wertvoll. Auch, was meinst du mit „Adressen nahe an die Funktion“? Gibt es einen konstanten / undefinierten Offset?
R2RT
2
@majkrzak Dies ist nicht die "wahre" Antwort, da es die am wenigsten tragbare von allen geposteten ist. Es ist auch nicht garantiert, die Adresse des Lambda selbst zurückzugeben.
anonym
es heißt so, aber "es ist nicht möglich" Antwort ist falsch asnwer
majkrzak
Der Befehlszeiger kann nicht zum Ableiten von Adressen von Objekten mit automatischer oder thread_localSpeicherdauer verwendet werden. Was Sie hier abrufen möchten, ist die Absenderadresse der Funktion, kein Objekt. Aber selbst das funktioniert nicht, da der vom Compiler generierte Funktionsprolog auf den Stapel verschoben und den Stapelzeiger angepasst wird, um Platz für lokale Variablen zu schaffen.
Maxim Egorushkin