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

Этот вопрос также относится к 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
}

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

// deleted overloads close possible hole in the type system

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

Эти функции-члены должны быть оставлены undefined.

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

Мое понимание "лазейки" состоит в том, что если у нас есть функция преобразования 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 в С++ 03 и использование явной функции преобразования в С++ 11 использовались, чтобы избежать этой "лазейки". Boost и TR1 используют идиому safe-bool в function, а С++ 11 делает функцию преобразования bool явной.

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

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

Ответ 1

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

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

Что такое "возможное отверстие в системе типов?"

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

Как он отличается от std::shared_ptr?

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

Ответ 3

Возможно, я ошибаюсь, но я думаю, что равенство объектов 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... все еще равным?

Ответ 4

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

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

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

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


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

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

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

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

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


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

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

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

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

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

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

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


Можно реализовать 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);

Живая демонстрация в Идеальная

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

//             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).

Ответ 5

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

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

f1 == f2;

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

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

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

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

Ответ 6

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

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

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, он действителен только с С++ 11.

Ответ 7

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

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

Ответ 8

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