【问题标题】:Why is std::function not equality comparable?为什么 std::function 不能相等比较?
【发布时间】:2011-04-07 11:42:46
【问题描述】:

这个问题也适用于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 规范 cmets (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 都使用 function 中的安全布尔惯用语,而 C++11 使 bool 转换函数显式化。

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

为什么std::function 不能相等可比?什么是“类型系统中可能存在的漏洞”?和std::shared_ptr有什么区别?

【问题讨论】:

  • 请注意,如果 * a.target&lt; ftor_type &gt;() == * b.target&lt; ftor_type &gt;() 指向等式可比函子,您可以请求它们。虽然这有点挑剔(底层对象不会隐式转换为请求的类型),但它确实指定了正在使用的比较语义。

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


【解决方案1】:

为什么std::function 不相等?

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

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

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

std::shared_ptr有什么不同?

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

【讨论】:

  • 谢谢。我非常专注于“类型系统漏洞”cmets,以至于我没有考虑语义。我会接受这个答案,因为我认为它最清楚地回答了第一部分和第三部分,尽管 In silico's answer 对第二部分的解释最好。
【解决方案2】:

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

【讨论】:

  • +1 是一个易于理解的示例,标准和常见问题解答很棒,但它们通常有点过于抽象,以至于人们无法感受其中的原因。
  • 同意@Matthieu:这是一个很好、很简单的问题示例。
  • boost::function 的工作(如果它支持比较) - 不是推导比较算法,而只是重定向比较调用。如果底层对象没有定义比较 - 那么它只是编译错误。如果底层对象定义了错误的比较 - 这也不是 boost::function 的问题。
  • @EvgenyPanasyuk:你忘记了类型擦除。出于包括代码膨胀和性能在内的多种原因,function 可能会决定将底层对象替换为另一个功能上无法区分的对象。你的提议会限制这种能力。此外,std::function 将有多个实现,因此您在指定其行为时必须格外小心。 (即 ADL 是如何工作的?)
  • @MSalters:1. 你为什么认为我忘记了类型擦除? 2.“替换底层对象”——这怎么可能?特别是在 ::target_type 和 ::target 的情况下?你有证据吗? 3. “所以你在指定它的行为时必须格外小心”——当然,ISO 的所有内容都必须明确定义。您认为 ADL 存在哪些问题?
【解决方案3】:

为什么std::function 不相等?

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

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

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


需要注意的是,compare a 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::functions,我们想为用户提供register_callbackunregister_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

demo源码:

//             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
________________

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

【讨论】:

    【解决方案4】:

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

    这里的主要评论是 std::function 的历史,其中 与 N1402 一起推出。在那期间 时间没有显式转换函数 存在,并且“安全布尔”成语 (基于指向成员的指针)是一个 流行的技术。唯一的 这个成语的缺点是 给定两个对象 f1f2 类型 std::function,表达式

    f1 == f2;
    

    格式正确,只是因为 内置 operator== 用于指向 成员被考虑在一个单一的 用户定义的转换。为了解决这个问题, 一组未定义的重载 添加了比较功能,例如 重载决议会更喜欢 那些最终出现链接错误的人。 删除的新语言工具 功能提供了更好的 解决此问题的诊断机制 问题。

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

    这个问题的中心点是, 与更换 通过显式转换的安全布尔成语 到bool,原来的“孔在类型 系统”不再存在,并且 因此评论是错误的,并且 多余的函数定义 也应该删除。

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

    【讨论】:

    • 是的,但是这个(原始帖子,因为已修复:-P)没有解决“为什么”,只有“什么”。有关“为什么”,请参阅 Boost.Function 常见问题解答。
    • 感谢您的链接。我应该检查缺陷清单;我只是想,因为它在所有三个中都被指定为相同的,所以它是故意的,而不是缺陷。
    【解决方案5】:

    其实,你可以比较目标。它可能会起作用,这取决于您想要从比较中获得什么。

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

    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 之后有效。

    【讨论】:

    • 这并不像你认为的那样。 target&lt;T&gt; 返回一个非空的T* IFF T 匹配底层函数指针/函子类型/绑定表达式类型。您将其称为function&lt;void(void)&gt;::target&lt;function&lt;void(void)&gt;&gt;function&lt;void(void)&gt; 的底层类型是 void(void)not function&lt;void(void)&gt;。获得的两个指针(ptr1ptr2)都是始终 nullptr,而nullptr &lt; nullptr 始终是false,这是阻止第二次插入的原因。第一次插入成功是因为set 是空的,所以没有什么可比较的。
    • Live Demo 表明set 最多只能接受一个function
    【解决方案6】:

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

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        猜你喜欢
        • 2014-01-14
        • 1970-01-01
        • 2011-08-16
        • 1970-01-01
        • 2012-08-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多