c ++ 11: Wie schreibe ich eine Wrapper-Funktion, um 'std :: function' Objekte zu machen

8

Ich versuche, einen Wrapper make_function zu schreiben, der wie std::make_pair ein Objekt std::function aus geeigneten Callable-Objekten erstellen kann.

Genau wie make_pair , erzeugt foo für einen Funktionszeiger auto f0 = make_function(foo); ein std::function Funktionsobjekt f0 der Signatur vom richtigen Typ. Nur zur Klarstellung, es macht mir nichts aus, gelegentlich type-Parameter an make_function zu geben, falls es schwierig (oder unmöglich) ist, den Typ vollständig aus den Parametern abzuleiten.

Was ich bis jetzt herausgefunden habe (Code unten) funktioniert gut für Lambdas, einige Funktionszeiger und Funktoren (ich habe keine flüchtigen Substanzen berücksichtigt). Aber ich konnte es nicht für std::bind oder std::bind<R> Ergebnisse arbeiten. Im folgenden Code

%Vor%

würde nicht kompilieren / arbeiten, mit gcc 4.8.1. Ich nehme an, dass ich das operator() für das bind Ergebnis nicht richtig erfasst habe, aber ich bin mir nicht sicher, wie ich es beheben kann.

Jede Hilfe zur Behebung dieses Falls oder Verbesserung in anderen Fällen wird geschätzt.

Meine Frage ist natürlich, wie Sie den Fehler im folgenden Beispiel beheben können.

Im Hintergrund kann einer der Fälle, in denen ich diesen Wrapper verwende, bei dieser Frage gefunden werden: Wie macht man C ++ 11-Funktionen, die die Funktion & lt; & gt; Parameter akzeptieren Lambdas automatisch . Wenn Sie die Verwendung von std::function oder meine spezielle Art der Verwendung nicht genehmigen, hinterlassen Sie bitte Ihre Kommentare in diesem Beitrag und besprechen Sie technische Probleme hier.

--- BEARBEITEN ---

Aus einigen Kommentaren habe ich erfahren, dass dies auf das Mehrdeutigkeitsproblem zurückzuführen ist (Ambiguität des Funktionsaufrufoperators () von std::bind results). Wie von der Antwort von @Mooing Duck gezeigt, besteht die Lösung darin, die Parametertypen explizit anzugeben. Ich habe den Code aktualisiert, um die drei Funktionen in der Antwort von @Mooing Duck zu kombinieren (mit leichten Änderungen der Typparameter), so dass der make_function -Wrapper nun eindeutige Fälle wie zuvor behandeln / typenableiten kann und die Spezifikation der vollständigen Typsignatur erlaubt wenn es Unklarheiten gibt.

(Mein ursprünglicher Code für die eindeutigen Fälle lautet: Ссылка und kann getestet werden unter: Ссылка ):

%Vor%

Ideone

    
tinlyx 12.02.2014, 20:21
quelle

3 Antworten

6

Das Problem ist, dass Ihr Code nicht richtig mit lambdas, bind oder funktionaloids arbeitet. Ihr Code geht davon aus, dass all diese Parameter keine Parameter annehmen. Um diese zu behandeln, müssen Sie die Parametertypen angeben:

%Vor%

Variablen:

%Vor%

Tests:

%Vor%

Ссылка Der einzige Grund, warum Ihr Lambda erfolgreich war, ist, dass es implizit in einen Funktionszeiger umgewandelt werden konnte, weil Ihr Beispiel es ist erfasst keinen Zustand. Beachten Sie, dass mein Code die Parametertypen für überladene Funktionen benötigt, also Funktionaloiden mit überladenem operator () (einschließlich Bind), aber jetzt in der Lage ist, alle nicht überladenen Funktionen abzuleiten.

Die decltype -Zeilen sind kompliziert, aber sie werden verwendet, um die Rückgabetypen abzuleiten. Beachten Sie, dass ich in keiner meiner Tests den Rückgabetyp angeben muss. Lassen Sie uns make_function<short,int,int> abbauen, als ob T ist char(*)(short, int, int) :

%Vor%     
Mooing Duck 12.02.2014, 21:32
quelle
6

Im Allgemeinen können Sie es nicht ohne die strenge Einschränkung lösen, dass alles, was Sie an make_function übergeben, nur mit genau einer Signatur aufgerufen werden kann.

Was wirst du mit etwas wie:

tun? %Vor%

C ++ 14 generische Lambdas haben das gleiche Problem.

Die Signatur in std::function basiert darauf, wie Sie sie aufrufen möchten und nicht darauf, wie Sie sie erstellen / zuweisen.

Sie können es auch nicht für std::bind lösen, da dies unbestimmt ist:

%Vor%     
Nevin 12.02.2014 20:35
quelle
2

Der Hauptgrund, warum Sie lambdas in std::function konvertieren möchten, ist, dass Sie zwei Überladungen haben wollen, von denen jede verschiedene Signaturen annimmt.

Eine gute Möglichkeit, dies zu lösen, ist std::result_of .

Angenommen, Sie erstellen eine Schleifenkontrollstruktur, die ein Lambda oder ein anderes funktionales Element verwendet. Wenn diese Funktion ungültig zurückgibt, möchten Sie unkontrolliert Schleife. Wenn es bool oder ähnliches zurückgibt, möchten Sie eine Schleife ausführen, während es true zurückgibt. Wenn es enum ControlFlow zurückgibt, möchten Sie auf den Rückgabewert ControlFlow achten ( continue oder break , sagen wir mal). Die fragliche Funktion benötigt entweder das Element, das überläuft, und optional einige zusätzliche Daten (der Index in der Iteration, vielleicht einige "Ort" -Metadaten über das Element usw.).

std::result_of lässt Sie so tun, als würde der übergebene Typ mit einer anderen Anzahl von Argumenten aufgerufen. Eine Merkmalsklasse könnte dann herausfinden, welche der obigen Signaturen die "beste Übereinstimmung" ist, und dann zu der Implementierung routen, die diese Signatur handhabt (möglicherweise durch Umhüllen der "einfacheren" Fälle in einem Lambda und Aufrufen der komplexeren Fälle) / p>

Naiv, Ihr make_function könnte dieses Problem haben, weil Sie dann einfach die verschiedenen std::function< blah(etc) > Fälle überladen könnten. Aber mit den auto -Parametern, die in der Pipe herunterkommen, und std::bind , die bereits eine perfekte Weiterleitung machen, behandelt dies nur die einfachsten Fälle.

std::result_of traitches-Klassen (und möglicherweise verwandte Konzeptvergleiche und requires -Klauseln) und Tag-Dispatching (oder SFINAE als letztes Mittel).

Der große Nachteil ist, dass Sie die Überschreibungsreihenfolge halb manuell manuell verwalten müssen. Ich könnte ein Hilfsprogramm in Hilfsklassen sehen, wo Sie eine Liste von zu vergleichenden Signaturen bereitstellen, und es erzeugt entweder ein boost::variant oder Sie erzeugen auch eine kanonische Ausgabe und eine Konvertierungsmethode für diese kanonische Ausgabe.

Die kurze Antwort? Die Implementierung von std::bind ist implementationsspezifische Details, aber sie kann eine perfekte Weiterleitung von variadischen Parameterpaketen beinhalten und ist daher nicht geeignet für Ihre "Holen Sie sich die Adresse der einzigen konkreten operator() " Technik, die Sie sind verwenden.

Als ein anderes Beispiel:

%Vor%

sollte wie folgt geschrieben werden:

%Vor%

wieder, result_of ist die richtige Lösung, die Dinge nicht unnötigerweise in std::function umwandelt.

Für fold_right erhalten wir:

%Vor%

was wiederum jede Art des Löschens in f überspringt. Und wenn Sie wirklich das Löschen auf f vornehmen möchten, können Sie Folgendes tun:

%Vor%

std::function ist ein Löschvorgang -Objekt. Sie geben Erase ein, weil Sie einen Typ verwenden möchten, an den Sie den Typ nicht übertragen möchten. Wenn man von einem Typ ableitet, welche Art von Ergebnis-Lösch-Objekt du erstellen solltest, ist das fast immer die falsche Antwort, weil du die ganze Abhängigkeit von nicht-typisierten gelöschten Fällen hast, und die ganze Ineffizienz des Typen-Löschens.

Das Ergebnis von

make_function hängt vom vollständigen Typ der Quelle ab, wodurch die Typauslöschung fast vollständig nutzlos wird.

    
Yakk 12.02.2014 21:43
quelle

Tags und Links