2012-07-24 5 views
17

Ich spiele mit den funktionalen Funktionen von C++ 11 herum. Eine Sache, die ich seltsam finde, ist, dass der Typ einer Lambda-Funktion tatsächlich KEINE Funktion <> Typ ist. Lambda's scheinen mit dem Typ-Inferenz-Mechanismus nicht besonders gut zu spielen.Warum haben Lambda-Funktionen in C++ 11 keine Funktion <> Typen?

Attached ist ein kleines Beispiel, in dem ich getestet habe, wie man die zwei Argumente einer Funktion zum Hinzufügen von zwei Ganzzahlen umkehrt. (Der verwendete Compiler war gcc 4.6.2 unter MinGW.) Im Beispiel wurde der Typ für addInt_f explizit mit der Funktion <> definiert, während addInt_l ein Lambda ist, dessen Typ typeninferenziert ist mit .

Wenn ich den Code kompiliert, kann die flip Funktion explizit typisierte Version von addInt akzeptieren, aber die Lambda-Version nicht, einen Fehler zu geben zu sagen, dass, testCppBind.cpp:15:27: error: no matching function for call to 'flip(<lambda(int, int)>&)'

Die nächsten paar Zeilen zeigen, dass die Lambda-Version (sowie eine 'rohe' Version) kann akzeptiert werden, wenn sie explizit in die entsprechende Funktion <> type umgewandelt wird.

Also meine Fragen sind:

  1. Warum ist es, dass eine Lambda-Funktion nicht in erster Linie function<> Typ hat? In dem kleinen Beispiel, warum nicht addInt_lfunction<int (int,int)> als der Typ anstelle eines anderen, lambda Typ haben? Was ist der Unterschied zwischen einer Funktion/einem funktionalen Objekt und einem Lambda?

  2. Wenn es einen grundlegenden Grund gibt, dass diese zwei unterschiedlich sein müssen. Ich habe gehört, dass Lambdas in function<> konvertiert werden können, aber sie sind anders. Ist das ein Design-Problem/ein Fehler von C++ 11, ein Implementierungsproblem oder gibt es einen Vorteil darin, die beiden so zu unterscheiden, wie es ist? Es scheint, dass die Typensignatur von addInt_l allein genügend Informationen über den Parameter und die Rückgabetypen der Funktion bereitgestellt hat.

  3. Gibt es eine Möglichkeit, das Lambda zu schreiben, damit das oben erwähnte explizite Typ-Casting vermieden werden kann?

Vielen Dank im Voraus.

//-- testCppBind.cpp -- 
    #include <functional> 
    using namespace std; 
    using namespace std::placeholders; 

    template <typename T1,typename T2, typename T3> 
    function<T3 (T2, T1)> flip(function<T3 (T1, T2)> f) { return bind(f,_2,_1);} 

    function<int (int,int)> addInt_f = [](int a,int b) -> int { return a + b;}; 
    auto addInt_l = [](int a,int b) -> int { return a + b;}; 

    int addInt0(int a, int b) { return a+b;} 

    int main() { 
     auto ff = flip(addInt_f); //ok 
     auto ff1 = flip(addInt_l); //not ok 
     auto ff2 = flip((function<int (int,int)>)addInt_l); //ok 
     auto ff3 = flip((function<int (int,int)>)addInt0); //ok 

     return 0; 
    } 

+4

Sie sollten nicht 'std :: function' Argumente nehmen, hauptsächlich, weil es den Typabzug hemmt (was Ihr Problem hier ist). –

+1

Verwandt: [C++ 11 leitet Typ nicht ab, wenn std :: function oder Lambda-Funktionen beteiligt sind] (http://stackoverflow.com/q/9998402/487781) – hardmath

+1

Lambdas werden in anonyme Functors (oder Funktionen, wenn sie Erfasse keine Umgebung). Die Umwandlung in std :: function würde eine starke Kopplung zwischen der Sprache und der Bibliothek einführen und wäre daher eine sehr schlechte Idee. – MFH

Antwort

39

std::function ist ein Werkzeug nützlich store jede Art von aufrufbaren Objekt unabhängig von dessen Typ. Um dies zu tun, muss eine Art Löschtechnik angewendet werden, und dies erfordert einige Overhead-Vorgänge.

Jede aufrufbar kann implizit in eine std::function umgewandelt werden, und deshalb funktioniert es normalerweise nahtlos.

wiederhole ich werde sicherstellen, dass es klar wird: std::function für Lambda-Ausdrücke oder Funktionszeiger nicht nur etwas ist: Es ist für ist jede Art von aufrufbar. Dazu gehören beispielsweise Dinge wie struct some_callable { void operator()() {} };. Das ist ein einfacher, aber es könnte so etwas wie dies stattdessen sein: oben

struct some_polymorphic_callable { 
    template <typename T> 
    void operator()(T); 
}; 

Ein Lambda ist nur noch ein weiteres Objekt aufrufbar, wie Instanzen des some_callable Objekts. Es kann in einem std::function gespeichert werden, da es aufrufbar ist, aber es hat nicht den Typ löschen Overhead von std::function.

Und das Komitee plant, lambdas in Zukunft polymorph zu machen, d. H. Lambdas, die wie oben aussehen. Welche Art von Lambda wäre das? std::function?


Jetzt ... Vorlage Parameter Abzug oder implizite Konvertierungen. Wähle eins. Das ist eine Regel von C++ - Vorlagen.

Um ein Lambda als std::function Argument zu übergeben, muss es implizit konvertiert werden. Ein Argument std::function bedeutet, dass Sie implizite Conversions über Typabzug wählen. Ihre Funktionsvorlage muss jedoch die Signatur ableiten oder explizit bereitstellen.

Die Lösung? Beschränken Sie Ihre Anrufer nicht auf std::function. Akzeptieren jede Art von aufrufbaren.

template <typename Fun> 
auto flip(Fun&& f) -> decltype(std::bind(std::forward<Fun>(f),_2,_1)) 
{ return std::bind(std::forward<Fun>(f),_2,_1); } 

Sie jetzt denken vielleicht, warum brauchen wir std::function dann. std::function bietet eine Art Löschung für callables mit einer bekannten Signatur. Das macht es im Wesentlichen nützlich, die von der Art gelöschten callables zu speichern und virtual Schnittstellen zu schreiben.

+0

: Danke für Ihre klare Erklärung. IMHO, die Tatsache, dass std :: -Funktion nur für den Speicher verwendet werden kann, ist eher enttäuschend. Wenn ich Ihre Antwort richtig verstehe, soll das Overhead vermeiden? Gilt das auch für die Smart Pointer? Außerdem sehe ich in Ihrem 'some_polymorphic_callable' Beispiel nicht, warum Funktion <> type den Typ für die Templated Lambda's nicht ausdrücken kann, da Funktion <> bereits Template Parameter haben kann. Wenn Overhead das einzige Problem ist, hat irgendjemand eine Lightweight-Funktion in Betracht gezogen? Es würde STL viel brauchbarer machen. – tinlyx

+0

Wenn im "declltype" -Beispiel der Funktionskörper aus 25 Codezeilen anstelle eines Einzeilers besteht, müssen wir dann 25 Zeilen in declltype einfügen? Außerdem sieht die Verwendung von '' fast wie void * aus oder ein Makro für mich. Eine falsche Verwendung von 'Fun' kann nur festgestellt werden, wenn ein Kompilierungsfehler auftritt. Im Vergleich dazu würde eine Funktion <> Parametertyp (falls verwendbar) der Person und dem Computer die Typ-Signatur eindeutig mitteilen. Zugegebenermaßen habe ich sehr begrenzte Kenntnisse über "Typ-Löschung" und dergleichen. Danke, dass du mir die Regeln gesagt hast. Ich frage mich nur, warum es so sein muss. – tinlyx

+0

@TingL Der Typ für das polymorphe Lambda kann nicht ausgedrückt werden, weil 'function <>' * ein * Signaturargument benötigt, während das polymorphe Lambda * eine unbegrenzte Anzahl verschiedener Signaturen * hätte (deshalb heißt es polymorph: es hat viele Formen)). Das Overhead-Problem gilt nicht ganz für Smartpointer. 'unique_ptr' hat wirklich * null * Overhead (das war eines der Designziele). 'shared_ptr' kann etwas Overhead haben, wenn Sie kein Multithread-Programm ausführen, aber Multithread-Programme sind ein Fall, in dem es eher verwendet wird. –

5
  1. Da function<> beschäftigt type erasure. Dadurch können mehrere verschiedene funktionsähnliche Typen in einer function<> gespeichert werden, was jedoch einen kleinen Laufzeitnachteil bedeutet. Typ löschen verbirgt den tatsächlichen Typ (Ihr spezifisches Lambda) hinter einer virtuellen Funktionsschnittstelle.
  2. Es gibt einen Vorteil zu diesem: eine der C++ Design "Axiome" ist, Overhead nie hinzuzufügen, es sei denn es wirklich benötigt wird. Mit diesem Setup haben Sie keinen Overhead bei der Verwendung von Typ-Inferenz (verwenden Sie oder übergeben als Vorlage Parameter), aber Sie haben immer noch die ganze Flexibilität, mit Nicht-Vorlage-Code über function<> zu verbinden. Beachten Sie auch, dass function<> kein Sprachkonstrukt ist, sondern eine Komponente der Standardbibliothek, die mit einfachen Sprachfunktionen implementiert werden kann.
  3. Nein, aber Sie können die Funktion schreiben, um nur den Typ der Funktion (Sprachkonstrukt) anstelle der Spezifik der function<> (Bibliothekskonstrukt) zu nehmen.Das macht es natürlich sehr viel schwieriger, den Rückgabetyp tatsächlich aufzuschreiben, da er nicht direkt die Parametertypen angibt. Mit einigen Meta-Programmierung a la Boost.FunctionTypes können Sie jedoch diese aus der Funktion, die Sie übergeben, abzuleiten. Es gibt einige Fälle, in denen dies nicht möglich ist, zum Beispiel mit Funktoren, die eine Vorlage operator() haben.
Verwandte Themen