Lambda Rücksendung selbst: ist das legal?
Betrachten diese ziemlich nutzloses Programm:
#include <iostream>
int main(int argc, char* argv[]) {
int a = 5;
auto it = [&](auto self) {
return [&](auto b) {
std::cout << (a + b) << std::endl;
return self(self);
};
};
it(it)(4)(6)(42)(77)(999);
}
Grundsätzlich sind wir versucht zu machen, ein lambda, das gibt sich.
- MSVC kompiliert das Programm, und es läuft
- gcc kompiliert das Programm, und es segfaults
- clang lehnt das Programm mit einer Meldung:
error: function 'operator()<(lambda at lam.cpp:6:13)>' with deduced return type cannot be used before it is defined
Dem compiler ist die richtige? Ist es eine statische constraint-Verletzung, UB, oder nicht?
Update diese geringfügigen änderungen akzeptiert clang:
auto it = [&](auto& self, auto b) {
std::cout << (a + b) << std::endl;
return [&](auto p) { return self(self,p); };
};
it(it,4)(6)(42)(77)(999);
Update 2: ich verstehen Sie, wie schreiben Sie einen Funktor, der gibt sich selbst, oder wie die Y combinator, dies zu erreichen. Das ist mehr eine Sprache-Anwalt Frage.
Update 3: die Frage ist nicht, ob es legal ist für einen lambda-Ausdruck zur Rückgabe selbst im Allgemeinen, sondern über die Rechtmäßigkeit dieses spezifische Art und Weise, dies zu tun.
Verwandte Frage: C++ lambda Rücksendung selbst.
- clang sieht mehr anständige in diesem moment, Frage ich mich, ob so ein Konstrukt kann auch typecheck, eher landet es in einem unendlichen Baum.
- Wenn ich dieses Beispiel ausführen mit msvc manchmal geht es, aber in der Regel endet es mit seg-fault.
- Ihre Frage, ob es legal ist, die sagt, das ist eine Sprache, die-Anwalt Frage, aber einige der Antworten, die nicht wirklich in das Konzept ... es ist wichtig, um die tags richtig
- Danke, aded ein tag
- ja die aktualisierte verwendet man
auto& self
wodurch die baumelnden Referenz-problem. - Ein interessanter Leckerbissen von einem theoretischen Standpunkt aus ist das korrekt definierten und auswertbaren lambda-Ausdrücke können nur die Rekursion (im Allgemeinen), wenn der Typ angegeben mit ausdrückliche Erklärung und in der Tat glaube ich, dass es vielleicht ein theorem in der ursprünglichen lambda-Kalkül besagt, dass solche Typen sind nicht ableitbar (mehrdeutig), wenn es nicht von einer person. Also während ich kann nicht sagen, was der Fehler ist oder was die c++ - Gründe sind, es ist keine kleine überraschung für mich, dass eine Sprache, die Probleme beim Umgang mit diesem. btw, wenn Sie glauben, dies sollte eine Antwort sein, lass es mich wissen, ich werde es verschieben.
- die C++ - Lambda-Ausdrücke sind wirklich nicht theoretische lambda-Ausdrücke. C++ hat eine eingebaute rekursive Typen, die das original einfach typisierte lambda-Kalkül Ausdrücken können, so kann es haben, Dinge, die isomorph zu a: a->a und andere Unmögliche Konstrukte.
- Vage Verwandte: stackoverflow.com/q/8595061/560648
- genug, aber die Probleme können sich noch halten und ist wahrscheinlich der Grund für bestimmte Entscheidungen..
- zur Klärung der Frage ist, dass, wenn einer schreibt eine rekursive Funktion ohne Angabe eines Typs gibt es Fälle, in denen die Art könnten mehrere Dinge. Natürlich, c++ und einige Dinge umgehen, indem Sie explizit den Typ. Die überprüfung eines Typs konsistent ist afaik immer möglich.
Du musst angemeldet sein, um einen Kommentar abzugeben.
Das Programm ist schlecht ausgebildet (die klappern richtig ist) pro [dcl.spec.auto]/9:
Grundsätzlich der Abzug von der Rückgabetyp der innere lambda-Ausdruck richtet sich auf sich selbst (die Person benannt wird hier der Aufruf-operator) - also musst du explizit einen Rückgabetyp. In diesem besonderen Fall, das ist unmöglich, denn Sie müssen den Typ der innere lambda-aber können es nicht benennen. Aber es gibt andere Fälle, wo versucht zu zwingen, rekursive Lambda-Ausdrücke wie diese, das kann funktionieren.
Sogar ohne,, Sie haben eine dangling reference.
Ich möchte einige mehr, nach einer Diskussion mit jemandem viel klüger, (D. H. T. C.) Es gibt einen wichtigen Unterschied zwischen dem original-code (leicht reduziert) und die vorgeschlagene neue version (ebenfalls reduziert):
Dass der innere Ausdruck
self(self)
ist nicht abhängigf1
, aberself(self, p)
hängt fürf2
. Wenn Ausdrücke sind nicht abhängig, Sie können verwendet werden,... eifrig ([temp.res]/8, z.B. wiestatic_assert(false)
ist ein harter Fehler, unabhängig davon, ob die Vorlage es findet sich in instanziiert wird oder nicht).Für
f1
compiler (wie, sagen wir, clang) können versuchen, Sie zu instanziieren dieser eifrig. Sie kennen die abgeleiteten Typ von der äußeren lambda-sobald Sie, dass;
am Punkt#2
oben (es ist der innere lambda-Typ), aber wir versuchen, es zu benutzen früher (denke es ist wie bei Punkt#1
) - wir versuchen, es zu benutzen, während wir noch analysieren der innere lambda-Ausdruck, bevor wir wissen, was Ihr Typ eigentlich ist. Das läuft in Konflikt mit der dcl.spec.auto/9.Jedoch für
f2
wir können nicht versuchen, zu instanziieren gespannt, weil es abhängig ist. Wir können nur instanziieren am point-of-use, durch die wir alles wissen.Um wirklich so etwas wie dies tun, müssen Sie eine y-combinator. Die Umsetzung vom Papier:
Und was Sie wollen, ist:
int
obwohl es hängt davon abself
)operator()
von der lambda.Bearbeiten: Es scheint einige kontroversen über die Frage, ob diese Konstruktion ist streng gültig, gemäß der C++ - Spezifikation. Vorherrschende Meinung scheint zu sein, dass es nicht gültig ist. Siehe die anderen Antworten für eine gründlichere Diskussion. Der Rest dieser Antwort gilt wenn die Konstruktion ist gültig; der geänderte code unten funktioniert mit MSVC++ und gcc, und der OP gepostet hat, weiter verändert code, der funktioniert mit clang auch.
Dies ist Undefiniertes Verhalten, weil der innere lambda-Ausdruck erfasst die parameter
self
durch Referenz, sondernself
geht out of scope, nachdem diereturn
auf Linie 7. Also, wenn der zurückgegebene lambda-Ausdruck ausgeführt, später ist der Zugriff auf eine Referenz auf eine variable, die mehr Umfang.Läuft das Programm mit
valgrind
veranschaulicht dies:Stattdessen können Sie ändern die äußeren lambda-Ausdruck selbst von Referenz-statt von Wert, so dass eine Reihe von unnötigen Kopien und auch die Lösung des Problems:
Dies funktioniert:
self
eine Referenz?self
eine Referenz, dieses problem geht Weg, aber die Klappern noch lehnt es aus einem anderen Grundself
erfasst wird, durch einen Verweis!TL;DR;
clang ist richtig.
Sieht es aus wie der Abschnitt der Norm darstellt, macht diese schlecht ausgebildet ist [dcl.spec.auto]p9:
Ursprünglichen Arbeit durch
Wenn wir uns den Vorschlag Ein Vorschlag zum Hinzufügen von Y Combinator, um die Standard-Bibliothek es bietet eine funktionierende Lösung:
und es explizit sagt, dein Beispiel ist nicht möglich:
und es verweist auf ein Koreferat in der Richard Smith spielt auf den Fehler, der Klang ist geben Sie:
Barry wies mich auf das follow-up Vorschlag Rekursive Lambda-Ausdrücke, die erklärt, warum dies nicht möglich ist, und arbeitet rund um die
dcl.spec.auto#9
Einschränkung und zeigt auch Methoden dies zu erreichen ist heute ohne it:self
scheint nicht wie ein Unternehmen.Wie es scheint, das Geräusch ist rechts. Betrachten wir ein Vereinfachtes Beispiel:
Let ' s go durch einen compiler (ein bisschen):
it
istLambda1
mit einem template-Aufruf-operator.it(it);
löst Instanziierung der call operatorauto
ist, so müssen wir folgern es.Lambda1
.self(self)
self(self)
ist genau das, was wir starteten mit!Als solche, die Art, können nicht abgeleitet werden.
Lambda1::operator()
ist einfachLambda2
. Dann innerhalb, dass der innere lambda-Ausdruck der return-Typ vonself(self)
, einen Anruf vonLambda1::operator()
, ist bekannt, auchLambda2
. Möglicherweise die formalen Regeln im Weg stehen, macht die triviale Abzug, aber die Logik, die hier präsentiert wird nicht. Die Logik hier nur Beträge in einer Behauptung. Wenn die formalen Regeln im Weg stehen, dann ist das ein Fehler in der formalen Regeln.Gut, dein code funktioniert nicht. Das bedeutet aber:
Test-code:
Ihrem code sowohl UB und schlecht gebildet ist keine Diagnose erforderlich. Das ist lustig; aber beide können behoben werden, unabhängig.
Erste, der UB:
dies ist UB, weil äußere dauert
self
von Wert, dann ist innere fängtself
Verweis, dann geht es zurück nachouter
beendet. So segfaulting ist definitiv ok.Fix:
Dem code bleibt, ist schlecht gebildet. Um dies zu sehen, können wir erweitern den lambdas:
diese instanziiert
__outer_lambda__::operator()<__outer_lambda__>
:Damit wir Nächstes bestimmen Sie den return-Typ der
__outer_lambda__::operator()
.Gehen wir durch Sie Zeile für Zeile. Zunächst erstellen wir
__inner_lambda__
Typ:Nun, schauen Sie es -- seiner Rückkehr geben wird
self(self)
oder__outer_lambda__(__outer_lambda__ const&)
. Aber wir sind in der Mitte versuchen, Rückschlüsse auf die return-Typ von__outer_lambda__::operator()(__outer_lambda__)
.Ihnen nicht erlaubt, das zu tun.
Während in der Tat der Rückgabetyp
__outer_lambda__::operator()(__outer_lambda__)
ist eigentlich nicht abhängig von der Rückgabetyp__inner_lambda__::operator()(int)
, C++ kümmert sich nicht, wenn die Aufzucht Rückgabewerte; es wird einfach überprüft den code Zeile für Zeile.Sowie
self(self)
verwendet wird, bevor wir schließen es. Schlecht gebildet Programm.Können wir patch dies durch ausblenden
self(self)
bis später:und jetzt ist der code korrekt und kompiliert. Aber ich denke, das ist ein bisschen hack, benutzen Sie einfach die ycombinator.
operator()
ist, kann nicht allgemein abgeleitet werden, bis es instanziiert (aufgerufen mit einige argument von irgendeiner Art). Und so eine manuelle Maschine-wie umschreiben template-basierte code funktioniert gut.Es ist leicht genug, um zu umschreiben, dass der code in Bezug auf die Klassen, die ein compiler würde, oder eher sollten, generieren für die lambda-Ausdrücke.
Wenn das erledigt ist, es ist klar, dass das Hauptproblem ist nur die baumelnden Referenz, und einen compiler, der nicht akzeptiert, der code ist etwas herausgefordert, in der lambda-Abteilung.
Rewrite-zeigt, dass es keine zyklischen Abhängigkeiten.
Eine komplett vorgefertigte version zu reflektieren, die Art und Weise, dass der innere lambda-Ausdruck in die original-code, erfasst ein Element, das von einem Vorlagen-Typ:
Ich denke, dass es dieses Template in der internen Maschinen -, dass die formalen Regeln, die entworfen sind, zu verbieten. Wenn Sie das tun, bewahre das original-Konstrukt.
template< class > class Inner;
's Vorlageoperator()
ist ...instanziiert? Naja, Falsches Wort. Geschrieben??? ... währendOuter::operator()<Outer>
vor den Rückgabetyp der äußeren Bediener abgeleitet wird. UndInner<Outer>::operator()
hat einen AufrufOuter::operator()<Outer>
selbst. Und das ist nicht erlaubt. Nun, die meisten Compiler nicht Hinweis dieself(self)
weil Sie warten, um zu folgern, der return-Typ vonOuter::Inner<Outer>::operator()<int>
wennint
übergeben wird. Sinnvoll. Aber es fehlt die schlecht gebildet sein von dem code.Innner<T>::operator()<U>
, instanziiert wird. Nach all den Rückgabetyp hängen von derU
hier. Nicht, aber im Allgemeinen.