Warum ist std :: function nicht gleichwertig?

Diese Frage gilt auch für boost::function und std::tr1::function .

std::function ist keine Gleichheit vergleichbar:

 #include  void foo() { } int main() { std::function f(foo), g(foo); bool are_equal(f == g); // Error: f and g are not equality comparable } 

In C ++ 11 existieren der operator== und der operator!= Überladungen einfach nicht. In einem frühen C ++ 11-Entwurf wurden die Überladungen mit dem Kommentar (N3092 §20.8.14.2) als gelöscht deklariert:

 // deleted overloads close possible hole in the type system 

Es sagt nicht, was das “mögliche Loch im Typsystem” ist. In TR1 und Boost sind die Überlastungen deklariert, aber nicht definiert. Die TR1 Spezifikation kommentiert (N1836 §3.7.2.6):

Diese Elementfunktionen sollen nicht definiert werden.

[ Hinweis: Die boolesche Konvertierung öffnet eine Lücke, in der zwei functionsinstanzen über == oder != Verglichen werden können. Diese undefinierten void Operatoren schließen die Lücke und stellen einen Fehler bei der Kompilierung sicher. Endnote ]

Mein Verständnis der “Lücke” ist, dass, wenn wir eine bool Conversion-function haben, diese Konvertierung in Gleichheitsvergleiche (und unter anderen Umständen) verwendet werden kann:

 struct S { operator bool() { return false; } }; int main() { S a, b; bool are_equal(a == b); // Uses operator bool on a and b! Oh no! } 

Ich hatte den Eindruck, dass das Safe-Bool-Idiom in C ++ 03 und die Verwendung einer expliziten Konvertierungsfunktion in C ++ 11 verwendet wurde, um dieses “Schlupfloch” zu vermeiden. Boost und TR1 verwenden beide das Safe-Bool-Idiom in der function und C ++ 11 macht die bool Conversion-function explizit.

Als ein Beispiel für eine class, die beides hat, std::shared_ptr beide eine explizite bool Konvertierungsfunktion und ist gleichheitsvergleichbar.

Warum ist std::function nicht gleichwertig? Was ist das “mögliche Loch im Typsystem?” Wie unterscheidet es sich von std::shared_ptr ?

Solutions Collecting From Web of "Warum ist std :: function nicht gleichwertig?"

Warum ist std::function nicht gleichwertig?

std::function ist ein Wrapper für beliebige aufrufbare Typen. Um Gleichheitsvergleiche überhaupt implementieren zu können, müssten Sie alle aufrufbaren Typen gleichheitsfähig machen, was eine Belastung für jeden darstellt, der ein functionsobjekt implementiert. Selbst dann würden Sie ein enges Konzept der Gleichheit bekommen, da äquivalente functionen ungleiche vergleichen würden, wenn (zum Beispiel) sie durch verbindliche Argumente in einer anderen Reihenfolge konstruiert würden. Ich glaube, dass es im allgemeinen Fall nicht möglich ist, auf Äquivalenz zu prüfen.

Was ist das “mögliche Loch im Typsystem?”

Ich denke, das bedeutet, dass es einfacher ist, die Operatoren zu löschen und sicher zu wissen, dass ihre Verwendung niemals gültigen Code liefert, als zu beweisen, dass es keine Möglichkeit für unerwünschte implizite Konvertierungen in einem bisher unentdeckten Fall gibt.

Wie unterscheidet es sich von std::shared_ptr ?

std::shared_ptr hat wohldefinierte Gleichheits-Semantik; Zwei pointers sind genau dann gleich, wenn sie entweder beide leer sind oder beide nicht leer sind und auf dasselbe Objekt zeigen.

Dies wird ausführlich in der Boost.Function FAQ diskutiert. 🙂

Ich mag mich irren, aber ich denke, dass die Gleichheit von std::function Objekten im generischen Sinne leider nicht lösbar ist. Beispielsweise:

 #include  #include  #include  void f() { printf("hello\n"); } int main() { boost::function f1 = f; boost::function f2 = boost::bind(f); f1(); f2(); } 

sind f1 und f2 gleich? Was passiert, wenn ich eine beliebige Anzahl von functionsobjekten hinzufüge, die sich einfach auf verschiedene Arten miteinander verbinden, was schließlich zu einem Aufruf von f … führt, der immer noch gleich ist?

Warum ist std :: function nicht gleichwertig?

Ich denke, der Hauptgrund ist, dass, wenn es sein würde, es nicht mit nicht gleichwertigen vergleichbaren Typen verwendet werden kann, selbst wenn der Gleichheitsvergleich niemals durchgeführt wird.

IE-Code, der einen Vergleich durchführt, sollte früh instanziiert werden – zu einem Zeitpunkt, zu dem ein Callable-Objekt in std :: function gespeichert wird, beispielsweise in einem der Konstruktoren oder Zuweisungsoperatoren.

Eine solche Beschränkung würde den scope der Anwendung stark einschränken und offensichtlich nicht für “universelle polymorphe functionsumhüllung” akzeptabel sein.


Es ist wichtig zu beachten, dass es möglich ist, boost :: function mit Callable Object zu vergleichen (aber nicht mit einer anderen boost :: -function).

functionsobjekt-Wrapper können über == oder! = Mit jedem functionsobjekt verglichen werden, das im Wrapper gespeichert werden kann.

Dies ist möglich, weil die function, die einen solchen Vergleich durchführt, auf der Basis des bekannten Operandentyps zum Zeitpunkt des Vergleichs instanziert wird.

Darüber hinaus verfügt die std :: function über die target member-function target , mit der ähnliche Vergleiche durchgeführt werden können. Tatsächlich sind die Vergleichsoperatoren von boost :: function in Bezug auf die Zielmemberfunktion implementiert .

Es gibt also keine technischen Barrieren, die die Implementierung von function_comparable blockieren.


Unter den Antworten gibt es ein gemeinsames Muster “unmöglich im Allgemeinen”:

  • Selbst dann würden Sie ein enges Konzept der Gleichheit bekommen, da äquivalente functionen ungleiche vergleichen würden, wenn (zum Beispiel) sie durch verbindliche Argumente in einer anderen Reihenfolge konstruiert würden. Ich glaube, dass es im allgemeinen Fall nicht möglich ist, auf Äquivalenz zu prüfen.

  • Ich mag mich irren, aber ich denke, dass die Gleichheit von std :: function-Objekten im generischen Sinne leider nicht lösbar ist.

  • Weil die Gleichwertigkeit von Turingmaschinen unentscheidbar ist. Bei zwei verschiedenen functionsobjekten können Sie nicht feststellen, ob sie die gleiche function berechnen oder nicht. [Diese Antwort wurde gelöscht]

Ich stimme überhaupt nicht damit überein: Es ist nicht Aufgabe der std :: -function, den Vergleich selbst durchzuführen, es ist Aufgabe, die Anfrage nur auf einen Vergleich mit darunterliegenden Objekten umzuleiten – das ist alles.

Wenn der zugrundeliegende Objekttyp keinen Vergleich definiert, wird es in jedem Fall ein Kompilierungserrors sein, std :: function wird nicht benötigt, um den Vergleichsalgorithmus abzuleiten.

Wenn der zugrundeliegende Objekttyp einen Vergleich definiert, der aber falsch arbeitet oder eine ungewöhnliche Semantik hat, ist es auch kein Problem der std :: -function selbst, sondern ein Problem des zugrundeliegenden Typs .


Es ist möglich, function_vergleichbar basierend auf std :: function zu implementieren.

Hier ist Proof-of-Concept:

 template inline bool func_compare(const Function &lhs,const Function &rhs) { typedef typename conditional < is_function::value, typename add_pointer::type, Callback >::type request_type; if (const request_type *lhs_internal = lhs.template target()) if (const request_type *rhs_internal = rhs.template target()) return *rhs_internal == *lhs_internal; return false; } #if USE_VARIADIC_TEMPLATES #define FUNC_SIG_TYPES typename ...Args #define FUNC_SIG_TYPES_PASS Args... #else #define FUNC_SIG_TYPES typename function_signature #define FUNC_SIG_TYPES_PASS function_signature #endif template struct function_comparable: function { typedef function Function; bool (*type_holder)(const Function &,const Function &); public: function_comparable() {} template function_comparable(Func f) : Function(f), type_holder(func_compare) { } template function_comparable &operator=(Func f) { Function::operator=(f); type_holder=func_compare; return *this; } friend bool operator==(const Function &lhs,const function_comparable &rhs) { return rhs.type_holder(lhs,rhs); } friend bool operator==(const function_comparable &lhs,const Function &rhs) { return rhs==lhs; } friend void swap(function_comparable &lhs,function_comparable &rhs)// noexcept { lhs.swap(rhs); lhs.type_holder.swap(rhs.type_holder); } }; 

Es gibt eine nette Eigenschaft – function_comparable kann auch mit der std :: -function verglichen werden.

Nehmen wir zum Beispiel an, wir haben einen Vektor von std :: function , und wir wollen für die functionen register_callback und unregister_callback des Benutzers angeben . Die Verwendung von function_comparable ist nur für den Parameter unregister_callback erforderlich:

 void register_callback(std::function callback); void unregister_callback(function_comparable callback); 

Live-Demo bei Ideone

Quellcode der Demo:

 // Copyright Evgeny Panasyuk 2012. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #include  #include  #include  #include  #include  #include  #include  #include  #include  #include  using namespace std; // _____________________________Implementation__________________________________________ #define USE_VARIADIC_TEMPLATES 0 template inline bool func_compare(const Function &lhs,const Function &rhs) { typedef typename conditional < is_function::value, typename add_pointer::type, Callback >::type request_type; if (const request_type *lhs_internal = lhs.template target()) if (const request_type *rhs_internal = rhs.template target()) return *rhs_internal == *lhs_internal; return false; } #if USE_VARIADIC_TEMPLATES #define FUNC_SIG_TYPES typename ...Args #define FUNC_SIG_TYPES_PASS Args... #else #define FUNC_SIG_TYPES typename function_signature #define FUNC_SIG_TYPES_PASS function_signature #endif template struct function_comparable: function { typedef function Function; bool (*type_holder)(const Function &,const Function &); public: function_comparable() {} template function_comparable(Func f) : Function(f), type_holder(func_compare) { } template function_comparable &operator=(Func f) { Function::operator=(f); type_holder=func_compare; return *this; } friend bool operator==(const Function &lhs,const function_comparable &rhs) { return rhs.type_holder(lhs,rhs); } friend bool operator==(const function_comparable &lhs,const Function &rhs) { return rhs==lhs; } // ... friend void swap(function_comparable &lhs,function_comparable &rhs)// noexcept { lhs.swap(rhs); lhs.type_holder.swap(rhs.type_holder); } }; // ________________________________Example______________________________________________ typedef void (function_signature)(); void func1() { cout < < "func1" << endl; } void func3() { cout << "func3" << endl; } class func2 { int data; public: explicit func2(int n) : data(n) {} friend bool operator==(const func2 &lhs,const func2 &rhs) { return lhs.data==rhs.data; } void operator()() { cout << "func2, data=" << data << endl; } }; struct Caller { template void operator()(Func f) { f(); } }; class Callbacks { vector> v; public: void register_callback_comparator(function_comparable callback) { v.push_back(callback); } void register_callback(function callback) { v.push_back(callback); } void unregister_callback(function_comparable callback) { auto it=find(v.begin(),v.end(),callback); if(it!=v.end()) v.erase(it); else throw runtime_error("not found"); } void call_all() { for_each(v.begin(),v.end(),Caller()); cout < < string(16,'_') << endl; } }; int main() { Callbacks cb; function_comparable f; f=func1; cb.register_callback_comparator(f); cb.register_callback(func2(1)); cb.register_callback(func2(2)); cb.register_callback(func3); cb.call_all(); cb.unregister_callback(func2(2)); cb.call_all(); cb.unregister_callback(func1); cb.call_all(); } 

Ausgabe ist:

 func1 func2, data=1 func2, data=2 func3 ________________ func1 func2, data=1 func3 ________________ func2, data=1 func3 ________________ 

PS Es scheint, als wäre es mit Hilfe von std :: type_index möglich, ähnlich wie function_comparable class zu implementieren, was auch das Ordnen (dh weniger) oder sogar das Hashing unterstützt. Aber nicht nur das Sortieren zwischen verschiedenen Typen, sondern auch das Bestellen innerhalb desselben Typs (dies erfordert Unterstützung von Typen wie LessThanComparable).

Laut http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-active.html#1240 :

Der führende Kommentar ist Teil der Geschichte von std::function , die mit N1402 eingeführt wurde. Während dieser Zeit gab es keine expliziten Konvertierungsfunktionen, und das “Safe-Bool” -Idiom (basierend auf pointersn zu Mitglied) war eine beliebte Technik. Der einzige Nachteil dieses Idioms war, dass zwei Objekte f1 und f2 vom Typ std :: der Ausdruck sind

f1 == f2;

war wohlgeformt, nur weil der eingebaute Operator == für pointers auf Element nach einer einzelnen benutzerdefinierten Konvertierung berücksichtigt wurde. Um dies zu beheben, wurde eine Überladungsgruppe von undefinierten Vergleichsfunktionen hinzugefügt, so dass die Überladungsauflösung diejenigen bevorzugen würde, die in einem Verknüpfungserrors enden. Die neue Sprachenfunktion gelöschter functionen bot einen viel besseren Diagnosemechanismus, um dieses Problem zu beheben.

In C ++ 0x werden die gelöschten functionen mit der Einführung expliziter Konvertierungsoperatoren als überflüssig erachtet, sodass sie wahrscheinlich für C ++ 0x entfernt werden.

Der zentrale Punkt dieses Problems ist, dass mit der Ersetzung des Safe-Bool-Idioms durch explizite Konvertierung in Bool das ursprüngliche “Loch im Typsystem” nicht mehr existiert und daher der Kommentar falsch ist und die überflüssigen functionsdefinitionen entfernt werden sollten auch.

Warum Sie std::function Objekte nicht vergleichen können, liegt wahrscheinlich daran, dass sie möglicherweise globale / statische functionen, Member-functionen, Funktoren usw. enthalten können, und std::function “löscht” einige Informationen über den zugrunde liegenden Typ . Die Implementierung eines Gleichheitsoperators wäre daher wahrscheinlich nicht möglich.

Tatsächlich können Sie Ziele vergleichen. Es kann funktionieren, hängt davon ab, was Sie vom Vergleich wünschen.

Hier der Code mit Ungleichheit, aber Sie können sehen, wie es funktioniert:

 template  struct Comparator { bool operator()(const Function& f1, const Function& f2) const { auto ptr1 = f1.target(); auto ptr2 = f2.target(); return ptr1 < ptr2; } }; typedef function Function; set> setOfFunc; void f11() {} int _tmain(int argc, _TCHAR* argv[]) { cout < < "was inserted - " << setOfFunc.insert(bind(&f11)).second << endl; // 1 - inserted cout << "was inserted - " << setOfFunc.insert(f11).second << endl; // 0 - not inserted cout << "# of deleted is " << setOfFunc.erase(f11) << endl; return 0; } 

Ups, es ist nur gültig seit C ++ 11.

Wie wäre es mit etwas wie dem Folgenden, das funktioniert gut zum Testen von Vorlagen.

 if (std::is_same::value) { ... } 

Das Mindeste, was getan werden könnte, ist, wenn std :: function die Adresse der function speichert, die für die Bindung an eine Zeichenkette verwendet wird, und stattdessen einen Zeichenkettenvergleich verwendet.