views:

269

answers:

5

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
}

The operator== and operator!= overloads are declared as deleted in C++0x 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++0x was used to avoid this "loophole." Boost and TR1 both use the safe-bool idiom in function and C++0x 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?

+2  A: 

Why is std::function not equality comparable?

Because the equivalence of turing machines is undecidable. Given two different functionobjects, you cannot possibly determine if they compute the same function or not.

Due to the same reason, there is no such thing as a set of functions in Haskell, by the way.

FredOverflow
+2  A: 

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

The leading comment here is part of the history of std::function, which was introduced with N1402. During that time no explicit conversion functions existed, and the "safe-bool" idiom (based on pointers-to-member) was a popular technique. The only disadvantage of this idiom was that given two objects f1 and f2 of type std::function the expression

f1 == f2;

was well-formed, just because the built-in operator== for pointer to member was considered after a single user-defined conversion. To fix this, an overload set of undefined comparison functions was added, such that overload resolution would prefer those ending up in a linkage error. The new language facility of deleted functions provided a much better diagnostic mechanism to fix this issue.

In C++0x, the deleted functions are considered superfluous with the introduction of explicit conversion operators, so they will probably be removed for C++0x.

The central point of this issue is, that with the replacement of the safe-bool idiom by explicit conversion to bool the original "hole in the type system" does no longer exist and therefore the comment is wrong and the superfluous function definitions should be removed as well.

As for why you can't compare std::function objects, it's probably because they can possibly hold global/static functions, member functions, functors, etc, and to do that std::function "erases" some information about the underlying type. Implementing an equality operator would probably not be feasible because of that.

In silico
Right, but this (the original post, since fixed :-P) doesn't address the "why", only the "what". See the Boost.Function FAQ for the "why".
Chris Jester-Young
Thanks for the link. I should have checked the defects list; I just figured since it was specified the same in all three that it was intentional and not a defect.
James McNellis
+8  A: 

This is thoroughly discussed in the Boost.Function FAQ. :-)

Chris Jester-Young
I should have figured there would be a Boost FAQ that discussed this. Thanks.
James McNellis
+3  A: 

I may be wrong, but I think that equality is of std::function objects is unfortunately not solvable in the generic sense. For example:

#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();
}

are f1 and f2 equal? What if I add an arbitrary number of function objects which simply wrap each other in various ways which eventually boils down to a call to f... still equal?

Evan Teran
+1 for an easy to grok example, standards and FAQ are great, but they are often a bit too abstract for people to just feel the reason.
Matthieu M.
Agreed with @Matthieu: that's a good, simple example of a problem I hadn't thought of.
James McNellis
+1  A: 

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 invalid, or both valid and both point to the same object.

Mike Seymour
Thanks. I was so focused on the "type system loophole" comments that I didn't think about the semantics. I'll accept this answer because in my opinion it most clearly answers the first and third parts, though the second part is explained best by [In silico's answer](http://stackoverflow.com/questions/3629835/why-is-stdfunction-not-equality-comparable/3629961#3629961).
James McNellis