Was ist die Art von Lambda, wenn es in C ++ 11 mit "auto" abgeleitet wird?

141

Ich hatte die Auffassung, dass der Typ eines Lambda ein Funktionszeiger ist. Als ich den folgenden Test durchführte, stellte ich fest, dass er falsch war ( Demo ).

#define LAMBDA [] (int i) -> long { return 0; }
int main ()
{
  long (*pFptr)(int) = LAMBDA;  // ok
  auto pAuto = LAMBDA;  // ok
  assert(typeid(pFptr) == typeid(pAuto));  // assertion fails !
}

Fehlt dem obigen Code ein Punkt? Wenn nicht, was ist der typeofLambda-Ausdruck, wenn er mit dem autoSchlüsselwort abgeleitet wird ?

iammilind
quelle
8
"Typ eines Lambda ist ein Funktionszeiger" - das wäre ineffizient und würde den ganzen Punkt von Lambdas verfehlen.
Konrad Rudolph

Antworten:

144

Der Typ eines Lambda-Ausdrucks ist nicht spezifiziert.

Aber sie sind im Allgemeinen nur syntaktischer Zucker für Funktoren. Ein Lambda wird direkt in einen Funktor übersetzt. Alles innerhalb des []wird in Konstruktorparameter und Elemente des Funktorobjekts umgewandelt, und die darin enthaltenen Parameter ()werden in Parameter für den Funktor umgewandelt operator().

Ein Lambda, das keine Variablen erfasst (nichts in den []'s), kann in einen Funktionszeiger konvertiert werden (MSVC2010 unterstützt dies nicht, wenn dies Ihr Compiler ist, diese Konvertierung jedoch Teil des Standards ist).

Der tatsächliche Typ des Lambda ist jedoch kein Funktionszeiger. Es ist ein nicht spezifizierter Funktortyp.

jalf
quelle
1
MSVC2010 unterstützt keine Konvertierung in Funktionszeiger, MSVC11 jedoch. blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx
KindDragon
17
+1 für "bloßer syntaktischer Zucker für Funktoren". Viel potenzielle Verwirrung kann vermieden werden, wenn man sich daran erinnert.
Ben
4
ein Funktor ist alles mit operator()im Grunde stackoverflow.com/questions/356950/c-functors-and-their-uses
TankorSmash
107

Es ist eine eindeutige unbenannte Struktur, die den Funktionsaufrufoperator überlastet. Jede Instanz eines Lambda führt einen neuen Typ ein.

Im Sonderfall eines nicht erfassenden Lambdas hat die Struktur zusätzlich eine implizite Konvertierung in einen Funktionszeiger.

Avakar
quelle
2
Gute Antwort. Viel genauer als meine. +1 :)
Jalf
4
+1 für den Unicity-Teil ist es zunächst sehr überraschend und verdient Aufmerksamkeit.
Matthieu M.
Nicht, dass es wirklich wichtig wäre, aber ist der Typ wirklich unbenannt oder wird ihm erst bei der Kompilierung ein Name gegeben? IOW, könnte man RTTI verwenden, um den Namen zu finden, für den sich der Compiler entschieden hat?
Ben
3
@Ben, es ist unbenannt und was die C ++ - Sprache betrifft, gibt es keinen "Namen, über den der Compiler entscheidet". Das Ergebnis von type_info::name()ist implementierungsdefiniert, sodass alles zurückgegeben werden kann. In der Praxis benennt der Compiler den Typ für den Linker.
Avakar
1
In letzter Zeit, wenn diese Frage gestellt, sage ich in der Regel , dass der Typ des Lambda hat einen Namen, der Compiler weiß es, es ist einfach unsagbar.
Andre Kostur
24

[C++11: 5.1.2/3]: Der Typ des Lambda-Ausdrucks (der auch der Typ des Abschlussobjekts ist) ist ein eindeutiger, unbenannter Nicht-Vereinigungsklassentyp - der als Schließungstyp bezeichnet wird - dessen Eigenschaften im Folgenden beschrieben werden. Dieser Klassentyp ist kein Aggregat (8.5.1). Der Schließungstyp wird im kleinsten Blockbereich, Klassenbereich oder Namespace-Bereich deklariert, der den entsprechenden Lambda-Ausdruck enthält . [..]

In der Klausel werden verschiedene Eigenschaften dieses Typs aufgelistet. Hier einige Highlights:

[C++11: 5.1.2/5]:Der Verschluss für einen Lambda-Ausdruck hat einen öffentlichen inlineFunktionsaufruf Operator (13.5.4) , deren Parameter und Rückgabetyp wird durch den beschriebene Lambda-Ausdruck ‚s - Parameter-Deklaration-Klausel und nachlauf return-Typ ist. [..]

[C++11: 5.1.2/6]:Der Schließungstyp für einen Lambda-Ausdruck ohne Lambda-Erfassung verfügt über eine öffentliche nicht virtuelle nicht explizite const-Konvertierungsfunktion für den Zeiger auf eine Funktion mit denselben Parameter- und Rückgabetypen wie der Funktionsaufrufoperator des Schließungstyps. Der von dieser Konvertierungsfunktion zurückgegebene Wert ist die Adresse einer Funktion, die beim Aufrufen den gleichen Effekt hat wie das Aufrufen des Funktionsaufrufoperators des Schließungstyps.

Die Folge dieser letzten Passage ist , dass, wenn Sie eine Konvertierung verwendet, die Sie zuweisen wären in der Lage LAMBDAzu pFptr.

Leichtigkeitsrennen im Orbit
quelle
2
#include <iostream>
#include <typeinfo>

#define LAMBDA [] (int i)->long { return 0l; }
int main ()
{
  long (*pFptr)(int) = LAMBDA;  // ok
  auto pAuto = LAMBDA;  // ok

  std::cout<<typeid( *pAuto ).name() << std::endl;
  std::cout<<typeid( *pFptr ).name() << std::endl;

  std::cout<<typeid( pAuto ).name() << std::endl;
  std::cout<<typeid( pFptr ).name() << std::endl;
}

Die Funktionstypen sind zwar gleich, aber das Lambda führt einen neuen Typ ein (wie ein Funktor).

BЈовић
quelle
Ich empfehle den CXXABI-Entwirrungsansatz, wenn Sie diesen Weg bereits gehen. Stattdessen benutze ich normalerweise __PRETTY_FUNCTION__wie in template<class T> const char* pretty(T && t) { return __PRETTY_FUNCTION__; }und ziehe das Extra aus, wenn es überfüllt wird. Ich ziehe es vor, die in der Vorlagenersetzung gezeigten Schritte zu sehen. Wenn Sie fehlen __PRETTY_FUNCTION__, gibt es Alternativen für MSVC usw., aber die Ergebnisse sind immer vom Compiler abhängig, aus dem gleichen Grund, aus dem CXXABI erforderlich ist.
John P
1

Es sollte auch beachtet werden, dass Lambda in einen Funktionszeiger konvertierbar ist. Typeid <> gibt jedoch ein nicht-trviales Objekt zurück, das sich von Lambda zu generischem Funktionszeiger unterscheiden sollte. Der Test für typeid <> ist also keine gültige Annahme. Im Allgemeinen möchte C ++ 11 nicht, dass wir uns um die Typspezifikation kümmern, alles was wichtig ist, wenn ein bestimmter Typ in einen Zieltyp konvertierbar ist.

Syed Raihan
quelle
Das ist fair, aber Drucktypen tragen wesentlich dazu bei, den richtigen Typ zu finden, ganz zu schweigen von den Fällen, in denen der Typ konvertierbar ist, aber andere Einschränkungen nicht erfüllt. (Ich würde immer darauf drängen, Einschränkungen zu "reifizieren", wo immer dies möglich ist, aber jemand, der dies versucht, hat umso mehr Grund, seine Arbeit während der Entwicklung zu zeigen.)
John P