library c++ function boost c++11 tr1

c++ - library - ¿Por qué std:: function not equality equality es comparable?



boost library c++ download (8)

¿Por qué std::function not equality equality es comparable?

std::function es un contenedor para los tipos invocables arbitrarios, por lo que para implementar la comparación de igualdad, deberá exigir que todos los tipos invocables sean comparables entre sí, imponiendo una carga a cualquiera que implemente un objeto de función. Incluso entonces, obtendría un concepto estrecho de igualdad, ya que las funciones equivalentes compararían la desigualdad si (por ejemplo) se construyeran mediante argumentos vinculantes en un orden diferente. Creo que es imposible probar la equivalencia en el caso general.

¿Cuál es el "agujero posible en el sistema de tipos"?

Supongo que esto significa que es más fácil eliminar los operadores, y saber con certeza que usarlos nunca dará un código válido, que demostrar que no hay posibilidad de conversiones implícitas no deseadas en un caso de esquina no descubierto previamente.

¿Cómo es diferente de std::shared_ptr ?

std::shared_ptr tiene una semántica de igualdad bien definida; dos punteros son iguales si y solo si están vacíos o no están vacíos y apuntan al mismo objeto.

Esta pregunta también se aplica a boost::function y std::tr1::function .

std::function no es comparable a la igualdad:

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

En C ++ 11, el operator== y el operator!= sobrecargas simplemente no existen. En un borrador temprano de C ++ 11, las sobrecargas se declararon como eliminadas con el comentario (N3092 §20.8.14.2):

// deleted overloads close possible hole in the type system

No dice cuál es el "agujero posible en el sistema de tipos". En TR1 y Boost, las sobrecargas se declaran pero no se definen. Los comentarios de la especificación TR1 (N1836 §3.7.2.6):

Estas funciones miembro no se definirán.

[ Nota: la conversión de tipo booleano abre una laguna mediante la cual se pueden comparar dos instancias de función mediante == o != . Estos operadores void indefinidos cierran la laguna y aseguran un error en tiempo de compilación. -finalizar nota ]

Mi comprensión del "vacío legal" es que si tenemos una función de conversión bool , esa conversión puede usarse en comparaciones de igualdad (y en otras circunstancias):

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! }

Tenía la impresión de que se utilizaba la expresión de seguridad-bool en C ++ 03 y el uso de una función de conversión explícita en C ++ 11 para evitar este "vacío legal". Boost y TR1 usan la expresión idiomática safe-bool en la function y C ++ 11 hace explícita la función de conversión bool .

Como ejemplo de una clase que tiene ambos, std::shared_ptr ambos tienen una función de conversión de bool explícita y es comparable a la igualdad.

¿Por qué std::function not equality equality es comparable? ¿Cuál es el "agujero posible en el sistema de tipos"? ¿Cómo es diferente de std::shared_ptr ?


¿Por qué std :: function not equality equality es comparable?

Creo que la razón principal es que, si así fuera, no se puede usar con tipos comparables que no sean de igualdad, incluso si la comparación de igualdad nunca se lleva a cabo.

Es decir, el código que realiza la comparación debe ser instanciado anticipadamente, en el momento en que el objeto invocable se almacena en std :: function, por ejemplo, en uno de los constructores o operadores de asignación.

Dicha limitación reduciría en gran medida el alcance de la aplicación, y obviamente no sería aceptable para la "envoltura de función polimórfica de propósito general" .

Es importante notar que es posible comparar la función boost :: con el objeto invocable (pero no con otra función boost ::)

Las envolturas de objeto de función se pueden comparar a través de == o! = Contra cualquier objeto de función que pueda almacenarse dentro del envoltorio.

Esto es posible porque la función que realiza dicha comparación se instantanea en el punto de comparación, según el tipo de operando conocido.

Además, std :: function tiene función de miembro de plantilla de destino , que se puede usar para realizar una comparación similar. De hecho, los operadores de comparación de boost :: function se implementan en términos de la función miembro de destino .

Entonces, no hay barreras técnicas que bloqueen la implementación de function_comparable .

Entre las respuestas hay un patrón común de "imposible en general":

  • Incluso entonces, obtendría un concepto estrecho de igualdad, ya que las funciones equivalentes compararían la desigualdad si (por ejemplo) se construyeran mediante argumentos vinculantes en un orden diferente. Creo que es imposible probar la equivalencia en el caso general.

  • Puedo estar equivocado, pero creo que la igualdad de los objetos std :: function no se puede resolver desafortunadamente en el sentido genérico.

  • Porque la equivalencia de las máquinas de turing es indecidible. Dado dos objetos funcionales diferentes, no es posible determinar si calculan la misma función o no. [Esa respuesta fue eliminada]

Estoy completamente en desacuerdo con esto: no es tarea de std :: function realizar la comparación en sí, su trabajo es simplemente redirigir la solicitud a la comparación con objetos subyacentes, eso es todo.

Si el tipo de objeto subyacente no define la comparación, será un error de compilación en cualquier caso, std :: function no es necesario para deducir el algoritmo de comparación.

Si el tipo de objeto subyacente define la comparación, pero que funciona mal, o tiene una semántica inusual, tampoco es un problema de la función std :: en sí, pero es un problema del tipo subyacente .

Es posible implementar function_comparable basado en std :: function.

Aquí hay una prueba de concepto:

template<typename Callback,typename Function> inline bool func_compare(const Function &lhs,const Function &rhs) { typedef typename conditional < is_function<Callback>::value, typename add_pointer<Callback>::type, Callback >::type request_type; if (const request_type *lhs_internal = lhs.template target<request_type>()) if (const request_type *rhs_internal = rhs.template target<request_type>()) 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<FUNC_SIG_TYPES> struct function_comparable: function<FUNC_SIG_TYPES_PASS> { typedef function<FUNC_SIG_TYPES_PASS> Function; bool (*type_holder)(const Function &,const Function &); public: function_comparable() {} template<typename Func> function_comparable(Func f) : Function(f), type_holder(func_compare<Func,Function>) { } template<typename Func> function_comparable &operator=(Func f) { Function::operator=(f); type_holder=func_compare<Func,Function>; 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); } };

Hay una buena propiedad: function_comparable se puede comparar con std :: function también.

Por ejemplo, supongamos que tenemos el vector de std :: function , y queremos dar para el usuario las funciones register_callback y unregister_callback . El uso de function_comparable solo se requiere para el parámetro unregister_callback :

void register_callback(std::function<function_signature> callback); void unregister_callback(function_comparable<function_signature> callback);

Demostración en vivo en Ideone

Código fuente de demostración:

// 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 <type_traits> #include <functional> #include <algorithm> #include <stdexcept> #include <iostream> #include <typeinfo> #include <utility> #include <ostream> #include <vector> #include <string> using namespace std; // _____________________________Implementation__________________________________________ #define USE_VARIADIC_TEMPLATES 0 template<typename Callback,typename Function> inline bool func_compare(const Function &lhs,const Function &rhs) { typedef typename conditional < is_function<Callback>::value, typename add_pointer<Callback>::type, Callback >::type request_type; if (const request_type *lhs_internal = lhs.template target<request_type>()) if (const request_type *rhs_internal = rhs.template target<request_type>()) 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<FUNC_SIG_TYPES> struct function_comparable: function<FUNC_SIG_TYPES_PASS> { typedef function<FUNC_SIG_TYPES_PASS> Function; bool (*type_holder)(const Function &,const Function &); public: function_comparable() {} template<typename Func> function_comparable(Func f) : Function(f), type_holder(func_compare<Func,Function>) { } template<typename Func> function_comparable &operator=(Func f) { Function::operator=(f); type_holder=func_compare<Func,Function>; 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<typename Func> void operator()(Func f) { f(); } }; class Callbacks { vector<function<function_signature>> v; public: void register_callback_comparator(function_comparable<function_signature> callback) { v.push_back(callback); } void register_callback(function<function_signature> callback) { v.push_back(callback); } void unregister_callback(function_comparable<function_signature> 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<function_signature> 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(); }

La salida es:

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

PD: parece que con la ayuda de std::type_index es posible implementar una clase similar a function_comparable , que también admite pedidos (es decir, menos) o incluso hash. Pero no solo ordenando entre diferentes tipos, sino también ordenando dentro del mismo tipo (esto requiere soporte de tipos, como LessThanComparable).


¿Qué tal si intentamos algo como lo siguiente, esto funciona bien para probar plantillas.

if (std::is_same<T1, T2>::value) { ... }


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

El comentario principal aquí es parte de la historia de std::function , que se introdujo con N1402. Durante ese tiempo no existían funciones de conversión explícitas, y la expresión "safe-bool" (basada en punteros a miembros) era una técnica popular. La única desventaja de este modismo era que, dados dos objetos f1 y f2 de tipo std :: funcionó la expresión

f1 == f2;

estaba bien formado, solo porque el operador incorporado == para el puntero al miembro se consideró después de una única conversión definida por el usuario. Para solucionar esto, se agregó un conjunto de sobrecarga de funciones de comparación indefinidas, de modo que la resolución de sobrecarga preferiría aquellas que terminan en un error de vinculación. La nueva función de lenguaje de las funciones eliminadas proporcionó un mecanismo de diagnóstico mucho mejor para solucionar este problema.

En C ++ 0x, las funciones eliminadas se consideran superfluas con la introducción de operadores de conversión explícitos, por lo que probablemente se eliminarán para C ++ 0x.

El punto central de este problema es que con la sustitución de la expresión segura-bool por conversión explícita a bool, el "agujero original en el sistema de tipo" ya no existe y, por lo tanto, el comentario es incorrecto y las definiciones de funciones superfluas deben eliminarse. también.

En cuanto a por qué no se pueden comparar objetos std::function , es probable que puedan tener funciones globales / estáticas, funciones miembro, funtores, etc., y para hacer eso, std::function "borra" cierta información sobre el tipo subyacente . Implementar un operador de igualdad probablemente no sería factible por eso.


En realidad, puedes comparar objetivos. Puede funcionar depende de lo que desee de la comparación.

Aquí el código con desigualdad, pero puedes ver cómo funciona:

template <class Function> struct Comparator { bool operator()(const Function& f1, const Function& f2) const { auto ptr1 = f1.target<Function>(); auto ptr2 = f2.target<Function>(); return ptr1 < ptr2; } }; typedef function<void(void)> Function; set<Function, Comparator<Function>> 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, solo es válido desde C ++ 11.



Puedo estar equivocado, pero creo que la igualdad de los objetos std::function lamentablemente no se puede resolver en el sentido genérico. Por ejemplo:

#include <boost/bind.hpp> #include <boost/function.hpp> #include <cstdio> void f() { printf("hello/n"); } int main() { boost::function<void()> f1 = f; boost::function<void()> f2 = boost::bind(f); f1(); f2(); }

son f1 y f2 iguales? ¿Qué sucede si agrego un número arbitrario de objetos de función que simplemente se envuelven entre sí de varias maneras que eventualmente se reduce a una llamada a f ... igual?


lo mínimo que se puede hacer es si std :: function guarda la dirección de la función utilizada para enlazar a una cadena y, en su lugar, utiliza la comparación de cadenas.