Comparing Std::Functions for Equality

Comparing std::functions for equality?

operator== for std::function compares a std::function with a null pointer, as far as I can tell the standard does not provide any details as to why.

Although, this boost FAQ entry, Why can't I compare boost::function objects with operator== or operator!=? provides a rationale and as far as I can tell should be applicable to std::function as well. Quoting the FAQ:

Comparison between boost::function objects cannot be implemented "well", and therefore will not be implemented. [...]

it then outlines requested solutions similar to Preet's and goes on to say:

The problem occurs when the type of the function objects stored by both f and g doesn't have an operator==[...]

and explains why this has to has to be dealt with in either the assignment operator or constructor and then goes on to say:

All of these problems translate into failures in the boost::function constructors or assignment operator, even if the user never invokes operator==. We can't do that to users.

Update

Found a standards rationale in Accessing the target of a tr1::function object, which is pretty old but is consistent with the boost FAQ and says:

operator== is unimplementable for tr1::function within the C++ language, because we do not have a reliable way to detect if a given type T is Equality Comparable without user assistance.

Why is std::function not equality comparable?

Why is std::function not equality comparable?

std::function is a wrapper for arbitrary callable types, so in order to implement equality comparison at all, you'd have to require that all callable types be equality-comparible, placing a burden on anyone implementing a function object. 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.

What is the "possible hole in the type system?"

I would guess this means it's easier to delete the operators, and know for certain that using them will never give valid code, than to prove there's no possibility of unwanted implicit conversions occurring in some previously undiscovered corner case.

How is it different from std::shared_ptr?

std::shared_ptr has well-defined equality semantics; two pointers are equal if and only if they are either both empty, or both non-empty and pointing to the same object.

std::function and comparison of std::function

A std::function is not a function pointer. You cannot compare std::functions like you compare function pointers. If you comparing two std::functions, and expecting them to compare equal to each if they've type-erased the same function, that's not going to work. A std::function can only be compared to a nullptr. This is the only defined equality operator for std::functions.

Your goal is to identify a std::function and remove it from a vector of installed callback functions. The usual way this kind of functionality is done is by assigning a separate label to a wrapped function, often a std::string that gives a unique name to each function, and then you find the function by name. It doesn't have to be a std::string, an enum will work just as well. The bottom line is that you will have to establish how your std::functions are identified "out of band".

Check if two std::function are Equal

I dont think that they both can be compared. Here is an example to explain some points on std::function comparison

Can two functors be compared for equality?

A raw function is eventually a pointer. You can dig it out of std::function
with std::function::target and then it's simply a comparison of void*.

How to compare two vectors for equality?

You can construct a std::unordered_set from each vector, then compare those, as shown in the code snippet below:

#include <iostream>  
#include <vector>
#include <unordered_set>

using namespace std;

int main()
{
std::vector<int> nums = { 1, 2, 3, 4, 5 };
std::vector<int> nums2 = { 5, 4, 3, 2, 1 };
std::vector<int> nums3 = { 5, 4, 9, 2, 1 };

std::unordered_set<int> s1(nums.begin(), nums.end());
std::unordered_set<int> s2(nums2.begin(), nums2.end());
std::unordered_set<int> s3(nums3.begin(), nums3.end());

if (s1 == s2) {
std::cout << "1 and 2 are equal";
}
else {
std::cout << "1 and 2 are different";
}
std::cout << std::endl;

if (s1 == s3) {
std::cout << "1 and 3 are equal";
}
else {
std::cout << "1 and 3 are different";
}
std::cout << std::endl;

return 0;
}

However, there are some points to bear in mind:

  1. For vectors of custom type objects, you would need to provide an operator== for that type (but that would have to be done anyway, or how can you say if the two vectors have the same contents).
  2. Vectors that containing duplicate entries will create sets that have those duplicates removed: Thus, {1, 2, 2, 3} will show equal to {1, 2, 3}.
  3. You will also need to provide a std:hash for your custom type. For a trivial class, bob, which just wraps an integer, that hash, and the required operator==, could be defined as shown below; you can then replace the <int> specializations in the above example with <bob> and it will work. (This cppreference article explains more about the hash.)
class bob {
public:
int data;
bob(int arg) : data{ arg } { }
};
bool operator==(const bob& lhs, const bob& rhs)
{
return lhs.data == rhs.data;
}
template<> struct std::hash<bob> {
std::size_t operator()(bob const& b) const noexcept {
return static_cast<size_t>(b.data);
}
};

Comparing std::function

Based on information from Stack Overflow at the following link, it IS possible but only if you wrap the std::function object in its own class.

std::vector of std::function

By using a wrapper class, you can test whether two wrapped std::function pointers are equal, but that doesn't tell you anything about what the std::function wraps. So, changing your design is probably a better approach.

edit: I came back to show the way I've solved a very similar problem.

0) Typedefs for conciseness.

    using std::placeholders;
typedef std::function < void ( int, float ) > some_func;
typedef std::pair < intptr_t, intptr_t > method_hash;
  1. Write your collection of std::function objects by binding pointers to methods or functions. Where you are doing this for static functions, omit some_object_ptr.

    some_func some_method ( std::bind ( some_method_ptr, 
    some_object_ptr, _1, _2 )
  2. Use std::reinterpret_cast < intptr_t > to create a unique hash for your function, and use it with std::pair to do so for methods.

     method_hash pairID ( reinterpret_cast < intptr_t > ( some_object_ptr ), 
    reinterpret_cast < intptr_t > ( some_method_ptr ) );
  3. Now your pairID can be stored in a vector or other container/array. Just be sure to maintain that the indices are aligned so that a hash always corresponds to the correct std::function object, and you can then use find ( ) to get an iterator to its position and distance ( ) to convert the iterator to the required index.

Note that this will have to be done every time your container is generated. Since it's based on pointers, the hashes will change over different runs of your program.

How can I compare std::function objects?

As shown here, template member function target accepts type that is compared with a stored object type. In you case it is a pointer to a function. You have to change

*iter->target<void()>() == myFunc2

to

*iter->target<void(*)()>() == myFunc2

Note that this will only only you to find a plain C function, not an arbitrary callable object (like a lambda function). I think you should consider using plain pointers instead of std::function here.



Related Topics



Leave a reply



Submit