62 votes

Pourquoi est std::function et non l'égalité comparable?

Cette question s'applique aussi à d' boost::function et std::tr1::function.

std::function n'est pas l'é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, operator== et operator!= surcharges n'existent tout simplement pas. Dans un début de C++11 projet de, les surcharges ont été déclarés comme étant supprimés avec le commentaire (N3092 §20.8.14.2):

// deleted overloads close possible hole in the type system

Il ne dit pas ce que la possibilité "d'un trou dans le système de type". Dans TR1 et Boost, les surcharges sont déclarés mais non définis. Le TR1 spécification commentaires (N1836 §3.7.2.6):

Ces fonctions de membre doivent être laissés dans le vague.

[Note: le booléen-comme la conversion ouvre une faille qui fonction à deux instances peuvent être comparés par == ou !=. Ces undefined void opérateurs à proximité de la faille et de veiller à une erreur de compilation. -la note de fin]

Ma compréhension de la "faille" est que si nous avons un bool fonction de conversion, la conversion peut être utilisé dans la comparaison 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'étais sous l'impression que le coffre-bool idiome en C++03 et l'utilisation d'une conversion explicite de la fonction en C++11 a été utilisé pour éviter cette "lacune." De stimuler et de TR1 à la fois utiliser le coffre-bool idiome en function et C++11 fait l' bool fonction de conversion explicite.

Comme un exemple d'une classe qui a les deux, std::shared_ptr à la fois explicitement bool de fonctions de conversion et d'égalité comparable.

Pourquoi est - std::function pas d'égalité comparable? Qu'est-ce que la possibilité "d'un trou dans le type de système?" Comment est-il différent de std::shared_ptr?

37voto

Mike Seymour Points 130519

Pourquoi est - std::function pas d'égalité comparable?

std::function est un wrapper pour arbitraire appelable types, afin de mettre en œuvre l'égalité de comparaison à tous, vous auriez à exiger que tous les appelable types de l'égalité-comparible, placer un fardeau sur quelqu'mise en œuvre d'une fonction d'objet. Même alors, vous obtiendrez un concept étroit de l'égalité, comme l'équivalent des fonctions serait de comparer l'inégalité (par exemple) si elles ont été construites par la liaison des arguments dans un ordre différent. Je crois qu'il est impossible de tester l'équivalence dans le cas général.

Qu'est-ce que la possibilité "d'un trou dans le type de système?"

Je suppose que cela signifie qu'il est plus facile de supprimer les opérateurs, et soyez certains que leur utilisation ne donnera jamais le code est valide, que pour prouver il n'y a pas de possibilité indésirables des conversions implicites dans certains jusque-là inconnues des cas de coin.

Comment est-il différent de std::shared_ptr?

std::shared_ptr a bien défini l'égalité sémantique; deux pointeurs sont égaux si et seulement si ils sont tous les deux vides, ou les deux non-vide et pointant vers le même objet.

26voto

Chris Jester-Young Points 102876

Ceci est discuté dans le coup de pouce.Fonction de la FAQ. :-)

22voto

Evan Teran Points 42370

J'ai peut-être tort, mais je pense que l'égalité est de std::function objets est, malheureusement, n'est pas soluble dans le 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();
}

sont - f1 et f2 de l'égalité? Ce que si j'ajoute un nombre arbitraire d'objets de fonction qui enroulez simplement les uns les autres de diverses manières qui finalement se résume à un appel à l' f... toujours égale?

13voto

Evgeny Panasyuk Points 5408

Pourquoi est std::function et non l'égalité comparable?

Je pense que la raison principale est que si il serait, alors il ne peut pas être utilisé avec la non égalité de types comparables, même si la comparaison d'égalité n'est jamais effectuée.

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

Une telle limitation serait grandement réduire la portée de l'application, et évidemment pas acceptable "à des fins générales fonction polymorphe wrapper".


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

La fonction de l'objet wrappers peuvent être comparées via == ou != contre toute de la fonction de l'objet qui peut être stocké à l'intérieur de l'emballage.

C'est possible, parce que la fonction qui effectue la comparaison instantiniated à un point de comparaison, en se fondant sur le savoir type d'opérande.

En outre, std::function a cible modèle de fonction de membre, qui peut être utilisé pour effectuer la comparaison similaire. En fait, boost::fonction de comparaison les opérateurs sont mis en œuvre en termes de cible de la fonction membre.

Donc, il n'y a pas d'obstacles techniques qui bloquent implementantion de function_comparable.


Parmi les réponses, il est commun "impossible en général" pattern:

  • Même alors, vous obtiendrez un concept étroit de l'égalité, comme l'équivalent des fonctions serait de comparer l'inégalité (par exemple) si elles ont été construites par la liaison des arguments dans un ordre différent. Je crois qu'il est impossible de tester l'équivalence dans le cas général.

  • J'ai peut-être tort, mais je pense que l'égalité est de std::function objets est, malheureusement, n'est pas soluble dans le sens générique.

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

Je suis complètement en désaccord avec ceci: il n'est pas d'emplois de std::function pour effectuer la comparaison elle-même, c'est du boulot, c'est juste pour rediriger la demande à la comparaison d'objets sous-jacents, c'est tout.

Si le sous-jacent type d'objet ne définit pas de la comparaison, il sera erreur de compilation dans tous les cas, std::function n'est pas nécessaire d'en déduire l'algorithme de comparaison.

Si le sous-jacent type d'objet définit la comparaison, mais qui fonctionne mal, ou avoir un peu inhabituel sémantique, il n'est pas problème de std::function elle-même, mais c'est le problème de type sous-jacent.


Il est possible de mettre en œuvre function_comparable basé sur std::function.

Ici en est 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 y a une belle propriété - function_comparable peut être comparé à des std::function trop.

Par exemple, disons que nous avons vecteur de std::function de l', et nous voulons donner de l'utilisateur register_callback et unregister_callback fonctions. À l'aide de function_comparable est requise uniquement pour les 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

Le 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 que, avec l'aide de std::type_index il est possible de mettre en œuvre similaire à function_comparable classe, qui prend également en charge la commande(c'est à dire de moins en moins) ou même de hachage. Mais pas seulement de la commande entre les différents types, mais aussi de la commande au sein d'un même type (ce qui nécessite la prise en charge de types, comme LessThanComparable).

6voto

In silico Points 30778

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

Le commentaire est ici une partie de l'histoire de l' std::function, ce qui a été introduit avec N1402. Au cours de cette le temps pas de fonctions de conversion explicite existé, et à la sécurité "bool" l'idiome (basé sur les pointeurs-à-membres) a été un technique populaire. La seule inconvénient de cet idiome était que étant donnés deux objets f1 et f2 de type std::function l'expression

f1 == f2;

a été bien formé, juste parce que l' opérateur== pour le pointeur membre a été considéré après une seule conversion définie par l'utilisateur. Pour résoudre ce problème, une surcharge ensemble de undefined les fonctions de comparaison a été ajoutée, tels que la résolution de surcharge préférez ceux qui se retrouvent dans une liaison d'erreur. La nouvelle fonctionnalité de langue de supprimé fonctions beaucoup mieux mécanisme de diagnostic pour résoudre ce question.

Dans C++0x, l'supprimé fonctions sont considérées comme superflu avec l'introduction de la conversion explicite des opérateurs, de sorte qu'ils seront probablement supprimés pour C++0x.

Le point central de cette question est, que le remplacement de la coffre-bool idiome par une conversion explicite bool origine du "trou dans le type système" n'existe plus et donc le commentaire est faux et le le superflu des définitions de fonction devrait également être supprimée.

Comme pour expliquer pourquoi vous ne pouvez pas comparer std::function objets, c'est probablement parce qu'ils peuvent éventuellement tenir statique/globale des fonctions, des fonctions de membre du, foncteurs, etc, et pour cela, std::function "efface" quelques informations sur le type sous-jacent. Mise en œuvre d'un opérateur d'égalité ne serait probablement pas possible à cause de cela.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X