为什么std :: function不平等?

这个问题也适用于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!= overload是不存在的。 在早期的C ++ 11草案中,重载被声明为删除(N3092§20.8.14.2):

 // deleted overloads close possible hole in the type system 

它没有说出“types系统中可能存在的漏洞”是什么。 在TR1和Boost中,重载是声明的,但没有定义。 TR1规范注释(N1836§3.7.2.6):

这些成员函数应该是未定义的。

[ 注意:类似布尔的转换会打开一个漏洞,通过==!=来比较两个函数实例。 这些未定义的void操作符closures了漏洞,并确保编译时错误。 – 注意 ]

我对“漏洞”的理解是,如果我们有一个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中的安全布尔成语和C ++ 11中显式转换函数的使用被用来避免这个“漏洞”。 Boost和TR1都在function使用safe-bool成语,C ++ 11使bool转换函数显式化。

作为具有两者的类的一个例子, std::shared_ptr都有一个显式的bool转换函数,并且是相等的。

为什么std::function不平等? 什么是types系统中可能存在的漏洞? 它与std::shared_ptr有何不同?

为什么std::function不平等?

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

什么是types系统中可能存在的漏洞?

我想这意味着删除操作符会更容易,并且确定使用它们永远不会提供有效的代码,而不是certificate在以前未发现的边界情况下不会发生不必要的隐式转换。

它与std::shared_ptr有何不同?

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

这是在Boost.Function常见问题中进行了深入的讨论。 🙂

我可能是错的,但我认为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 …仍然相等?

为什么std :: function不平等?

我认为主要的原因是,如果是这样的话,即使没有进行平等比较,也不能用于非平等可比types。

也就是说,执行比较的代码应该提前实例化 – 当可调用对象存储到std :: function时,例如在构造函数或赋值运算符中。

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


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

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

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

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

所以,没有任何技术障碍阻碍了函数的可执行性的实现。


在答案中有普遍的“一般不可能”模式:

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

  • 我可能是错的,但我认为std :: function对象的平等不幸是不能解决的一般意义上。

  • 因为图灵机的等价性是不可判定的。 给定两个不同的函数对象,你不可能确定它们是否计算相同的函数。 [这个答案被删除了]

我完全不同意这个:std :: function本身并不是执行比较的工作,它只是请求redirect到与底层对象的比较 – 就这些了。

如果底层对象types没有定义比较 – 无论如何都是编译错误,不需要使用std :: function来推断比较algorithm。

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


基于std :: function可以实现function_comparable。

这是概念validation:

 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 ::函数

例如,假设我们有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); 

现场演示在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类,它也支持sorting(即更less)甚至散列。 但不仅仅是在不同types之间进行sorting,还要在同一types中进行sorting(这要求types的支持,比如LessThanComparable)。

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

这里的主要评论是在N1402中引入的std::function的历史的一部分。 在此期间,没有明确的转换function,“安全 – 傻瓜”成语(基于指向成员)是一种stream行的技术。 这个习惯用法的唯一缺点是给定两个对象f1和f2的typesstd :: functionexpression式

f1 == f2;

是格式良好的,只是因为指向成员的内置运算符==在单个用户定义的转换之后被考虑。 为了解决这个问题,增加了一个未定义的比较函数的重载集合,这样重载parsing会更喜欢那些以链接错误结束的。 新function的语言function提供了一个更好的诊断机制来解决这个问题。

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

这个问题的中心点在于,通过将显式转换为布尔代替安全布尔方式,原来的“types系统中的空洞”不再存在,因此评论是错误的,并且应该删除多余的函数定义以及。

至于为什么你不能比较std::function对象,这可能是因为它们可能包含全局/静态函数,成员函数,仿函数等等,并且std::function “擦除”一些关于底层types的信息。 实施一个平等运营商可能是不可行的,因为这一点。

其实,你可以比较目标。 这可能取决于你想要比较的东西。

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

 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开始才有效。

如何尝试类似以下内容,这对testing模板很有效。

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

可以做的最less的是如果std :: function保存用于绑定到一个string的函数的地址,而不是使用string比较。