Ich bin neu in C ++ 11. Ich schreibe die folgende rekursive Lambda-Funktion, aber sie wird nicht kompiliert.
sum.cpp
#include <iostream>
#include <functional>
auto term = [](int a)->int {
return a*a;
};
auto next = [](int a)->int {
return ++a;
};
auto sum = [term,next,&sum](int a, int b)mutable ->int {
if(a>b)
return 0;
else
return term(a) + sum(next(a),b);
};
int main(){
std::cout<<sum(1,10)<<std::endl;
return 0;
}
Kompilierungsfehler:
vimal @ linux-718q: ~ / Study / 09C ++ / c ++ 0x / lambda> g ++ -std = c ++ 0x sum.cpp
sum.cpp: In der Lambda-Funktion: sum.cpp: 18: 36: Fehler: ' ((<lambda(int, int)>*)this)-><lambda(int, int)>::sum
' kann nicht als Funktion verwendet werden
gcc version
gcc version 4.5.0 20091231 (experimentell) (GCC)
Aber wenn ich die Erklärung sum()
wie folgt ändere , funktioniert es:
std::function<int(int,int)> sum = [term,next,&sum](int a, int b)->int {
if(a>b)
return 0;
else
return term(a) + sum(next(a),b);
};
Könnte jemand bitte Licht ins Dunkel bringen?
mutable
Schlüsselwort dort?std::function<int(int,int)> sum = [&](int a, int b) {
Antworten:
Denken Sie an den Unterschied zwischen der automatischen Version und der vollständig angegebenen Typversion. Das Auto- Schlüsselwort leitet seinen Typ von dem ab, mit dem es initialisiert wurde, aber was Sie damit initialisieren, muss wissen, um welchen Typ es sich handelt (in diesem Fall muss der Lambda-Abschluss die Typen kennen, die erfasst werden). So etwas wie ein Henne-Ei-Problem.
Andererseits muss der Typ eines vollständig spezifizierten Funktionsobjekts nichts darüber "wissen", was ihm zugewiesen wird, und so kann der Abschluss des Lambdas ebenfalls vollständig über die Typen informiert werden, die er erfasst.
Betrachten Sie diese geringfügige Änderung Ihres Codes und es kann sinnvoller sein:
Offensichtlich würde dies mit Auto nicht funktionieren . Rekursive Lambda-Funktionen funktionieren einwandfrei (zumindest in MSVC, wo ich Erfahrung mit ihnen habe), nur dass sie nicht wirklich mit Typinferenz kompatibel sind.
quelle
auto
Variable im Initialisierer zu verweisen . Der Typ der automatischen Variablen ist noch nicht bekannt, wenn der Initialisierer verarbeitet wird.sum
andere alsstd::function<int(int, int)>
oder hat sich die C ++ - Spezifikation einfach nicht die Mühe gemacht, darauf zu schließen?Der Trick besteht darin, die Lambda-Implementierung als Parameter und nicht durch Erfassung in sich selbst einzugeben.
Alle Probleme in der Informatik können durch eine andere Indirektionsebene gelöst werden . Ich habe diesen einfachen Trick zuerst bei gefunden http://pedromelendez.com/blog/2015/07/16/recursive-lambdas-in-c14/ gefunden.
Es ist erforderlich C ++ 14 , während die Frage auf C ++ 11, aber vielleicht interessant für die meisten.
Das Durchgehen
std::function
ist ebenfalls möglich, kann jedoch zu einem langsameren Code führen. Aber nicht immer. Schauen Sie sich die Antworten auf std :: function vs template anDies ist nicht nur eine Besonderheit von C ++, sondern eine direkte Abbildung auf die Mathematik der Lambda-Rechnung. Aus Wikipedia :
quelle
function<>
. Ich kann nicht verstehen, warum es jemand bevorzugen würde. Edit: Es ist anscheinend schneller.error: use of ‘[...]’ before deduction of ‘auto’
- musste explizit den Rückgabetyp angeben (brauchte andererseits keine veränderbare).Mit C ++ 14 ist es jetzt ganz einfach, ein effizientes rekursives Lambda
std::function
in nur wenigen Codezeilen zu erstellen, ohne den zusätzlichen Aufwand dafür zu verursachen (mit einer kleinen Bearbeitung des Originals, um zu verhindern, dass der Benutzer versehentlich eine Kopie erstellt ):mit dem Ihr ursprünglicher
sum
Versuch wird:In C ++ 17 können wir mit CTAD einen Abzugsleitfaden hinzufügen:
Das macht die Hilfsfunktion überflüssig. Wir können einfach
y_combinator{[](auto self, ...){...}}
direkt schreiben .In C ++ 20 mit CTAD für Aggregate ist der Abzugsleitfaden nicht erforderlich.
quelle
std::forward<decltype(sum)>(sum)
anstattsum
in der letzten Zeile.operator()
also gibt es nichts zu gewinnen durch Weiterleitungsum
const
falls das bereitgestellte Funktionsobjekt einen Nichtaufrufoperator hatconst
. Verwenden Sie SFINAE und berechnen Sienoexcept
für beide. Außerdem ist die Maker-Funktion in C ++ 17 nicht mehr erforderlich.auto sum
kopiert ... aber es kopiert areference_wrapper
, was dasselbe ist wie eine Referenz. Wenn Sie dies einmal in der Implementierung tun, wird keine der Verwendungen versehentlich kopiert.Ich habe eine andere Lösung, arbeite aber nur mit staatenlosen Lambdas:
Der Trick dabei ist, dass Lambdas auf statische Variablen zugreifen können und Sie zustandslose in Funktionszeiger konvertieren können.
Sie können es mit Standard-Lambdas verwenden:
Seine Arbeit in GCC 4.7
quelle
Sie können eine Lambda-Funktion selbst rekursiv aufrufen lassen. Das einzige, was Sie tun müssen, ist, es über einen Funktions-Wrapper zu referenzieren, damit der Compiler den Rückgabe- und Argumenttyp kennt (Sie können keine Variable - das Lambda selbst - erfassen, die noch nicht definiert wurde). .
Achten Sie darauf, dass Ihnen der Umfang des Wrappers nicht ausgeht. F.
quelle
Um Lambda ohne Verwendung externer Klassen und Funktionen (wie
std::function
oder Festkomma-Kombinator) rekursiv zu machen, kann in C ++ 14 die folgende Konstruktion verwendet werden ( Live-Beispiel ):Drucke:
Beachten Sie, dass der Ergebnistyp des Lambda explizit angegeben werden sollte.
quelle
Ich habe einen Benchmark durchgeführt, bei dem eine rekursive Funktion mit einer rekursiven Lambda-Funktion unter Verwendung der
std::function<>
Erfassungsmethode verglichen wurde . Mit vollständigen Optimierungen in Clang Version 4.1 lief die Lambda-Version deutlich langsamer.Erzeugt Ergebnisse:
(Hinweis: Ich habe auch mit einer Version bestätigt, die die Eingaben von cin übernommen hat, um die Auswertung der Kompilierungszeit zu vermeiden.)
Clang erzeugt auch eine Compiler-Warnung:
Was erwartet und sicher ist, sollte aber beachtet werden.
Es ist großartig, eine Lösung in unseren Toolbelts zu haben, aber ich denke, die Sprache wird einen besseren Weg brauchen, um diesen Fall zu behandeln, wenn die Leistung mit den aktuellen Methoden vergleichbar sein soll.
Hinweis:
Wie ein Kommentator betonte, scheint die neueste Version von VC ++ einen Weg gefunden zu haben, dies bis zur gleichen Leistung zu optimieren. Vielleicht brauchen wir doch keinen besseren Weg, um damit umzugehen (außer für syntaktischen Zucker).
Wie einige andere SO-Beiträge in den letzten Wochen dargelegt haben, kann die Leistung
std::function<>
selbst die Ursache für die Verlangsamung gegenüber der direkten Aufruffunktion sein, zumindest wenn die Lambda-Erfassung zu groß ist, um in einige bibliotheksoptimierte Raumnutzungenstd::function
für kleine Funktoren zu passen (Ich denke, irgendwie mögen die verschiedenen Optimierungen für kurze Zeichenfolgen?).quelle
Dies ist eine etwas einfachere Implementierung des Fixpoint-Operators, die es etwas offensichtlicher macht, was genau vor sich geht.
quelle
std::function
Funktionszeiger ersetzen (von Kernen funktioniert dies nur mit normaler Funktion und zustandslosen Lambdas). Übrigensfib_nonr
sollte akzeptierenfixpoint<int,int>
, wenn Sie es verwenden,std::function
erfordern Sie das Erstellen einer neuen Kopie von*this
.Hier ist eine verfeinerte Version der Y-Kombinator-Lösung, die auf einer von @Barry vorgeschlagenen basiert.
Um dies zu verwenden, könnte man Folgendes tun
Es ähnelt dem
let rec
Schlüsselwort in OCaml, ist jedoch nicht dasselbe.quelle
C ++ 14: Hier ist ein rekursiver anonymer Satz von Lambdas ohne Status ohne Status, der alle Zahlen von 1, 20 ausgibt
Wenn ich das richtig verstehe, wird die Y-Kombinator-Lösung verwendet
Und hier ist die Summenversion (n, m)
quelle
Hier ist die endgültige Antwort für das OP. Visual Studio 2010 unterstützt jedoch nicht die Erfassung globaler Variablen. Und Sie müssen sie nicht erfassen, da auf globale Variablen durch Definieren global zugegriffen werden kann. Die folgende Antwort verwendet stattdessen eine lokale Variable.
quelle
Sie versuchen, eine Variable (Summe) zu erfassen, die Sie gerade definieren. Das kann nicht gut sein.
Ich denke nicht, dass wirklich selbstrekursive C ++ 0x-Lambdas möglich sind. Sie sollten jedoch in der Lage sein, andere Lambdas zu fangen.
quelle
Diese Antwort ist der von Yankes unterlegen, aber hier geht es weiter:
quelle
reinterpret_cast
. In Ihrem Fall ist es wahrscheinlich am besten, eine Struktur zu erstellen, die diese ersetztdp_type
. Es sollte ein Feld habenfp_type
, kann ausfp_type
einem Operator()
mit Argumenten wie konstruiert werden und einen Operator habenfp_type
. Dies liegt in der Nähe,std::function
ermöglicht jedoch selbstreferenzierende Argumente.struct
würde auch eine zusätzliche Indirektionsebene hinzufügen. Das Beispiel funktioniert und die Besetzung ist standardkonform, ich weiß nicht, wofür das-1
war.-1
wusste ich nicht, wer es dir gibt, aber ich denke, esreinterpret_cast
sollte als letztes Mittel verwendet werden.cast
soll nach dem c ++ 11 Standard garantiert funktionieren. Die Verwendung von a könntestruct
in meinen Augen die Verwendung eines Lambda-Objekts zunichte machen. Schließlich ist das, wasstruct
Sie vorschlagen, ein Funktor, der ein Lambda-Objekt verwendet.std::function
und Sie werden etwas in der Nähe haben, das ich mir vorgestellt habe. Dies hat wahrscheinlich eine ähnliche Leistung wie Ihre Lösung.Sie benötigen einen Festkomma-Kombinator. Sehen Sie das .
oder schauen Sie sich den folgenden Code an:
quelle