Gibt es einen Grund, keine globalen Lambdas zu verwenden?

89

Wir hatten eine Funktion, die ein nicht erfassendes Lambda in sich selbst verwendete, z.

void foo() {
  auto bar = [](int a, int b){ return a + b; }

  // code using bar(x,y) a bunch of times
}

Jetzt wurde die vom Lambda implementierte Funktionalität an anderer Stelle benötigt, sodass ich das Lambda aus foo()dem Bereich global / Namespace herausheben werde. Ich kann es entweder als Lambda belassen, es zu einer Option zum Kopieren und Einfügen machen, oder es in eine richtige Funktion ändern:

auto bar = [](int a, int b){ return a + b; } // option 1
int bar(int a, int b){ return a + b; } // option 2

void foo() {
  // code using bar(x,y) a bunch of times
}

Ändern es auf eine einwandfreie Funktion ist trivial, aber es machte mich wundern , wenn es aus irgendeinem Grund ist nicht , es zu verlassen als Lambda? Gibt es einen Grund, nicht überall nur Lambdas anstelle "regulärer" globaler Funktionen zu verwenden?

Baruch
quelle
Ich gehe davon aus, dass das Erfassen unbeabsichtigter Variablen zu vielen Fehlern führen würde, wenn Lambdas nicht sorgfältig verwendet werden
Macroland
einige Argumente für diesen youtube.com/watch?v=Ft3zFk7VPrE
sudo rm -rf Schrägstrich

Antworten:

61

Es gibt einen sehr wichtigen Grund, keine globalen Lambdas zu verwenden: weil es nicht normal ist.

Die reguläre Funktionssyntax von C ++ gibt es seit den Tagen von C. Programmierer wissen seit Jahrzehnten, was diese Syntax bedeutet und wie sie funktioniert (obwohl zugegebenermaßen diese ganze Sache mit dem Zerfall von Funktion zu Zeiger manchmal sogar erfahrene Programmierer beißt). Wenn ein C ++ - Programmierer mit einem höheren Können als "absoluter Neuling" eine Funktionsdefinition sieht, weiß er, was er bekommt.

Ein globales Lambda ist ein ganz anderes Tier. Es hat ein anderes Verhalten als eine reguläre Funktion. Lambdas sind Objekte, Funktionen nicht. Sie haben einen Typ, aber dieser Typ unterscheidet sich vom Typ ihrer Funktion. Und so weiter.

Jetzt haben Sie die Messlatte für die Kommunikation mit anderen Programmierern höher gelegt. Ein C ++ - Programmierer muss Lambdas verstehen, wenn er verstehen will, was diese Funktion tut. Und ja, dies ist 2019, also sollte ein anständiger C ++ - Programmierer eine Idee haben, wie ein Lambda aussieht. Aber es ist immer noch eine höhere Messlatte.

Und selbst wenn sie es verstehen, wird die Frage im Kopf dieses Programmierers sein ... warum hat der Verfasser dieses Codes es so geschrieben? Und wenn Sie keine gute Antwort auf diese Frage haben (z. B. weil Sie das Überladen ausdrücklich verbieten möchten , wie in den Anpassungspunkten für Bereiche), sollten Sie den allgemeinen Mechanismus verwenden.

Ziehen Sie erwartete Lösungen gegebenenfalls neuen vor. Verwenden Sie die am wenigsten komplizierte Methode, um Ihren Standpunkt zu vermitteln.

Nicol Bolas
quelle
9
"seit den Tagen von C" Vorher mein Freund
Lightness Races in Orbit
4
@LightnessRaces: Mein Hauptanliegen war es auszudrücken, dass es die Funktionssyntax von C ++ seit C gibt, so dass viele Leute durch Inspektion wissen, was es ist.
Nicol Bolas
2
Stimmen Sie all dieser Antwort mit Ausnahme des ersten Satzes zu. "Es ist nicht normal" ist eine vage und nebulöse Aussage. Vielleicht meinten Sie es im Sinne von "es ist ungewöhnlich und verwirrend"?
Einpoklum
1
@einpoklum: C ++ hat viele Dinge, die als "ungewöhnlich und verwirrend" angesehen werden können und in ihrer Domäne immer noch "normal" sind (siehe Deep Template Metaprogramming). Ich denke, "normal" ist in diesem Fall vollkommen gültig; Es ist keine Sache, die sowohl historisch als auch heute innerhalb der "Normen" der C ++ - Programmiererfahrung liegt.
Nicol Bolas
@NicolBolas: Aber in diesem Sinne ist es Zirkelschluss: OP fragt sich, ob wir eine seltene Praxis anwenden sollten. Seltene Praktiken sind fast per Definition nicht "die Norm". Aber nm.
Einpoklum
53

Ich kann mir einige Gründe vorstellen, warum Sie globale Lambdas als Ersatz für reguläre Funktionen vermeiden möchten:

  • reguläre Funktionen können überlastet werden; Lambdas können nicht (es gibt jedoch Techniken, um dies zu simulieren)
  • Trotz der Tatsache, dass sie funktionsähnlich sind, belegt selbst ein nicht erfassendes Lambda wie dieses Speicher (im Allgemeinen 1 Byte für nicht erfassendes).
    • Wie in den Kommentaren erwähnt, werden moderne Compiler diesen Speicher nach der Als-ob- Regel optimieren

"Warum sollte ich keine Lambdas verwenden, um staatliche Funktoren (Klassen) zu ersetzen?"

  • Klassen haben einfach weniger Einschränkungen als Lambdas und sollten daher das erste sein, wonach Sie greifen
    • (öffentliche / private Daten, Überladung, Hilfsmethoden usw.)
  • Wenn das Lambda einen Staat hat, ist es umso schwieriger zu überlegen, wann es global wird.
    • Wir sollten es vorziehen, eine Instanz einer Klasse im engstmöglichen Bereich zu erstellen
  • Es ist bereits schwierig, ein nicht erfassendes Lambda in einen Funktionszeiger umzuwandeln, und es ist unmöglich für ein Lambda, das irgendetwas in seiner Erfassung angibt.
    • Klassen bieten uns eine einfache Möglichkeit, Funktionszeiger zu erstellen, und sie sind auch das, womit sich viele Programmierer besser auskennen
  • Lambdas mit einem Capture können nicht standardmäßig erstellt werden (in C ++ 20. Bisher gab es ohnehin keinen Standardkonstruktor).
AndyG
quelle
Es gibt auch den impliziten "this" -Zeiger auf ein Lambda (auch einen nicht erfassenden), der beim Aufrufen des Lambda gegenüber dem Aufrufen der Funktion zu einem zusätzlichen Parameter führt.
1201ProgramAlarm
@ 1201ProgramAlarm: Das ist ein guter Punkt; Es kann viel schwieriger sein, einen Funktionszeiger für ein Lambda zu erhalten (obwohl nicht erfassende Lambdas in reguläre Funktionszeiger zerfallen können)
AndyG
3
Auf der anderen Seite fördert ein Lambda anstelle einer Funktion das Inlining, wenn es irgendwo übergeben wird, anstatt direkt aufgerufen zu werden. Natürlich könnte man den Funktionszeiger dafür in einen packen std::integral_constant...
Deduplicator
@Deduplicator: " Ein Lambda anstelle einer Funktion fördert das Inlining " Nur um klar zu sein, gilt dies nur, wenn "irgendwo" eine Vorlage ist, die die aufgerufene Funktion als beliebigen aufrufbaren Typ und nicht als Funktionszeiger verwendet.
Nicol Bolas
2
@AndyG Sie vergessen die Als-ob-Regel: Programme, die weder die Größe des Lambda anfordern (weil… warum?!) Noch einen Zeiger darauf erstellen, müssen (und im Allgemeinen nicht). Platz dafür zuweisen.
Konrad Rudolph
9

Nachdem ich gefragt hatte, überlegte ich mir einen Grund, dies nicht zu tun: Da es sich um Variablen handelt, sind sie anfällig für das Fiasko der statischen Initialisierungsreihenfolge ( https://isocpp.org/wiki/faq/ctors#static-init-order ), das dies könnte verursachen Fehler auf der ganzen Linie.

Baruch
quelle
Sie könnten sie zu constexprLambdas machen ... der eigentliche Punkt ist: Verwenden Sie einfach eine Funktion.
Einpoklum
8

Gibt es einen Grund, nicht überall Lambdas anstelle von "regulären" globalen Funktionen zu verwenden?

Ein Problem mit einer bestimmten Komplexität erfordert eine Lösung mit mindestens derselben Komplexität. Wenn es jedoch eine weniger komplexe Lösung für dasselbe Problem gibt, gibt es wirklich keine Rechtfertigung für die Verwendung der komplexeren. Warum Komplexität einführen, die Sie nicht brauchen?

Zwischen einem Lambda und einer Funktion ist eine Funktion einfach die weniger komplexe Art von Entität der beiden. Sie müssen nicht rechtfertigen, kein Lambda zu verwenden. Sie müssen die Verwendung eines begründen. Ein Lambda-Ausdruck führt einen Schließungstyp ein, der ein unbenannter Klassentyp mit allen üblichen speziellen Elementfunktionen ist, einen Funktionsaufrufoperator und in diesem Fall einen impliziten Konvertierungsoperator in einen Funktionszeiger und erstellt ein Objekt dieses Typs. Das Kopieren und Initialisieren einer globalen Variablen aus einem Lambda-Ausdruck ist einfach viel mehr als nur das Definieren einer Funktion. Es definiert einen Klassentyp mit sechs implizit deklarierten Funktionen, definiert zwei weitere Operatorfunktionen und erstellt ein Objekt. Der Compiler muss noch viel mehr tun. Wenn Sie keine der Funktionen eines Lambda benötigen, verwenden Sie kein Lambda…

Michael Kenzel
quelle
6

Lambdas sind anonyme Funktionen .

Wenn Sie ein benanntes Lambda verwenden, bedeutet dies, dass Sie grundsätzlich eine benannte anonyme Funktion verwenden. Um dieses Oxymoron zu vermeiden, können Sie auch eine Funktion verwenden.

Eric Duminil
quelle
1
Dies scheint ein Argument aus der Terminologie zu sein, dh. verwirrende Benennung und Bedeutung. Es kann leicht widerlegt werden, indem definiert wird, dass ein benanntes Lambda nicht mehr anonym ist (duh). Hier geht es nicht darum, wie das Lambda verwendet wird, sondern darum, dass die „anonyme Funktion“ eine falsche Bezeichnung ist und nicht mehr korrekt ist, wenn ein Lambda an einen Namen gebunden ist.
Konrad Rudolph
@KonradRudolph: Es ist nicht nur Terminologie, deshalb wurden sie überhaupt erst erstellt. Zumindest historisch gesehen sind Lambdas anonyme Funktionen, und deshalb ist es verwirrend, sie zu benennen, insbesondere wenn stattdessen Funktionen erwartet werden.
Eric Duminil
1
Nein, nein. Lambdas werden routinemäßig benannt (normalerweise nur lokal), daran ist nichts verwirrend.
Konrad Rudolph
1
Ich bin mit anonymen Funktionen nicht einverstanden , es ist eher ein Funktor, aber ich sehe das Problem nicht darin, eine (benannte) Instanz zu haben, anstatt sie nur als rvalue-Referenz zu verwenden. (da Ihr Punkt auch im lokalen Bereich gelten sollte).
Jarod42
5

Wenn es einen Grund gibt, es nicht als Lambda zu belassen? Gibt es einen Grund, nicht überall Lambdas anstelle von "regulären" globalen Funktionen zu verwenden?

Früher haben wir Funktionen anstelle von Global Functor verwendet, sodass die Kohärenz und das Prinzip des geringsten Erstaunens verletzt werden .

Die Hauptunterschiede sind:

  • Funktionen können überlastet werden, Funktoren hingegen nicht.
  • Funktionen können mit ADL gefunden werden, nicht mit Funktoren.
Jarod42
quelle