2010-09-02 12 views
58

Esta pregunta también se aplica a boost::function y std::tr1::function.¿Por qué std :: function not equality equality es comparable?

std::function no es comparable 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, simplemente no existen los operator== y operator!= sobrecargas. En uno de los primeros C++ 11 proyectos, las sobrecargas fueron declarados como eliminado con el comentario (N3092 §20.8.14.2):

// deleted overloads close possible hole in the type system 

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

Estas funciones miembro se deja sin definir.

[Nota: la conversión boolean-como se abre un vacío por el que dos casos de función pueden ser comparados a través de == o !=. Estos operadores indefinidos void cierran la laguna y garantizan un error en tiempo de compilación. -fin nota]

Mi comprensión de la "escapatoria" es que si tenemos una función bool conversión, que la conversión puede ser utilizado en las 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 lenguaje seguro en C++ 03 y el uso de una función de conversión explícita en C++ 11 para evitar esta "laguna". Boost y TR1 usan la expresión salvo-bool en 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 bool explícita y es comparable a la igualdad.

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

+0

Tenga en cuenta que usted puede pedir '* a.target < ftor_type >() == * b.target < ftor_type >()' si apuntan a la igualdad de funtores-comparables. Aunque esto es un poco meticuloso (el objeto subyacente no se convertirá implícitamente al tipo solicitado), sí especifica exactamente qué semántica de comparación se está utilizando. – Potatoswatter

Respuesta

36

¿Por qué no es igual la igualdad std::function?

std::function es un contenedor de tipos arbitrarios que se puede llamar, por lo que a fin de aplicar comparación de igualdad en todo, habría que exigir que todos los tipos exigibles haber igualdad-comparible, colocando una carga para nadie implementación de un objeto 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 "posible orificio en el sistema de tipos?"

yo supongo que esto significa que es más fácil eliminar los operadores, y saber con certeza que el uso de ellos nunca dará un código válido, que para demostrar que no hay posibilidad de que las conversiones implícitas no deseados que se producen en algunos casos esquina no habían sido descubiertos.

¿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.

+0

Gracias. Estaba tan concentrado en los comentarios de "lagunas del sistema de tipos" que no pensé en la semántica. Aceptaré esta respuesta porque, en mi opinión, es la respuesta más clara a la primera y tercera partes, aunque la segunda parte se explica mejor por [Respuesta de In silico] (http://stackoverflow.com/questions/3629835/why-is- stdfunction-not-equality-comparable/3629961 # 3629961). –

6

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

El comentario importante 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 miembro) era una técnica popular . La única desventaja de este idioma fue que dados dos objetos f1 y f2 de tipo std :: funcionan de la expresión

f1 == f2;

fue bien formados

, sólo porque el operador incorporado para == el puntero al miembro se consideró después de una sola conversión definida por el usuario .Para solucionar esto, se añadió una sobrecarga conjunto de funciones de comparación no definidos, tales que la sobrecarga de resolución preferirían los que terminan en un error de vinculación. La nueva instalación de lenguaje eliminados funciones proporciona una mejor mecanismo de diagnóstico 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 idiomática segura-bool por conversión explícita a bool el "agujero en el sistema de tipos " original ya no existe y por lo tanto la el comentario es incorrecto y las definiciones de funciones superfluas también deben eliminarse.

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

+0

Correcto, pero esta (la publicación original, ya que está fijada :-P) no aborda el "por qué", solo el "qué". Consulte las preguntas frecuentes de Boost.Function para el "por qué". –

+0

Gracias por el enlace. Debería haber revisado la lista de defectos; Acabo de imaginar que se especificó lo mismo en los tres que fue intencional y no un defecto. –

24

Esto se discute a fondo en el Boost.Function FAQ. :-)

+0

Debería haber pensado que habría una pregunta frecuente de Boost que discutía esto. Gracias. –

20

puedo estar equivocado, pero creo que la igualdad de std::function objetos lamentablemente no es solucionable 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 f1f2 y la igualdad? ¿Qué sucede si agrego un número arbitrario de objetos de función que simplemente se envuelven entre sí de varias maneras que finalmente se reduce a una llamada al f ... igual?

+5

+1 para un ejemplo fácil de asimilar, los estándares y las preguntas frecuentes son geniales, pero a menudo son demasiado abstractos como para que la gente sienta el motivo. –

+0

De acuerdo con @ Matthieu: es un ejemplo bueno y simple de un problema que no había pensado. –

+0

Trabajo de la función boost :: (si admitiera la comparación): no es para deducir el algoritmo de comparación, sino simplemente para redirigir la invocación de comparación. Si el objeto subyacente no define la comparación, entonces es simplemente un error de compilación. Si el objeto subyacente define la comparación incorrecta, tampoco es problema de la función boost ::. –

-1

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

12

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

Creo que la razón principal es que, si así fuera, no se puede usar con tipos no equivalentes de igualdad, incluso si la comparación de igualdad nunca se realiza.

I.e. 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 ámbito de aplicación, y obviamente no es aceptable para "contenedor de función polimórfica de propósito general".


Es improtante tener en cuenta, que es posible compare boost::function con objeto invocable (pero no con otro impulso :: función)

envoltorios de objetos de función se pueden comparar a través == o! = contra cualquier objeto de función que pueda almacenarse dentro del contenedor.

Esto es posible, ya que la función que realiza esta comparación se instantiniated en el punto de comparación, sobre la base de conocimientos tipo de operando.

Por otra parte, std :: function tiene target template member function, que se puede utilizar para realizar una comparación similar. De hecho, los operadores de comparación de boost :: function son implemented in terms of target member function.

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


Entre las respuestas no es común "imposible en general" patrón:

  • Incluso entonces, se obtendría un concepto estrecho de la igualdad, como funciones equivalentes compararían desigual si (por ejemplo) fueron construidos por 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 es de std :: objetos función no es solucionable por desgracia en el sentido genérico.

  • Debido a 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. [Se suprimió Esa respuesta]

estoy completamente en desacuerdo con esto: no es el trabajo de std :: función para realizar la comparación en sí, es el trabajo es sólo para redirigir solicitud a comparación con los 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 ::, pero es un problema de subyacente tipo.


Es posible implementar function_comparable basado en std :: función.

Aquí está la 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); 
    } 
}; 

Existe cierta propiedad agradable - function_comparable puede ser comparado contra std :: función también.

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

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

Live demo at Ideone

El 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(); 
} 

de salida es:

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

P.S. 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).

3

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

Aquí el código con la desigualdad, pero se puede 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, sólo es válida ya que C++ 11.

+1

Genial ... resolvió mi problema. – ashish

0

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

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