C++11 leitet den Typ nicht ab, wenn std::function- oder Lambda-Funktionen beteiligt sind
Lesezeit: 8 Minuten
Datenkunde
Wenn ich diese Funktion definiere,
template<class A>
set<A> test(const set<A>& input) {
return input;
}
Ich kann es mit aufrufen test(mySet) an anderer Stelle im Code, ohne den Vorlagentyp explizit definieren zu müssen. Wenn ich jedoch die folgende Funktion verwende:
template<class A>
set<A> filter(const set<A>& input,function<bool(A)> compare) {
set<A> ret;
for(auto it = input.begin(); it != input.end(); it++) {
if(compare(*it)) {
ret.insert(*it);
}
}
return ret;
}
Wenn ich diese Funktion mit aufrufe filter(mySet,[](int i) { return i%2==0; });
Ich bekomme folgenden Fehler:
Fehler: keine passende Funktion für den Aufruf von ‘filter(std::set&, main()::)’
Warum kann c++11 den Vorlagentyp nicht erraten, wenn ich die Lambda-Funktion direkt in den Ausdruck einfüge, ohne direkt eine zu erstellen std::function?
BEARBEITEN:
Auf Anraten von Luc Danton in den Kommentaren gibt es hier eine Alternative zu der Funktion, die ich zuvor hatte und bei der die Vorlagen nicht explizit übergeben werden müssen.
Diese kann per angerufen werden set<int> result = filter(myIntSet,[](int i) { i % 2 == 0; }); ohne die Vorlage zu benötigen.
Der Compiler kann sogar bis zu einem gewissen Grad die Rückgabetypen erraten, indem er das neue Schlüsselwort decltype und die Syntax des neuen Funktionsrückgabetyps verwendet. Hier ist ein Beispiel, das einen Satz in eine Karte umwandelt, wobei eine Filterfunktion und eine Funktion verwendet werden, die die Schlüssel basierend auf den Werten generiert:
Unabhängig von Ihrer Frage, aber Sie wissen, dass Ihre filter ist im Wesentlichen äquivalent zu einer nicht generischen Version von std::copy_ifnicht wahr?
– Jerry Sarg
3. April 2012 um 17:45 Uhr
Ah, std::copy_if war mir nicht bekannt, danke für den Hinweis. Dies ist jedoch Teil einer größeren Gruppe von 4 Funktionen, eine, die set => map beim Filtern konvertiert, und ich sehe keine Möglichkeit, dies mit copy_if zu implementieren und dem Benutzer zu ermöglichen, Schlüssel mit den Werten im Set zu erstellen. Aus Gründen der Konsistenz in der Verwendung entscheide ich mich dafür, dies auf diese Weise zu tun.
– Datenkunde
3. April 2012 um 18:27 Uhr
Für die Aufzeichnung, wenn Sie einen Funktor akzeptieren möchten, ist es normalerweise idiomatisch, ihn zu einem allgemeinen Vorlagenparameter zu machen, dh template<typename A, typename Predicate> set<A> filter(set<A> const& input, Predicate compare);. Wie Sie gerade gesehen haben, std::function funktioniert nicht, um zu dokumentieren, dass das übergebene Prädikat eine Signaturübereinstimmung haben sollte bool(A); es gibt andere Möglichkeiten, das zu tun. Weiterhin gibt es andere Nachteile bei der Verwendung std::function als Funktionsargument.
– Luc Danton
4. April 2012 um 3:11 Uhr
David Rodríguez – Dribeas
Das Problem liegt in der Natur von Lambdas. Sie sind Funktionsobjekte mit einem festen Satz von Eigenschaften gemäß dem Standard, aber sie sind es nicht eine Funktion. Der Standard legt fest, dass Lambdas umgewandelt werden können std::function<> mit den genauen Arten von Argumenten und, falls sie keinen Zustand haben, Funktionszeigern.
Aber das bedeutet nicht, dass ein Lambda ein ist std::function noch ein Funktionszeiger. Sie sind einzigartige Arten der Umsetzung operator().
Die Typableitung hingegen wird nur exakte Typen ableiten, ohne Konvertierungen (außer konstanten/flüchtigen Qualifikationen). Denn das Lambda ist nicht a std::function der Compiler kann den Typ im Aufruf nicht ableiten: filter(mySet,[](int i) { return i%2==0; }); irgendeine zu sein std::function<> Instanziierung.
Wie bei den anderen Beispielen konvertieren Sie im ersten das Lambda in den Funktionstyp und übergeben diesen dann. Der Compiler kann dort den Typ ableiten, wie im dritten Beispiel, wo die std::function ist ein rvalue (temporär) desselben Typs.
Wenn Sie den instanziierenden Typ angeben int Für die Vorlage, zweites Arbeitsbeispiel, kommt der Abzug nicht ins Spiel, der Compiler verwendet den Typ und konvertiert dann das Lambda in den entsprechenden Typ.
Beachten Sie, dass es nicht das Lambda ist, das die Konvertierung durchführt std::functiones ist std::function die alles akzeptiert, was aufrufbar ist.
– Xeo
25. April 2012 um 4:38 Uhr
Die Typableitung führt auch Basistyp-“Konvertierungen” durch, um vollständig zu sein.
– Yakk – Adam Nevraumont
23. März 2015 um 14:13 Uhr
Es kann zum Laufen gebracht werden, indem der Abzug für die deaktiviert wird compare Arg. Wir können dies mit dieser Signatur tun: template<class A> set<A> filter(const set<A>& input ,typename NoOp<function<bool(A)> >::type compare ) wo Noop ist definiert als template <typename T> struct NoOp { typedef T type; };
– Aaron McDaid
29. Juni 2015 um 15:45 Uhr
… Dies erlaubt A vom ersten arg abzuleiten, dann unverändert für die zu verwenden compare Arg.
– Aaron McDaid
29. Juni 2015 um 15:46 Uhr
@AaronMcDaid Das ist eine nette Lösung, aber ich möchte keine einführen NoOp struct sehr Zeit, die ich brauche. Weißt du zufällig, ob eine solche NoOp Typ ist in modernen C++-Standards als Teil von STL verfügbar? Es scheint nützlich zu sein.
– bluenote10
19. November 2018 um 15:56 Uhr
Nawaz
Vergiss deinen Fall. da dies zu komplex für eine Analyse ist.
Hier könnte man versucht sein, das zu denken T wird davon abgeleitet intund Daher sollte der obige Funktionsaufruf funktionieren. Nun, das ist nicht der Fall. Um die Sache einfach zu halten, stellen Sie sich vor, dass es eine gibt Ein weiterer Konstruktor, der dauert int als:
Was jetzt T sollte für den Anruf abgeleitet werden f(10)? Jetzt scheint es noch schwieriger zu sein.
Es handelt sich daher um einen nicht ableitbaren Kontext, der erklärt, warum Ihr Code nicht funktioniert std::function Das ist ein identischer Fall – sieht nur an der Oberfläche komplex aus. Beachten Sie, dass Lambdas sind nicht typ std::function – Sie sind im Grunde genommen Instanzen von Compiler-generierten Klassen (d. h. sie sind Funktoren von unterschiedlich Typen als std::function).
Wenn wir haben:
template <typename R, typename T>
int myfunc(std::function<R(T)> lambda)
{
return lambda(2);
}
int r = myfunc([](int i) { return i + 1; });
Es wird nicht kompiliert. Aber wenn Sie vorher erklären:
Sie können Ihre Funktion problemlos mit einem Lambda-Parameter aufrufen.
Hier gibt es 2 neue Codeteile.
Zuerst haben wir eine Funktionsdeklaration, die nur nützlich ist, um einen Funktionszeigertyp im alten Stil basierend auf gegebenen Vorlagenparametern zurückzugeben:
Unabhängig von Ihrer Frage, aber Sie wissen, dass Ihre
filter
ist im Wesentlichen äquivalent zu einer nicht generischen Version vonstd::copy_if
nicht wahr?– Jerry Sarg
3. April 2012 um 17:45 Uhr
Ah, std::copy_if war mir nicht bekannt, danke für den Hinweis. Dies ist jedoch Teil einer größeren Gruppe von 4 Funktionen, eine, die set => map beim Filtern konvertiert, und ich sehe keine Möglichkeit, dies mit copy_if zu implementieren und dem Benutzer zu ermöglichen, Schlüssel mit den Werten im Set zu erstellen. Aus Gründen der Konsistenz in der Verwendung entscheide ich mich dafür, dies auf diese Weise zu tun.
– Datenkunde
3. April 2012 um 18:27 Uhr
Für die Aufzeichnung, wenn Sie einen Funktor akzeptieren möchten, ist es normalerweise idiomatisch, ihn zu einem allgemeinen Vorlagenparameter zu machen, dh
template<typename A, typename Predicate> set<A> filter(set<A> const& input, Predicate compare);
. Wie Sie gerade gesehen haben,std::function
funktioniert nicht, um zu dokumentieren, dass das übergebene Prädikat eine Signaturübereinstimmung haben solltebool(A)
; es gibt andere Möglichkeiten, das zu tun. Weiterhin gibt es andere Nachteile bei der Verwendungstd::function
als Funktionsargument.– Luc Danton
4. April 2012 um 3:11 Uhr