23

I came up with the idea to define a generic comparison operator which would work with any type, for the fun of it.

#include <cstring>
#include <iostream>

class A
{
    public:
        A(int id) : id(id) {}

    private:
        int id;
};

template <class T>
inline bool operator==(const T& a, const T& b)
{
    return memcmp(&a, &b, sizeof(a)) == 0; // implementation is unimportant (can fail because of padding)
}

int main()
{
    std::cout << (A(10) == A(10)) << std::endl; // 1
    std::cout << (A(10) == A(15)) << std::endl; // 0
}

I think this could be useful to get around the lack of default comparison operator in c++.

Is this a terrible idea? I wonder if doing this could break anything in some circumstances?

Manfred Radlwimmer
  • 13,257
  • 13
  • 53
  • 62
MaxV37
  • 530
  • 4
  • 15
  • 21
    *"implementation is unimportant"* No, it's not. It turns compilation errors into runtime errors. That's bad. – Baum mit Augen Aug 09 '17 at 11:38
  • I meant that it's not the point of the question. – MaxV37 Aug 09 '17 at 11:39
  • 18
    This is as bad as a `reinterpret_cast` to silence compiler errors. Types that do not define a comparison operator do it for various reasons. You substitute correct, well defined code, for some seldom useful syntactic sugar. – StoryTeller - Unslander Monica Aug 09 '17 at 11:39
  • 1
    @Bathsheba Cue someone overloading the unary `operator&` for their type. – Baum mit Augen Aug 09 '17 at 11:40
  • 6
    If you have a reference or pointer in your object, you will be comparing the adresses instead of the values – Serge Kireev Aug 09 '17 at 11:43
  • 2
    And a class with a mutable member is also going to be suspect. Since `const` methods can change the mutable member, but should not make equal objects unequal, it follows that equality can't depend on those `mutable` members. – MSalters Aug 09 '17 at 14:55
  • Your implementation of `operator==` invokes undefined behaviour if `sizeof(b) < sizeof(a)` though this could be remedied if you use `std::min(sizeof(a), sizeof(b))` instead. – David Foerster Aug 09 '17 at 17:25
  • surely you meant to write `template ` not `template `, right? – cat Aug 09 '17 at 20:00
  • @cat `template ` and `template ` are exactly the same. – MaxV37 Aug 09 '17 at 20:49
  • @MaxV37 Oh, I thought `typename` names any type but `class` is for reference types only, that is actual `class`es – cat Aug 09 '17 at 20:59
  • @cat `typename` and `class` are indeed [equivalent](https://stackoverflow.com/questions/2023977/difference-of-keywords-typename-and-class-in-templates). That reference type thing is a myth spread by Java coders with a poor understanding of C++ types. – Silvio Mayolo Aug 09 '17 at 22:28
  • if I do `string a = "hello world blah blah blah this is a very long string"; string b = a;` then your operator will tell me that `a == b` is false. – user253751 Aug 10 '17 at 05:49
  • So on top of all headache from C++ you also want the itching of PHP? This is pure masochism. – klutt Aug 15 '17 at 19:26

4 Answers4

52

Doing this is indeed a terrible idea.

If some type does not define an equality operator, it is most likely because you cannot reasonably compare two objects of that type for equality.

Even for the case where the missing equality operator was an oversight by the implementer, any "catch-all" implementation you would come up with is highly unlikely to do something sensible.

So to conclude: Don't do this! Compile time errors are better than runtime errors; instead of prematurely adding a most certainly broken "solution" hiding the actual problem, add actual solutions as the compile time errors occur.


For starters, the solution you came up with fails for types with padding, types with overloaded unary operator&, and any type that has some pointer or reference like member; or even types with any member or base of any of the aforementioned categories. So for a ton of stuff.

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
25

Let's take a perfectly normal class, say String. It's implemented as you'd think, with a char* that points to a new[]'ed buffer.

Now compare two of them. Obviously, String("abc")==String("abc"). Yet your implementation fails this test, as the two pointers differ.

Equality is defined by the class semantics, not by the bits directly inside the object.

MSalters
  • 173,980
  • 10
  • 155
  • 350
5

Yes, it is a terrible idea:
in case of uninitialized pointer:
Here is one failing sample (so this code has two different outputs):

#include <cstring>
#include <iostream>

class A {
public:
  A(int id) : id(id) {}

private:
  int id;
  A* a;
};

template <class T> inline bool operator==(const T &a, const T &b) {
  return memcmp(&a, &b, sizeof(a)) == 0;
}

int main() {
  std::cout << (A(10) == A(10)) << std::endl; // 1
  std::cout << (A(10) == A(15)) << std::endl; // 0
}

output:

0
0

and chances for two same values of RAM initial contents for two pointers is highly unlikely, then the other output is:

1
0
wasmup
  • 14,541
  • 6
  • 42
  • 58
  • 4
    Actually, your example has undefined behaviour, since the member `a` of `A` is unintialised. Outputting two zero values is not guaranteed. – Peter Aug 09 '17 at 13:52
1

As a slight aside, the nicest way I know of writing equality operators for classes with lots of members uses this idea (this code requires C++ 14):

#include <tuple>

struct foo
{
    int x    = 1;
    double y = 42.0;
    char z   = 'z';

    auto
    members() const
    {
        return std::tie(x, y, z);
    }
};

inline bool
operator==(const foo& lhs, const foo& rhs)
{
    return lhs.members() == rhs.members();
}

int
main()
{
    foo f1;
    foo f2;

    return f1 == f2;
}

Code on Compiler Explorer

Jeff
  • 718
  • 8
  • 20