为什么std :: function不相等?

Jam*_*lis 61 c++ boost function tr1 c++11

这个问题也适用于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
}
Run Code Online (Sandbox Code Playgroud)

在C++ 11中,operator==operator!=重载并不存在.在早期的C++ 11草案中,使用注释(N3092§20.8.14.2)将重载声明为已删除:

// deleted overloads close possible hole in the type system
Run Code Online (Sandbox Code Playgroud)

它没有说明"类型系统中可能存在的漏洞"是什么.在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!
}
Run Code Online (Sandbox Code Playgroud)

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

作为具有两者的类的示例,std::shared_ptr两者都具有显式bool转换函数并且具有可比性.

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

Mik*_*our 37

为什么std::function不平等可比?

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

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

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

它有什么不同std::shared_ptr

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


Eva*_*ran 22

我可能错了,但我认为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();
}
Run Code Online (Sandbox Code Playgroud)

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

  • 一个易于理解的例子,标准和常见问题解答的+1很棒,但它们往往有点过于抽象,人们只能感受到原因. (5认同)

Evg*_*yuk 13

为什么std :: function不相等?

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

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

这种限制将大大缩小应用范围,并且显然对于"通用多态函数包装器"是不可接受的.


值得注意的是,可以将boost :: function与可调用对象进行比较(但不能与另一个boost :: function进行比较)

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

这是可能的,因为执行这种比较的函数是基于已知操作数类型在比较点即时发送的.

而且,std :: function具有目标模板成员函数,可用于执行类似的比较.事实上,boost :: function的比较运算符是根据目标成员函数实现的.

因此,没有阻碍function_comparable实现的技术障碍.


答案中有一个共同的"一般不可能"的模式:

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

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

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

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

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

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


它可以实现function_comparable基础上的std ::功能.

这是概念验证:

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);
    }
};
Run Code Online (Sandbox Code Playgroud)

有一些不错的属性 - function_comparable也可以与std :: function进行比较.

例如,假设我们有std :: function的向量,我们想为用户提供register_callbackunregister_callback函数.仅对unregister_callback参数使用function_comparable:

void register_callback(std::function<function_signature> callback);
void unregister_callback(function_comparable<function_signature> callback);
Run Code Online (Sandbox Code Playgroud)

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();
}
Run Code Online (Sandbox Code Playgroud)

输出是:

func1
func2, data=1
func2, data=2
func3
________________
func1
func2, data=1
func3
________________
func2, data=1
func3
________________
Run Code Online (Sandbox Code Playgroud)

PS似乎在std :: type_index的帮助下,可以实现类似于function_comparable类,它也支持排序(即更少)甚至散列.但不仅在不同类型之间进行排序,而且在相同类型中排序(这需要类型的支持,如LessThanComparable).


In *_*ico 6

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

这里的主要评论是std::functionN1402引入的历史的一部分.在此期间,没有明确的转换函数,"safe-bool"成语(基于指向成员的指针)是一种流行的技术.这个成语的唯一缺点是给定了两个对象f1和f2类型为std :: function的表达式

f1

格式正确,只是因为在单个用户定义的转换后,内置运算符==指向成员的指针.为了解决这个问题,添加了一组未定义的比较函数的重载,这样重载解析就更喜欢最终出现在链接错误中的那些.已删除函数的新语言工具提供了更好的诊断机制来解决此问题.

在C++ 0x中,删除的函数在引入显式转换运算符时被认为是多余的,因此它们可能会被移除以用于C++ 0x.

这个问题的中心点是,通过显式转换为bool来替换safe-bool习语,原始的"类型系统中的漏洞"不再存在,因此注释是错误的,应该删除多余的函数定义同样.

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