Why is std::function
not equality comparable?
I think main reason is that if it were, then it couldn't be used with non equality comparable types, even if equality comparison is never performed.
I.e. code that performs comparison should be instantiated early - at the time when a callable object is stored into std::function
, for instance in one of the constructors or assignment operators.
Such a limitation would greatly narrow the scope of application, and obviously not be acceptable for a "general-purpose polymorphic function wrapper".
It is important to note that it is possible to compare a boost::function
with a callable object (but not with another boost::function
)
Function object wrappers can be compared via ==
or !=
against any function object that can be stored within the wrapper.
This is possible, because function that performs such comparison is instantiated at point of comparison, based on known operand types.
Moreover, std::function
has a target
template member function, which can be used to perform similar comparison. In fact boost::function
's comparison operators are implemented in terms of target
member function.
So, there are no technical barriers which block implementation of function_comparable
.
Among answers there is common "impossible in general" pattern:
Even then, you'd get a narrow concept of equality, as equivalent functions would compare unequal if (for example) they were constructed by binding arguments in a different order. I believe it's impossible to test for equivalence in the general case.
I may be wrong, but I think that equality is of std::function
objects is unfortunately not solvable in the generic sense.
Because the equivalence of Turing machines is undecidable. Given two different function objects, you cannot possibly determine if they compute the same function or not. [That answer was deleted]
I completely disagree with this: it is not the job of std::function
to perform comparison itself; its job is just to redirect request to comparison to underlying objects - that's all.
If underlying object type does not define comparison - it will be a compilation error; in any case, std::function
is not required to deduce a comparison algorithm.
If the underlying object type defines comparison, but which works wrongly, or has some unusual semantic - it is not the problem of std::function
itself either, but it is problem of the underlying type.
It is possible to implement function_comparable
based on std::function
.
Here is a proof-of-concept:
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);
}
};
There is a nice property - function_comparable
can be compared against std::function
too.
For instance, let's say we have vector of std::function
s, and we want to give users register_callback
and unregister_callback
functions. Use of function_comparable
is required only for unregister_callback
parameter:
void register_callback(std::function<function_signature> callback);
void unregister_callback(function_comparable<function_signature> callback);
Live demo at Ideone
Source code of 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();
}
Output is:
func1
func2, data=1
func2, data=2
func3
________________
func1
func2, data=1
func3
________________
func2, data=1
func3
________________
P.S. It seems that with help of std::type_index
, it is possible to implement something similar to function_comparable
class, which also supports ordering (i.e. std::less
) or even hashing. Not only ordering between different types, but also ordering within same type (this requires support from types, like LessThanComparable
).
* a.target< ftor_type >() == * b.target< ftor_type >()
if they point to equality-comparable functors. Although this is a bit finicky (the underlying object will not be implicitly converted to the requested type), it does specify exactly whose comparison semantics are being used. – Adriell