Почему std :: function не сравнимо по равенству? - PullRequest
60 голосов
/ 02 сентября 2010

Этот вопрос также относится к boost::function и std::tr1::function.

std::function не сравнимо по равенству:

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

В C ++ 11 operator== иoperator!= перегрузок просто не существует.В ранней версии C ++ 11 перегрузки были объявлены как удаленные с комментарием (N3092 §20.8.14.2):

// deleted overloads close possible hole in the type system

В нем не говорится, что такое «возможная дыра в системе типов»,В TR1 и Boost перегрузки объявляются, но не определяются.Комментарии спецификации TR1 (N1836 §3.7.2.6):

Эти функции-члены должны оставаться неопределенными.

[ Примечание: открывается логическое преобразованиелазейка, посредством которой два экземпляра функции можно сравнивать с помощью == или !=.Эти неопределенные void операторы закрывают лазейку и обеспечивают ошибку времени компиляции. - конец примечания ]

Мое понимание "лазейки" заключается в том, что если у нас есть функция преобразования bool, это преобразование может использоваться в сравнениях на равенство (и в другихобстоятельства):

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

У меня сложилось впечатление, что идиома safe-bool в C ++ 03 и использование явной функции преобразования в C ++ 11 использовались, чтобы избежать этой "лазейки".Boost и TR1 оба используют идиому safe-bool в function, а C ++ 11 делает явной функцию преобразования bool.

В качестве примера класса, который имеет оба, std::shared_ptr оба имеютявная bool функция преобразования и сопоставимость по равенству.

Почему std::function не сопоставимо по равенству?Что такое «возможная дыра в системе типов»?Чем он отличается от std::shared_ptr?

Ответы [ 8 ]

37 голосов
/ 02 сентября 2010

Почему std::function не сравнимо по равенству?

std::function - это обертка для произвольных вызываемых типов, поэтому для реализации сравнения на равенствоВы должны будете требовать, чтобы все вызываемые типы были сопоставимы по равенству, что обременяет любого, кто реализует объект функции.Даже тогда вы получите узкое понятие равенства, поскольку эквивалентные функции будут сравнивать неравные, если (например) они были построены путем связывания аргументов в другом порядке.Я считаю, что невозможно проверить на эквивалентность в общем случае.

Что такое «возможная дыра в системе типов?»

Я думаю, это означает, что это прощеудалить операторы и точно знать, что их использование никогда не даст действительного кода, чем доказать, что в некоторых ранее неизвестных угловых случаях невозможно нежелательное неявное преобразование.

Чем оно отличается отstd::shared_ptr?

std::shared_ptr имеет четко определенную семантику равенства;два указателя равны тогда и только тогда, когда они либо пустые, либо оба непустые и указывают на один и тот же объект.

26 голосов
/ 02 сентября 2010

Это подробно обсуждается в FAQ по Boost.Function .: -)

21 голосов
/ 02 сентября 2010

Я могу ошибаться, но я думаю, что равенство std::function объектов, к сожалению, невозможно решить в общем смысле. Например:

#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 и f2? Что если я добавлю произвольное количество функциональных объектов, которые просто оборачивают друг друга различными способами, что в итоге сводится к вызову f ... все еще равному?

13 голосов
/ 28 октября 2012

Почему std :: function не сравнимо по равенству?

Я думаю, что основная причина в том, что если это так, то его нельзя использовать с сопоставимыми по неравенству типами, даже если сравнение по равенству никогда не выполняется.

т.е. код, который выполняет сравнение, должен быть создан раньше - в тот момент, когда вызываемый объект сохраняется в std :: function, например, в одном из конструкторов или операторов присваивания.

Такое ограничение значительно сузило бы область применения и, очевидно, неприемлемо для "обертки полиморфной функции общего назначения" .


Неверно отметить, что можно сравнить boost :: function с вызываемым объектом (но не с другим boost :: function)

Обертки объекта функции можно сравнить через == или! = С любым объектом функции, который может быть сохранен в оболочке.

Это возможно, потому что функция, которая выполняет такое сравнение, мгновенно вызывается в точке сравнения на основе известного типа операнда.

Кроме того, std :: function имеет target шаблонную функцию , которая может использоваться для аналогичного сравнения. Фактически операторы сравнения boost :: function реализованы в терминах target функция-члена .

Итак, нет технических барьеров, которые блокируют реализацию function_comparable .


Среди ответов распространен шаблон "невозможно вообще":

  • Даже тогда вы получили бы узкое понятие равенства, поскольку эквивалентные функции сравнивали бы неравно, если бы (например) они были построены путем связывания аргументов в другом порядке. Я считаю, что невозможно проверить на эквивалентность в общем случае.

  • Возможно, я ошибаюсь, но я думаю, что равенство объектов std :: function, к сожалению, не решаемо в общем смысле.

  • Потому что эквивалентность машин Тьюринга неразрешима. Учитывая два различных функциональных объекта, вы не можете определить, вычисляют ли они одну и ту же функцию или нет. [Этот ответ был удален]

Я полностью не согласен с этим: задача std :: function не выполнять само сравнение, а просто перенаправить запрос на сравнение с базовыми объектами - и все.

Если базовый тип объекта не определяет сравнение - это будет ошибка компиляции в любом случае, std :: function не требуется для вывода алгоритма сравнения.

Если базовый тип объекта определяет сравнение, но работает неправильно или имеет некоторую необычную семантику - это не проблема самого std :: function, но это проблема базового типа .


Возможно реализовать function_comparable на основе std :: function.

Вот подтверждение концепции:

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

Есть хорошее свойство - function_comparable можно сравнить с std :: function .

Например, допустим, у нас есть вектор функций std :: function , и мы хотим предоставить пользователю register_callback и unregister_callback функций. Использование function_comparable требуется только для unregister_callback параметр:

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

Демонстрация в реальном времени на Ideone

Исходный код демо:

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

Вывод:

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

P.S. Кажется, что с помощью std :: type_index можно реализовать аналогичный function_comparable класс, который также поддерживает упорядочение (т.е. меньше) или даже хеширование. Но не только упорядочение между разными типами, но и упорядочение внутри одного и того же типа (для этого требуется поддержка типов, таких как LessThanComparable).

6 голосов
/ 02 сентября 2010

Согласно http://www.open -std.org / jtc1 / sc22 / wg21 / docs / lwg-active.html # 1240 :

Ведущий комментарий здесь является частью история std::function, которая был представлен с N1402. Во время этого время нет явных функций преобразования существовала и идиома "безопасный бул" (на основе указателей на член) был популярная техника. Единственный Недостатком этой идиомы было то, что даны два объекта типа f1 и f2 std :: function выражение

f1 == f2;

был хорошо сформирован, просто потому, что встроенный оператор == для указателя на Член был рассмотрен после одного определяемое пользователем преобразование. Чтобы исправить это, набор перегрузки неопределен добавлены функции сравнения, такие это разрешение перегрузки предпочтет те, которые заканчиваются ошибкой связи. Новый языковой объект удален функции обеспечили намного лучше диагностический механизм, чтобы исправить это выпуск.

В C ++ 0x удаленные функции считаются излишними с введением явных операторов преобразования, поэтому они, вероятно, будут удалены для C ++ 0x.

Суть этого вопроса в том, что с заменой идиома Safe-Bool путем явного преобразования закинуть оригинальную "дыру в типе система "больше не существует и поэтому комментарий неправильный и определения лишних функций следует также удалить.

Что касается того, почему вы не можете сравнивать std::function объекты, это, вероятно, потому, что они могут содержать глобальные / статические функции, функции-члены, функторы и т. Д., И для этого std::function "стирает" некоторую информацию о лежащем в основе тип. Реализация оператора равенства, вероятно, была бы невозможна из-за этого.

4 голосов
/ 24 сентября 2015

На самом деле, вы можете сравнить цели. Это может работать в зависимости от того, что вы хотите от сравнения.

Вот код с неравенством, но вы можете увидеть, как он работает:

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

Упс, он действителен только с C ++ 11.

0 голосов
/ 24 сентября 2015

Как насчет того, чтобы попробовать что-то вроде следующего, это хорошо работает для тестирования шаблонов.

if (std::is_same<T1, T2>::value)
{
    ...
}
0 голосов
/ 27 июня 2011

самое меньшее, что можно было бы сделать, это если std :: function сохраняет адрес функции, используемой для привязки к строке, и вместо этого использует сравнение строк.

...