为什么std :: function不相等?

时间:2010-09-02 18:01:22

标签: c++ function boost c++11 tr1

此问题也适用于boost::functionstd::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运算符关闭了漏洞并确保编译时错误。 -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!
}

我的印象是C ++ 03中的safe-bool成语和C ++ 11中使用显式转换函数被用来避免这个“漏洞”。 Boost和TR1都使用function中的safe-bool习语,C ++ 11使bool转换函数显式化。

作为同时具有两者的类的示例,std::shared_ptr两者都具有明确的bool转换函数并且具有可比性。

为什么std::function不具有可比性?什么是“类型系统中可能存在的漏洞?”它与std::shared_ptr有什么不同?

8 个答案:

答案 0 :(得分:37)

  

为什么std::function不具有可比性?

std::function是任意可调用类型的包装器,因此为了实现相等性比较,您必须要求所有可调用类型都是相等比较的,这对任何实现函数对象的人来说都是一种负担。 。即使这样,你也会得到一个狭隘的相等概念,因为等价函数会比较不等,如果(例如)它们是通过以不同顺序绑定参数来构造的。我认为在一般情况下测试等效性是不可能的。

  

什么是“类型系统中可能出现的漏洞?”

我猜这意味着删除运算符会更容易,并且肯定地知道使用它们永远不会提供有效的代码,而不是证明在以前未被发现的一些极端情况下不可能发生不必要的隐式转换。

  

它与std::shared_ptr有什么不同?

std::shared_ptr具有明确定义的等式语义;两个指针是相等的,当且仅当它们都是空的,或者都是非空的并指向同一个对象。

答案 1 :(得分:26)

Boost.Function FAQ对此进行了详尽的讨论。 : - )

答案 2 :(得分:21)

我可能错了,但我认为遗憾的是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();
}

f1f2相等吗?如果我添加任意数量的函数对象,这些函数对象只是以各种方式相互包裹,最终归结为对f的调用...仍然相等?

答案 3 :(得分:13)

  

为什么std :: function不能相等?

我认为主要原因是,如果它是,那么它就不能用于非等同可比类​​型,即使从未进行过相等比较。

即。执行比较的代码应该尽早实例化 - 在可调用对象存储到std :: function时,例如在构造函数或赋值运算符中。

这种限制会大大缩小应用范围,显然不适用于“通用多态函数包装器”


值得注意的是,compare boost::function可以使用可调用对象(但不能使用另一个boost :: function)

  

函数对象包装器可以通过==或!=与任何可以存储在包装器中的函数对象进行比较。

这是可能的,因为根据已知操作数类型,在比较点即时执行执行此类比较的函数。

此外, std :: function target template member function,可用于执行类似的比较。事实上,boost :: function的比较运算符是implemented in terms of target member function

因此,没有技术障碍阻碍 function_comparable 的实施。


在答案中,常见的是“一般不可能”的模式:

  •   

    即使这样,你也会得到一个狭隘的相等概念,因为等价函数会比较不等,如果(例如)它们是通过以不同顺序绑定参数来构造的。我认为在一般情况下测试等效性是不可能的。

  •   

    我可能错了,但我认为std :: function对象的相等性很遗憾在一般意义上是不可解决的。

  •   

    因为图灵机的等价性是不可判定的。给定两个不同的函数对象,您无法确定它们是否计算相同的函数。 [该答案已删除]

我完全不同意这一点:执行比较本身不是std :: function的工作,它的工作只是重定向请求与底层对象的比较 - 这就是全部。

如果底层对象类型没有定义比较 - 在​​任何情况下都是编译错误,std :: function不需要推导比较算法。

如果底层对象类型定义了比较,但是它工作错误,或者有一些不寻常的语义 - 它也不是std :: function本身的问题,但它是底层类型的问题。


可以基于std :: function实现 function_comparable

这是概念验证:

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 进行比较。

例如,假设我们有vector of std::function's,我们希望为用户提供 register_callback unregister_callback 功能。只有 unregister_callback 参数才需要使用 function_comparable

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

Live demo at 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)。

答案 4 :(得分:6)

根据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。

  

这个问题的核心是,   随着更换   通过显式转换的安全布尔习语   为了吹嘘原来的“洞型”   系统“不再存在   因此评论是错误的   多余的功能定义   也应该删除。

至于为什么你不能比较std::function个对象,可能是因为它们可能包含全局/静态函数,成员函数,仿函数等,并且这样做std::function“擦除”一些有关基础类型的信息。由于这个原因,实现一个相等运算符可能是不可行的。

答案 5 :(得分:4)

实际上,您可以比较目标。它可能取决于你想要比较什么。

这里的代码有不等式,但你可以看到它是如何工作的:

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,它仅在C ++ 11之后才有效。

答案 6 :(得分:0)

如何尝试以下内容,这适用于测试模板。

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

答案 7 :(得分:-1)

至少可以做的是,如果std :: function保存用于绑定到字符串的函数的地址,而是使用字符串比较。