Why is std::function not equality comparable?

James McNellis picture James McNellis · Sep 2, 2010 · Viewed 15.8k times · Source

This question also applies to boost::function and std::tr1::function.

std::function is not equality comparable:

#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
}

In C++11, the operator== and operator!= overloads just don't exist. In an early C++11 draft, the overloads were declared as deleted with the comment (N3092 §20.8.14.2):

// deleted overloads close possible hole in the type system

It does not say what the "possible hole in the type system" is. In TR1 and Boost, the overloads are declared but not defined. The TR1 specification comments (N1836 §3.7.2.6):

These member functions shall be left undefined.

[Note: the boolean-like conversion opens a loophole whereby two function instances can be compared via == or !=. These undefined void operators close the loophole and ensure a compile-time error. —end note]

My understanding of the "loophole" is that if we have a bool conversion function, that conversion may be used in equality comparisons (and in other circumstances):

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!
}

I was under the impression that the safe-bool idiom in C++03 and the use of an explicit conversion function in C++11 was used to avoid this "loophole." Boost and TR1 both use the safe-bool idiom in function and C++11 makes the bool conversion function explicit.

As an example of a class that has both, std::shared_ptr both has an explicit bool conversion function and is equality comparable.

Why is std::function not equality comparable? What is the "possible hole in the type system?" How is it different from std::shared_ptr?

Answer

Mike Seymour picture Mike Seymour · Sep 2, 2010

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.