62

这个问题也适用于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==andoperator!=重载根本不存在。在早期的 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!
}

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

作为具有两者的类的示例,std::shared_ptr两者都具有显式bool转换功能并且是相等可比的。

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

4

7 回答 7

37

为什么std::function平等没有可比性?

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

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

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

它与 有何不同std::shared_ptr

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

于 2010-09-02T18:45:05.107 回答
23

我可能错了,但我认为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... 仍然相等的调用?

于 2010-09-02T18:21:14.063 回答
13

为什么std::function平等没有可比性?

我认为主要原因是如果是这样,那么即使从未执行相等比较,它也不能与非相等比较类型一起使用。

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

这样的限制会大大缩小应用范围,对于“通用多态函数包装器”显然是不可接受的。


需要注意的是,可以将 aboost::function与可调用对象进行比较(但不能与 another进行比较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

例如,假设我们有s 的向量std::function,我们想给用户register_callbackunregister_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
________________

PS 似乎在 的帮助下std::type_index,可以实现类似于function_comparable类的东西,它也支持排序(即std::less)甚至散列。不仅在不同类型之间排序,而且在同一类型内排序(这需要类型的支持,例如LessThanComparable)。

于 2012-10-27T23:23:50.297 回答
6

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

这里的主要评论是std::functionN1402 引入的历史的一部分。在那段时间里,不存在显式转换函数,并且“安全布尔”习语(基于指向成员的指针)是一种流行的技术。这个习语的唯一缺点是给定两个对象f1f2类型 std::function,表达式

f1 == f2;

operator==格式正确,只是因为在单个用户定义的转换之后考虑了指向成员的内置for 指针。为了解决这个问题,添加了一组未定义的比较函数的重载,这样重载决议会更喜欢那些最终导致链接错误的函数。删除函数的新语言工具提供了一个更好的诊断机制来解决这个问题。

在 C++11 中,由于引入了显式转换运算符,删除的函数被认为是多余的,因此它们可能会在 C++11 中被删除。

这个问题的中心点是,通过显式转换为替换安全布尔成语bool,原来的“类型系统中的漏洞”不再存在,因此注释错误,应该删除多余的函数定义也是。

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

于 2010-09-02T18:17:24.510 回答
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 开始有效。

于 2015-09-24T13:47:39.140 回答
-1

试试下面的方法怎么样,这对于测试模板很有效。

if (std::is_same<T1, T2>::value)
{
    ...
}
于 2015-09-24T20:19:23.933 回答
-2

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

于 2011-06-27T04:57:51.403 回答