web-dev-qa-db-fra.com

Pourquoi std :: function n'est-il pas comparable?

Cette question s'applique également à boost::function et std::tr1::function.

std::function n'est pas une égalité comparable:

#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, les surcharges operator== et operator!= n'existent tout simplement pas. Dans une version préliminaire de C++ 11, les surcharges étaient déclarées comme supprimées avec le commentaire (N3092 §20.8.14.2):

// deleted overloads close possible hole in the type system

Il ne dit pas ce qu'est le "trou possible dans le système de types". Dans TR1 et Boost, les surcharges sont déclarées mais non définies. La spécification TR1 commente (N1836 §3.7.2.6):

Ces fonctions membres ne doivent pas être définies.

[ Remarque: la conversion de type booléen ouvre une faille permettant de comparer deux instances de fonctions via == ou !=. Ces opérateurs non définis void ferment la faille et garantissent une erreur lors de la compilation. —fin note ]

D'après ce que je comprends de "l'échappatoire", si nous avons une fonction de conversion bool, cette conversion peut être utilisée dans les comparaisons d'égalité (et dans d'autres circonstances):

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

J'avais l'impression que l'idiome safe-bool en C++ 03 et l'utilisation d'une fonction de conversion explicite en C++ 11 étaient utilisés pour éviter cette "échappatoire". Boost et TR1 utilisent l'idiome safe-bool dans function et C++ 11 rend la fonction de conversion bool explicite.

Comme exemple de classe qui a les deux, std::shared_ptr possède une fonction de conversion explicite bool et est comparable en égalité.

Pourquoi std::function n'est-il pas comparable? Quel est le "trou possible dans le système de types?" En quoi est-ce différent de std::shared_ptr?

59
James McNellis

Pourquoi std::function n'est pas une égalité comparable?

std::function est un wrapper pour les types appelables arbitraires. Pour implémenter la comparaison d'égalité, vous devez donc exiger que tous les types appelables soient comparables, ce qui alourdira la tâche de quiconque implémentera un objet fonction. Même dans ce cas, vous obtiendrez un concept étroit d’égalité, car des fonctions équivalentes compareraient des inégalités si (par exemple) elles étaient construites par des arguments de liaison dans un ordre différent. Je crois qu'il est impossible de tester l'équivalence dans le cas général.

Quel est le "trou possible dans le système de types?"

J'imagine que cela signifie qu'il est plus facile de supprimer les opérateurs et de savoir avec certitude que leur utilisation ne donnera jamais un code valide, que de prouver qu'il est impossible que des conversions implicites non désirées se produisent dans certains cas non encore découverts.

En quoi est-il différent de std::shared_ptr ?

std::shared_ptr a une sémantique d'égalité bien définie; deux pointeurs sont égaux si et seulement s'ils sont soit vides, soit non vides et pointant vers le même objet.

37
Mike Seymour

Ceci est décrit en détail dans Boost.Function FAQ . :-)

25
Chris Jester-Young

Je peux me tromper, mais je pense que l’égalité des objets std::function n’est malheureusement pas résolue au sens générique. Par exemple:

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

f1 et f2 sont-ils égaux? Que se passe-t-il si j'ajoute un nombre arbitraire d'objets de fonction qui se recouvrent simplement de différentes manières, ce qui revient finalement à un appel à f... toujours égal?

20
Evan Teran

Pourquoi std :: function n'est-il pas comparable?

Je pense que la raison principale est que si tel est le cas, il ne peut pas être utilisé avec des types comparables non égaux, même si la comparaison d’égalité n’est jamais effectuée.

C'est à dire. le code qui effectue la comparaison doit être instancié tôt - au moment où l'objet appelable est stocké dans std :: function, par exemple dans l'un des constructeurs ou des opérateurs d'affectation.

Une telle limitation réduirait considérablement le champ d'application et ne serait évidemment pas acceptable pour "wrapper de fonction polymorphe à usage général" .


Il est improtant de noter qu'il est possible de comparer boost :: function avec un objet appelable (mais pas avec un autre boost :: function) 

Les wrappers d'objet de fonction peuvent être comparés via == ou! = Avec tout objet de fonction pouvant être stocké dans le wrapper.

Cela est possible car la fonction qui effectue une telle comparaison est présentée instantanément au point de comparaison, en fonction du type d'opérande connu.

De plus, std :: function a target template, fonction membre , qui peut être utilisé pour effectuer une comparaison similaire. En fait, les opérateurs de comparaison de boost :: function sont implémentés sous la forme de target fonction membre .

Il n’existe donc aucune barrière technique qui bloque la mise en oeuvre de function_comparable .


Parmi les réponses, il y a un schéma commun "impossible en général":

  • Même dans ce cas, vous obtiendrez un concept étroit d’égalité, car des fonctions équivalentes compareraient des inégalités si (par exemple) elles étaient construites par des arguments de liaison dans un ordre différent. Je crois qu'il est impossible de tester l'équivalence dans le cas général.

  • Je peux me tromper, mais je pense que l’égalité entre les objets std :: function ne peut malheureusement pas être résolue au sens générique.

  • Parce que l'équivalence des machines de turing est indécidable. Étant donné deux objets de fonction différents, vous ne pouvez pas déterminer s'ils calculent la même fonction ou non. [Cette réponse a été supprimée]

Je suis complètement en désaccord avec ceci: ce n'est pas le travail de std :: function de faire la comparaison elle-même, c'est juste le travail de redirect demande de comparaison avec les objets sous-jacents - c'est tout.

Si le type d'objet sous-jacent ne définit pas la comparaison - il s'agira d'une erreur de compilation, std :: function n'est pas obligé de déduire l'algorithme de comparaison.

Si le type d'objet sous-jacent définit la comparaison, mais fonctionne mal, ou a une sémantique inhabituelle - ce n'est pas un problème de std :: function lui-même, mais un problème de sous-type .


Il est possible d'implémenter function_comparable basé sur std :: function.

Voici la preuve de concept:

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

Il existe des propriétés de Nice - function_comparable peuvent être comparées à std :: function aussi.

Par exemple, supposons que nous ayons le vecteur de std :: function , et que nous voulons donner à l'utilisateur register_callback et unregister_callback functions. L'utilisation de function_comparable n'est requise que pour unregister_callback paramètre:

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

Démonstration en direct à Ideone

Code source de la démo:

//             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 sortie est:

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

P.S. Il semble qu'avec l'aide de std :: type_index , il soit possible d'implémenter une méthode similaire à function_comparable class, qui prend également en charge l'ordre (c'est-à-dire moins) ou même le hachage. Mais pas seulement la commande entre différents types, mais aussi la commande dans le même type (cela nécessite le support de types, comme LessThanComparable).

13
Evgeny Panasyuk

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

Le commentaire principal ici fait partie de De l'historique de std::function, qui a été introduit avec N1402. Pendant cette période de , Il n'existait aucune fonction de conversion explicite Et l'idiome "safe-bool" (Basé sur les pointeurs vers les membres) était une technique populaire de . Le seul inconvénient De cet idiome est que A deux objets f1 et f2 de type Std :: function l'expression 

f1 == f2;

était bien formé, simplement parce que l'opérateur intégré == pour le pointeur sur le membre a été pris en compte après une seule conversion définie par l'utilisateur . Pour résoudre ce problème, Un ensemble de surcharges de fonctions de comparaison non définies A été ajouté, de telle sorte que La résolution de surcharge préfère À celles qui se retrouvent dans une erreur de liaison. ____.] La nouvelle installation linguistique des fonctions Supprimées offrait un mécanisme de diagnostic Bien meilleur pour résoudre ce problème .

En C++ 0x, les fonctions supprimées étant considérées comme superflues avec l'introduction d'opérateurs de conversion explicites, elles seront probablement supprimées pour C++ 0x.

Le point central de cette question est, Qu’avec le remplacement de l’idiome Safe-bool par une conversion explicite Afin de bouder le "trou original" dans le type system "n'existe plus et . Par conséquent, le commentaire est erroné et les définitions de fonctions superflues doivent également être supprimées.

La raison pour laquelle vous ne pouvez pas comparer les objets std::function, c’est probablement parce qu’ils peuvent éventuellement contenir des fonctions globales/statiques, des fonctions membres, des foncteurs, etc., et std::function "efface" certaines informations sur le type sous-jacent. L'implémentation d'un opérateur d'égalité ne serait probablement pas réalisable pour cette raison.

6
In silico

En fait, vous pouvez comparer les objectifs. Cela peut marcher dépend de ce que vous voulez de la comparaison. 

Voici le code avec l'inégalité, mais vous pouvez voir comment cela fonctionne:

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, il n'est valable que depuis C++ 11.

3
Yola

Pourquoi ne pas essayer quelque chose comme ce qui suit, cela fonctionne bien pour tester des modèles.

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