359

I am a big fan of letting the compiler do as much work for you as possible. When writing a simple class the compiler can give you the following for 'free':

  • A default (empty) constructor
  • A copy constructor
  • A destructor
  • An assignment operator (operator=)

But it cannot seem to give you any comparison operators - such as operator== or operator!=. For example:

class foo
{
public:
    std::string str_;
    int n_;
};

foo f1;        // Works
foo f2(f1);    // Works
foo f3;
f3 = f2;       // Works

if (f3 == f2)  // Fails
{ }

if (f3 != f2)  // Fails
{ }

Is there a good reason for this? Why would performing a member-by-member comparison be a problem? Obviously if the class allocates memory then you'd want to be careful, but for a simple class surely the compiler could do this for you?

Mark Ingram
  • 71,849
  • 51
  • 176
  • 230
Rob
  • 76,700
  • 56
  • 158
  • 197
  • 6
    Of course, also the destructor is provided for free. – Johann Gerell Oct 20 '08 at 17:28
  • 38
    In one of his recent talks, Alex Stepanov pointed out that it was a mistake not to have a default automatic `==`, in the same way that there is a default automatic assignment (`=`) under certain conditions. (The argument about pointers is inconsistent because the logic applies both for `=` and `==`, and not just for the second). – alfC Sep 02 '15 at 06:45
  • 3
    @becko, it is one of the first in either the series "Efficient programming with components" or "Programming Conversations" both at A9, available in Youtube. – alfC Mar 16 '16 at 00:28
  • 2
    See this answer for C++20 information: https://stackoverflow.com/a/50345359 – VLL Nov 02 '18 at 07:38
  • My list of reasons - https://stackoverflow.com/a/5740505/410767 – Tony Delroy Mar 28 '23 at 12:30

13 Answers13

346

The argument that if the compiler can provide a default copy constructor, it should be able to provide a similar default operator==() makes a certain amount of sense. I think that the reason for the decision not to provide a compiler-generated default for this operator can be guessed by what Stroustrup said about the default copy constructor in "The Design and Evolution of C++" (Section 11.4.1 - Control of Copying):

I personally consider it unfortunate that copy operations are defined by default and I prohibit copying of objects of many of my classes. However, C++ inherited its default assignment and copy constructors from C, and they are frequently used.

So instead of "why doesn't C++ have a default operator==()?", the question should have been "why does C++ have a default assignment and copy constructor?", with the answer being those items were included reluctantly by Stroustrup for backwards compatibility with C (probably the cause of most of C++'s warts, but also probably the primary reason for C++'s popularity).

For my own purposes, in my IDE the snippet I use for new classes contains declarations for a private assignment operator and copy constructor so that when I gen up a new class I get no default assignment and copy operations - I have to explicitly remove the declaration of those operations from the private: section if I want the compiler to be able to generate them for me.

pooya13
  • 2,060
  • 2
  • 23
  • 29
Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • 36
    Good answer. I'd just like to point out that in C++11, rather than making the assignment operator and copy constructor private, you can remove them completely like this: `Foo(const Foo&) = delete; // no copy constructor` and `Foo& Foo=(const Foo&) = delete; // no assignment operator` – karadoc Jun 05 '13 at 10:19
  • 12
    "However, C++ inherited its default assignment and copy constructors from C" That does not imply why you have to make ALL C++ types this way. They should have just restricted this to plain old PODs, just the types that are in C already, no more. – thesaint Aug 11 '14 at 11:25
  • 6
    I can certainly understand why C++ inherited these behaviors for `struct`, but I do wish that it let `class` behave differently (and sanely). In the process, it also would have given a more meaningful difference between `struct` and `class` beside default access. – jamesdlin Jun 01 '18 at 00:42
128

Even in C++20, the compiler still won't implicitly generate operator== for you

struct foo
{
    std::string str;
    int n;
};

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ill-formed

But you will gain the ability to explicitly default == since C++20:

struct foo
{
    std::string str;
    int n;

    // either member form
    bool operator==(foo const&) const = default;
    // ... or friend form
    friend bool operator==(foo const&, foo const&) = default;
};

Defaulting == does member-wise == (in the same way that the default copy constructor does member-wise copy construction). The new rules also provide the expected relationship between == and !=. For instance, with the declaration above, I can write both:

assert(foo{"Anton", 1} == foo{"Anton", 1}); // ok!
assert(foo{"Anton", 1} != foo{"Anton", 2}); // ok!

This specific feature (defaulting operator== and symmetry between == and !=) comes from one proposal that was part of the broader language feature that is operator<=>.

Sergei Krivonos
  • 4,217
  • 3
  • 39
  • 54
Anton Savin
  • 40,838
  • 8
  • 54
  • 90
  • 4
    @dcmm88 Uhfortunately it won't be available in C++17. I've updated the answer. – Anton Savin Apr 19 '17 at 23:57
  • 3
    A modified proposal that allows the same thing (except the short form) is going to be in C++20 though :) – Rakete1111 Mar 02 '19 at 06:28
  • 2
    @artin It makes sense as adding new features to language should not break existing implementation. Adding new library standards or new things compiler can do is one thing. Adding new member functions where they did not exist previously is whole different story. To secure your project from mistakes it would require much more effort. I'd personally prefer compiler flag to switch between explicit and implicit default. You build project from older C++ standard, use explicit default by compiler flag. You already update compiler so you should configure it properly. For new projects make it implicit. – Maciej Załucki Jan 06 '20 at 20:55
81

The compiler wouldn't know whether you wanted a pointer comparison or a deep (internal) comparison.

It's safer to just not implement it and let the programmer do that themselves. Then they can make all the assumptions they like.

Mark Ingram
  • 71,849
  • 51
  • 176
  • 230
  • 335
    That problem doesn't stop it from generating a copy ctor, where it's quite harmful. – MSalters Oct 20 '08 at 09:57
  • 1
    Copy constructors are used in an entirely different context than comparison operators. And, IMHO, its context is clear in what it is doing. – spoulson Oct 20 '08 at 10:02
  • 12
    There is the problem of compatibility with C: C89 produces code for structs that mimicks C++'s assignement (and probably copy constructors... I have to check). Thus, it is normal C++ generates similar codes. – paercebal Oct 20 '08 at 13:43
  • 90
    Copy constructors (and `operator=`) generally work in the same context as comparison operators - that is, there is an expectation that after you perform `a = b`, `a == b` is true. It definitely makes sense for the compiler to provide a default `operator==` using the same aggregate value semantics as it does for `operator=`. I suspect paercebal is actually correct here in that `operator=` (and copy ctor) are provided solely for C compatibility, and they didn't want to make situation any worse. – Pavel Minaev Oct 29 '09 at 07:31
  • 52
    -1. Of course you want a deep comparison, if the programmer wanted a pointer comparison, he'd write (&f1 == &f2) – Viktor Sehr Sep 01 '10 at 09:07
  • 71
    Viktor, I suggest you re-think your response. If the class Foo contains a Bar*, then how would the compiler know whether Foo::operator== wants to compare the address of Bar*, or the contents of Bar? – Mark Ingram Sep 01 '10 at 13:59
  • 58
    @Mark: If it contains a pointer, comparing the pointer values is reasonable - if it contains a value, comparing the values is reasonable. In exceptional circumstances, the programmer could override. This is just like the language implements comparison between ints and pointer-to-ints. – Eamon Nerbonne Sep 26 '11 at 08:45
  • 19
    -1 as pointed out by others this is not consistent argument when comparing to operator= – naumcho Jan 05 '12 at 19:50
  • 8
    None of the stated reasons are valid. That would not be the craziest thing in the c++ standard if it said that compilers must provide a default 'operator==' comparing each field value. And hell, if we want a security against pointers, just issue a warning or an error in such cases. – Jem Apr 26 '13 at 20:31
  • 17
    @MarkIngram: your remark makes no sense. the compiler implemented operator == should just call operators == on all members if they are accessible. so for example a clone_ptr would not break the deep chain. neither would std::vector. however pointers and smart pointers would compare shallowly. – v.oddou Apr 09 '14 at 14:58
  • 7
    The compiler wouldn't need to figure that out. One of the two choices could be specified as the standard, then the compiler vendors would just have to follow that standard. The obvious choice, of course, would be shallow comparison for pointers, and value comparison for members, exactly the way v.oddou describes. – Balthazar May 15 '15 at 15:55
  • Why not applying same logic for the assignment operator too? There is an inconsistency issue. – Jolly Roger Sep 07 '19 at 12:43
  • @JollyRoger, not really, assignment operator can just assign each member variable, there's no ambiguity. – Mark Ingram Sep 09 '19 at 09:29
  • 1
    I don't see how it's a problem to compare the value of each member discretely. If the user wanted to compare the addresses of each object, then they'd implement `&A == &B` and be explicit about it. As for deep comparisons like `strcmp`, they don't cause an issue because C++ is rather clear about pointers being integer values (addresses) which can be compared normally (`==`) with another pointer. – Lapys Apr 21 '20 at 16:38
  • 1
    This inconsistency still continues within the standart committee as well. In C++ 20, they changed their mind. Now you can request a default comparison operator (under strong ordering condition). – E_g Nov 02 '20 at 12:53
47

IMHO, there is no "good" reason. The reason there are so many people that agree with this design decision is because they did not learn to master the power of value-based semantics. People need to write a lot of custom copy constructor, comparison operators and destructors because they use raw pointers in their implementation.

When using appropriate smart pointers (like std::shared_ptr), the default copy constructor is usually fine and the obvious implementation of the hypothetical default comparison operator would be as fine.

alexk7
  • 2,721
  • 1
  • 21
  • 18
  • The other answers try to justify the lack of an automatically defined operator== by comparing with C or give workarounds but this is the real answer It is just bad design. Comparison should actually recursively cascade by default. – phinz Apr 14 '23 at 20:36
45

It's answered C++ didn't do == because C didn't, and here is why C provides only default = but no == at first place. C wanted to keep it simple: C implemented = by memcpy; however, == cannot be implemented by memcmp due to padding. Because padding is not initialized, memcmp says they are different even though they are the same. The same problem exists for empty class: memcmp says they are different because size of empty classes are not zero. It can be seen from above that implementing == is more complicated than implementing = in C. Some code example regarding this. Your correction is appreciated if I'm wrong.

Rio Wing
  • 662
  • 5
  • 12
  • 8
    C++ doesn't use memcpy for `operator=` - that would only work for POD types, but C++ provides a default `operator=` for non POD types too. – Flexo Dec 07 '11 at 16:58
  • 4
    Yeah, C++ implemented = in a more sophisticated way. It seems C just implemented = with a simple memcpy. – Rio Wing Dec 09 '11 at 22:34
35

In this video Alex Stepanov, the creator of STL addresses this very question at about 13:00. To summarize, having watched the evolution of C++ he argues that:

  • It's unfortunate that == and != are not implicitly declared (and Bjarne agrees with him). A correct language should have those things ready for you (he goes further on to suggest you should not be able to define a != that breaks the semantics of ==)
  • The reason this is the case has its roots (as many of C++ problems) in C. There, the assignment operator is implicitly defined with bit by bit assignment but that wouldn't work for ==. A more detailed explanation can be found in this article from Bjarne Stroustrup.
  • In the follow up question Why then wasn't a member by member comparison used he says an amazing thing : C was kind of a homegrown language and the guy implementing these stuff for Ritchie told him he found this to be hard to implement!

He then says that in the (distant) future == and != will be implicitly generated.

Triskeldeian
  • 590
  • 3
  • 18
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
24

C++20 provides a way to easily implement a default comparison operator.

Example from cppreference.com:

class Point {
    int x;
    int y;
public:
    auto operator<=>(const Point&) const = default;
    // ... non-comparison functions ...
};

// compiler implicitly declares operator== and all four relational operators work
Point pt1, pt2;
if (pt1 == pt2) { /*...*/ } // ok, calls implicit Point::operator==
std::set<Point> s; // ok
s.insert(pt1); // ok
if (pt1 <= pt2) { /*...*/ } // ok, makes only a single call to Point::operator<=>
Barry
  • 286,269
  • 29
  • 621
  • 977
VLL
  • 9,634
  • 1
  • 29
  • 54
  • 9
    I'm surprised that they used `Point` as an example for an _ordering_ operation, since there is no reasonable default way to order two points with `x` and `y` coordinates... – pipe Nov 14 '18 at 09:51
  • 5
    @pipe If you don't care in which order the elements are, using default operator makes sense. For example, you might use `std::set` to make sure all points are unique, and `std::set` uses `operator<` only. – VLL Jan 24 '19 at 11:50
  • 2
    About return type `auto`: For *this case* can we always assume it will be `std::strong_ordering` from `#include `? – kevinarpe Jun 14 '20 at 13:09
  • 3
    @kevinarpe The return type is `std::common_comparison_category_t`, which for this class becomes the default ordering (`std::strong_ordering`). – VLL Jun 15 '20 at 05:44
16

It is not possible to define default ==, but you can define default != via == which you usually should define yourselves. For this you should do following things:

#include <utility>
using namespace std::rel_ops;
...

class FooClass
{
public:
  bool operator== (const FooClass& other) const {
  // ...
  }
};

You can see http://www.cplusplus.com/reference/std/utility/rel_ops/ for details.

In addition if you define operator< , operators for <=, >, >= can be deduced from it when using std::rel_ops.

But you should be careful when you use std::rel_ops because comparison operators can be deduced for the types you are not expected for.

More preferred way to deduce related operator from basic one is to use boost::operators.

The approach used in boost is better because it define the usage of operator for the class you only want, not for all classes in scope.

You can also generate "+" from "+=", - from "-=", etc... (see full list here)

Nate Kohl
  • 35,264
  • 10
  • 43
  • 55
sergtk
  • 10,714
  • 15
  • 75
  • 130
  • 5
    There's a reason `rel_ops` was deprecated in C++20: because [it doesn't work](https://godbolt.org/z/o0U3JS), at least not everywhere, and certainly not consistently. There is no reliable way to get `sort_decreasing()` to compile. On the other hand, [Boost.Operators](https://godbolt.org/z/FJdMIz) works and has always worked. – Barry Mar 15 '19 at 20:26
11

C++0x has had a proposal for default functions, so you could say default operator==; We've learnt that it helps to make these things explicit.

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

Conceptually it is not easy to define equality. Even for POD data, one could argue that even if the fields are the same, but it is a different object (at a different address) it is not necessarily equal. This actually depends on the usage of the operator. Unfortunately your compiler is not psychic and cannot infer that.

Besides this, default functions are excellent ways to shoot oneself in the foot. The defaults you describe are basically there to keep compatibility with POD structs. They do however cause more than enough havoc with developers forgetting about them, or the semantics of the default implementations.

Paul de Vrieze
  • 4,888
  • 1
  • 24
  • 29
  • 11
    There is no ambiguity for POD structs - they should behave in exact same way any other POD type does, which is value equality (rather then reference equality). One `int` created via copy ctor from another is equal to the one from which it was created; the only logical thing to do for a `struct` of two `int` fields is to work in exact same way. – Pavel Minaev Oct 29 '09 at 07:33
  • Well, but what if the POD struct has a value whose relevance is dependent on another value. In the case that the value is not relevant, it's no longer bit-for-bit equality. Equality can really range between one and the same object (only with pointers/references) to anything somewhat the save (perhaps of a subclass) – Paul de Vrieze Nov 04 '09 at 22:01
  • That's true but C++ does have a clear definition of a "value" (it's whatever is copied in the default copy constructor). The default equality, if there is one, would certainly work the same way: it would simply call `operator=` on each field. This would ensure that any POD values are compared as values, pointers are compared by reference, and objects with overridden `operator=` methods are compared as the class designer intended. I'd say conceptually it *is* easy to define equality. They didn't do it for other reasons. – mgiuca Jun 11 '12 at 09:09
  • @PavelMinaev Even for POD structs, it depends on what they represent on whether they are the same. Should equality be defined as identity, equivalence, or an other option. I'll agree with you that for many POD structs, equivalence (the same data values) is the right answer. – Paul de Vrieze Jun 13 '12 at 12:11
  • @PavelMinaev: All of the uses I can think of for a general-purpose means of equivalence testing would require that every object compare equivalent to itself. Unfortunately, IEEE-754 forbids the `==` and `!=` operators from exhibiting such behavior. – supercat Mar 04 '15 at 20:22
  • @mgiuca: Given `struct {double v;} a,b; a.v=0.0/0.0; b=a;`, what should `a==b` report? Were it not for the broken behavior of `==` with floating-point types, then `a.v==b.v` would be true, and `a==b` would pose no problem. As it is, though, while I can see great value in having a standard means of asking objects to test equivalence, such usefulness would be predicated upon a contractual requirement that the test behave as an equivalence relation, something the `==` operator can't do with all types. – supercat Mar 04 '15 at 20:33
  • @supercat Why do you assume the automatic `==` operator should be required to satisfy reflexitivity? I agree that would be ideal, but given C++ already has `==` operators that aren't reflexive (as you point out), an automatically generated `==` operator for a composite type can only be as good as the `==` operators of its fields. This doesn't detract from the usefulness of automatically generating a default `==` operator that says "we're equal if all our fields are equal according to `==`". – mgiuca Mar 05 '15 at 01:57
  • 1
    @mgiuca: I can see considerable usefulness for a universal equivalence relation that would allow any type that behaves as a value to be used as a key in a dictionary or similar collection. Such collections cannot behave usefully without a guaranteed-reflexive equivalence relation, however. IMHO, the best solution would be to define a new operator which all built-in types could implement sensibly, and define some new pointer types which were like the existing ones except that some would define equality as reference equivalence while others would chain to the target's equivalence operator. – supercat Mar 05 '15 at 04:07
  • @supercat: That is really just an argument for `==` implementations to obey the rules of equivalence relations. Unfortunately, that ship has sailed, and I don't think introducing a new operator that's almost exactly the same as `==` except in a few corner cases is really that helpful. As far as I know, *all* built-in types obey equivalence laws other than float and double. Of course, user-defined types can define `==` and violate those laws, but so they can too with your new operator. None of this affects whether having automatic `==` for user-defined types is useful or not. – mgiuca Mar 05 '15 at 07:58
  • @mgiuca: There are many situations where it's necessary to test floating-point numbers for equivalence. If programmers wanting to test fp equivalence have to write something like `bool equals(double x, double y) { return (x==y) || (x!=x) && (y!=y); }`, such testing is apt to be inefficient even on hardware which would allow direct equivalence testing. Adding a means of equivalence testing which would work for fp types, but could also be implemented by other types that promise that it represents an equivalence relation would seem nicer than adding one just for fp types. – supercat Mar 05 '15 at 15:05
  • @supercat Sorry for slow reply. Yeah, I'm not disagreeing that it would be helpful to have a proper equivalence for FP. Rather, that the *root* of the issue you are talking about is not that the `==` operator is broken, but rather that equality for FPs is broken. `==` for almost all intents and purposes does represent an equivalence class, and therefore extending it automatically to structs would make sense. It just has this one rare edge case where it's broken. Adding a new operator would only confuse matters and solve hardly any problems. ("Why are there 2 equality operators?!") – mgiuca Mar 13 '15 at 06:14
  • 1
    @supercat By analogy, you could make almost the same argument for the `+` operator in that it is non-associative for floats; that is `(x + y) + z` != `x + (y + z)`, due to the way FP rounding occurs. (Arguably, this is a far worse problem than `==` because it is true for normal numeric values.) You might suggest adding a new addition operator that works for all numeric types (even int) and is almost exactly the same as `+` but it is associative (somehow). But then you would be adding bloat and confusion to the language without really helping that many people. – mgiuca Mar 13 '15 at 06:20
  • 1
    @mgiuca: Having things which are quite similar except at edge cases is often *extremely* useful, and misguided efforts to avoid such things result in much needless complexity. If client code will sometimes need edge cases to be handled one way, and sometimes need them to be handled another, having a method for each style of handling will eliminate a lot of edge-case-handling code in the client. As for your analogy, there is no way to define operation on fixed-sized floating-point values to yield transitive results in all cases (though some 1980s languages had better semantics... – supercat Mar 14 '15 at 01:16
  • 1
    ...than today's in that regard) and thus the fact that they don't do the impossible should not be a surprise. There is no fundamental obstacle, however, to implementing an equivalence relation which would be universally applicable to any type of value which can be copied. – supercat Mar 14 '15 at 01:19
5

Just so that the answers to this question remains complete as the time passes by: since C++20 it can be automatically generated with command auto operator<=>(const foo&) const = default;

It will generate all the operators: ==, !=, <, <=, >, and >=, see https://en.cppreference.com/w/cpp/language/default_comparisons for details.

Due to operator's look <=>, it is called a spaceship operator. Also see Why do we need the spaceship <=> operator in C++?.

EDIT: also in C++11 a pretty neat substitute for that is available with std::tie see https://en.cppreference.com/w/cpp/utility/tuple/tie for a complete code example with bool operator<(…). The interesting part, changed to work with == is:

#include <tuple>

struct S {
………
bool operator==(const S& rhs) const
    {
        // compares n to rhs.n,
        // then s to rhs.s,
        // then d to rhs.d
        return std::tie(n, s, d) == std::tie(rhs.n, rhs.s, rhs.d);
    }
};

std::tie works with all comparison operators, and is completely optimized away by the compiler.

Janek_Kozicki
  • 736
  • 7
  • 9
2

Is there a good reason for this? Why would performing a member-by-member comparison be a problem?

It may not be a problem functionally, but in terms of performance, default member-by-member comparison is liable to be more sub-optimal than default member-by-member assignment/copying. Unlike order of assignment, order of comparison impacts performance because the first unequal member implies the rest can be skipped. So if there are some members that are usually equal you want to compare them last, and the compiler doesn't know which members are more likely to be equal.

Consider this example, where verboseDescription is a long string selected from a relatively small set of possible weather descriptions.

class LocalWeatherRecord {
    std::string verboseDescription;
    std::tm date;
    bool operator==(const LocalWeatherRecord& other){
        return date==other.date
            && verboseDescription==other.verboseDescription;
    // The above makes a lot more sense than
     // return verboseDescription==other.verboseDescription
     //     && date==other.date;
    // because some verboseDescriptions are liable to be same/similar
    }
}

(Of course the compiler would be entitled to disregard the order of comparisons if it recognizes that they have no side-effects, but presumably it would still take its que from the source code where it doesn't have better information of its own.)

Museful
  • 6,711
  • 5
  • 42
  • 68
  • 2
    But nobody keeps you from writing an optimizing user-defined comparison if you find a performance problem. In my experience that would be a minuscule minority of cases though. – Peter - Reinstate Monica Mar 26 '19 at 15:36
0

I agree, for POD type classes then the compiler could do it for you. However what you might consider simple the compiler might get wrong. So it is better to let the programmer do it.

I did have a POD case once where two of the fields were unique - so a comparison would never be considered true. However the comparison I needed only ever compared on the payload - something the compiler would never understand or could ever figure out on it's own.

Besides - they don't take long to write do they?!

graham.reeds
  • 16,230
  • 17
  • 74
  • 137
  • 3
    It's not that they take time to write, it's that it's easy to mess them up (or forget to update them later as you add more member-variables to the class). Nothing is more fun than spending several hours tracking down a run-time bug that was caused by the `==` operator neglecting to compare one of the three dozen member-variables of a POD class :/ – Jeremy Friesner Apr 09 '21 at 01:01