2012-04-03 3 views
41

Wenn ich diese Funktion definieren,C++ 11 nicht Typen ableiten, wenn std :: Funktion oder Lambda-Funktionen beteiligt sind

template<class A> 
set<A> test(const set<A>& input) { 
    return input; 
} 

ich es test(mySet) an anderer Stelle im Code verwenden, ohne explizit die anrufen definieren Vorlagentyp Aber wenn ich die folgende Funktion:

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 aufrufen mit filter(mySet,[](int i) { return i%2==0; }); bekomme ich folgende Fehlermeldung:

error: no matching function for call to ‘filter(std::set&, main()::)’

jedoch alle diese Versionen tun Arbeit:

std::function<bool(int)> func = [](int i) { return i%2 ==0; }; 
set<int> myNewSet = filter(mySet,func); 

set<int> myNewSet = filter<int>(mySet,[](int i) { return i%2==0; }); 

set<int> myNewSet = filter(mySet,function<bool(int)>([](int i){return i%2==0;})); 

Warum kann C++ 11 den Schablonentyp nicht erraten, wenn ich die Lambda-Funktion direkt anlege? y innerhalb des Ausdrucks ohne direkt eine std::function zu erstellen?

EDIT:

Per Beratung von Luc Danton in den Kommentaren, hier ist eine Alternative zu der Funktion hatte ich früher, die nicht die Vorlagen benötigt explizit übergeben werden.

template<class A,class CompareFunction> 
set<A> filter(const set<A>& input,CompareFunction compare) { 
    set<A> ret; 
    for(auto it = input.begin(); it != input.end(); it++) { 
     if(compare(*it)) { 
      ret.insert(*it); 
     } 
    } 
    return ret; 
} 

Dies kann durch set<int> result = filter(myIntSet,[](int i) { i % 2 == 0; });, ohne dass die Vorlage aufgerufen werden.

Der Compiler kann sogar die Rückgabetypen in gewissem Umfang erraten, indem er das neue Schlüsselwort decltype verwendet und die Syntax des neuen Funktionsrückgabetyps verwendet. Hier ist ein Beispiel, das einen Satz auf einer Karte konvertiert, unter Verwendung einer Filterfunktion und eine Funktion, die die Schlüssel auf der Grundlage der Werte erzeugt:

template<class Value,class CompareType,class IndexType> 
auto filter(const set<Value>& input,CompareType compare,IndexType index) -> map<decltype(index(*(input.begin()))),Value> { 
    map<decltype(index(*(input.begin()))),Value> ret; 
    for(auto it = input.begin(); it != input.end(); it++) { 
     if(compare(*it)) { 
      ret[index(*it)] = *it; 
     } 
    } 
    return ret; 
} 

Es kann auch ohne Verwendung der Schablone direkt aufgerufen werden, wie

map<string,int> s = filter(myIntSet,[](int i) { return i%2==0; },[](int i) { return toString(i); }); 
+5

Nicht verwandt mit Ihrer Frage, aber Sie erkennen, dass Ihr 'Filter' ist im Wesentlichen äquivalent zu einer nicht-generischen Version von' std :: copy_if', nicht wahr? –

+0

Ah, mir war std :: copy_if nicht bekannt, danke, dass du darauf hinweist. Dies ist jedoch Teil einer größeren Gruppe von 4 Funktionen, eine, die set => map während der Filterung konvertiert, und ich sehe keine Möglichkeit, das mit copy_if zu implementieren und dem Benutzer zu erlauben, Schlüssel unter Verwendung der Werte in der Menge zu erzeugen. Aus Gründen der Konsistenz verwende ich es auf diese Weise. – Datalore

Antwort

50

Das Problem ist auf die Art von Lambdas. Sie sind Funktionsobjekte mit einem festen Satz von Eigenschaften nach dem Standard, aber sie sind nicht eine Funktion. Der Standard bestimmt, dass lambdas in std::function<> mit den genauen Typen von Argumenten konvertiert werden können und, wenn sie keine Status haben, Funktionszeiger.

Aber das bedeutet nicht, dass ein Lambda ein std::function noch ein Funktionszeiger ist. Sie sind einzigartige Typen, die operator() implementieren.

Der Typabzug wiederum wird nur exakte Typen ohne Conversions (außer const/volatile qualifications) herleiten. Da der Lambda-Code kein std::function ist, kann der Compiler den Typ im Aufruf nicht ableiten: filter(mySet,[](int i) { return i%2==0; }); um eine std::function<> Instanz zu sein.

Wie die anderen Beispiele, in der ersten, konvertieren Sie das Lambda in den Funktionstyp, und dann übergeben Sie das. Der Compiler kann den Typ dort ableiten, wie im dritten Beispiel, wo std::function ein rvalue (temporär) des gleichen Typs ist.

Wenn Sie den Instanziierungstyp int der Vorlage zur Verfügung stellen, wird im zweiten Arbeitsbeispiel der Abzug nicht ins Spiel kommen, der Compiler wird den Typ verwenden und dann das Lambda in den entsprechenden Typ konvertieren.

+11

Beachten Sie, dass es nicht das Lambda ist, das die Konvertierung in 'std :: function 'durchführt, sondern' std :: function', das alles akzeptiert, was aufrufbar ist. – Xeo

+0

Der Typabzug macht auch die "Konvertierungen" vom Basistyp vollständig. – Yakk

+3

Es kann gemacht werden, um zu funktionieren, indem Abzug für das "vergleichen" Argument deaktiviert wird.Wir können mit dieser Unterschrift tun: 'Vorlage gesetzt Filter (konst gesetzt & Eingabe , Typname NoOp > :: Typ vergleichen )' 'wo Noop' als' template struct NoOp definiert ist { typedef T-Typ; }; ' –

7

Vergessen Sie Ihren Fall. denn das ist zu komplex für die Analyse.

Nehmen Sie dieses einfache Beispiel:

template<typename T> 
struct X 
{ 
    X(T data) {} 
}; 

template<typename T> 
void f(X<T> x) {} 

Jetzt f nennen wie:

f(10); 

Hier könnte man zu denken, versucht sein, dass T wird daher intund abgeleitet werden, die obige Funktion Anruf sollte funktionieren. Nun, das ist nicht der Fall. Um Sache einfach zu halten, sich vorstellen, dass es andere Konstruktor, der int wie nimmt:

template<typename T> 
struct X 
{ 
    X(T data) {} 
    X(int data) {} //another constructor 
}; 

Nun, was T sollte geschlossen werden, wenn ich schreibe f(10)? Nun, T könnte jeder Typ sein.

Beachten Sie, dass es viele andere solche Fälle geben könnte. Nehmen Sie diese Spezialisierung, zum Beispiel:

template<typename T> 
struct X<T*>   //specialized for pointers 
{ 
    X(int data) {}; 
}; 

Nun, was T sollte für den Anruf f(10) zu ableiten? Jetzt scheint es noch schwieriger.

Es ist daher nicht nachvollziehbaren Kontext, der erklärt, warum Ihr Code nicht funktioniert für std::function, die ein identischer Fall ist — sieht einfach komplex an der Oberfläche. Beachten Sie, dass lambdas sind nicht vom Typ std::function — sie sind im Grunde Instanzen Compiler generierten Klassen (das heißt, sie sind functors von verschiedenen Typen als std::function).

Verwandte Themen