131

Comparing two instances of the following struct, I receive an error:

struct MyStruct1 {
    MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int = -1) :
        my_struct_2(_my_struct_2),
        an_int(_an_int)
    {}

    std::string toString() const;

    MyStruct2 my_struct_2;
    int an_int;
};

The error is:

error C2678: binary '==' : no operator found which takes a left-hand operand of type 'myproj::MyStruct1' (or there is no acceptable conversion)

Why?

Azat Ibrakov
  • 9,998
  • 9
  • 38
  • 50
Jonathan Livni
  • 101,334
  • 104
  • 266
  • 359

8 Answers8

173

In C++, structs do not have a comparison operator generated by default. You need to write your own:

bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return /* your comparison code goes here */
}
Anthony Williams
  • 66,628
  • 14
  • 133
  • 155
  • 21
    @Jonathan: Why would C++ know how you want to compare your `struct`s for equality? And if you want the simple way, there's always `memcmp` so long your structs don't contain pointer. – Xeo Apr 21 '11 at 06:59
  • 17
    @Xeo: `memcmp` fails with non-POD members (like `std::string`) and padded structures. – fredoverflow Apr 21 '11 at 08:07
  • 1
    @Xeo - modern languages have a default comparison which is the most common and some way to override. Probably because modern languages hide most of the pointers\references mess. – Jonathan Livni Apr 21 '11 at 08:31
  • 17
    @Jonathan The "modern" languages I know do provide a `==` operator---with a semantic that is almost never what is wanted. (And they don't provide a means of overriding it, so you end up having to use a member function). The "modern" languages I know also don't provide value semantics, so you're forced to use pointers, even when they aren't appropriate. – James Kanze Apr 21 '11 at 08:48
  • 2
    @James - Our use cases may differ :) – Jonathan Livni Apr 25 '11 at 11:34
  • @Anthony - Is this also true for classes? – Jonathan Livni Apr 25 '11 at 11:35
  • 4
    @Jonathan Cases definitely do vary, even within a given program. For entity objects, the solution provided by Java works very well (and of course, you can do exactly the same thing in C++---it's even idiomatic C++ for entity objects). The question is what to do about value objects. C++ provides a default `operator=` (even if it often does the wrong thing), for reasons of C compatibility. C compatibility doesn't require an `operator==`, however. Globally, I prefer what C++ does to what Java does. (I don't know C#, so maybe that's better.) – James Kanze Apr 25 '11 at 14:56
  • 1
    @Jonathan In C++, `struct` declares/defines a class: the only difference between a class declared with `struct` and a class declared with `class` is the default access. So, a class defined with `class` still needs `operator==` to be defined manually. – Anthony Williams Apr 26 '11 at 07:12
  • 1
    @Xeo, to use in generic fucntion, why shallow comparison could be bad idea for default? – Yola Mar 14 '13 at 10:45
  • 16
    At least it _should_ be possible to `= default` it! – user362515 Feb 16 '16 at 16:12
  • 1
    There have been recent proposals to add default comparisons to C++17. They've all had problems so far though, but we might get them ironed out in time. – Anthony Williams Feb 17 '16 at 09:20
  • @AnthonyWilliams How about just giving it as the default keyword for comparisons? – Brent Nov 25 '16 at 23:42
  • 2
    Since in C++ `struct` is a class you can add `bool operator==(const MyStruct1& other)` definition and declaration to your `struct` and then use it. – rbaleksandar Nov 10 '17 at 10:47
  • You can, but that is asymmetric in terms of implicit conversions to the non-member version – Anthony Williams Nov 12 '17 at 16:29
  • Would it be better to put this definition in a header file or a cpp file? – Tyler Hilbert Sep 30 '18 at 22:31
  • Just treat it like any other function: if it's short, make it `inline` in a header file; if it's long, put it in a .cpp file. – Anthony Williams Oct 16 '18 at 15:12
131

C++20 introduced default comparisons, aka the "spaceship" operator<=>, which allows you to request compiler-generated </<=/==/!=/>=/ and/or > operators with the obvious/naive(?) implementation...

auto operator<=>(const MyClass&) const = default;

...but you can customise that for more complicated situations (discussed below). See here for the language proposal, which contains justifications and discussion. This answer remains relevant for C++17 and earlier, and for insight in to when you should customise the implementation of operator<=>....

It may seem a bit unhelpful of C++ not to have already Standardised this earlier, but often structs/classes have some data members to exclude from comparison (e.g. counters, cached results, container capacity, last operation success/error code, cursors), as well as decisions to make about myriad things including but not limited to:

  • which fields to compare first, e.g. comparing a particular int member might eliminate 99% of unequal objects very quickly, while a map<string,string> member might often have identical entries and be relatively expensive to compare - if the values are loaded at runtime, the programmer may have insights the compiler can't possibly
  • in comparing strings: case sensitivity, equivalence of whitespace and separators, escaping conventions...
  • precision when comparing floats/doubles
  • whether NaN floating point values should be considered equal
  • comparing pointers or pointed-to-data (and if the latter, how to know how whether the pointers are to arrays and of how many objects/bytes needing comparison)
  • whether order matters when comparing unsorted containers (e.g. vector, list), and if so whether it's ok to sort them in-place before comparing vs. using extra memory to sort temporaries each time a comparison is done
  • how many array elements currently hold valid values that should be compared (is there a size somewhere or a sentinel?)
  • which member of a union to compare
  • normalisation: for example, date types may allow out-of-range day-of-month or month-of-year, or a rational/fraction object may have 6/8ths while another has 3/4ers, which for performance reasons they correct lazily with a separate normalisation step; you may have to decide whether to trigger a normalisation before comparison
  • what to do when weak pointers aren't valid
  • how to handle members and bases that don't implement operator== themselves (but might have compare() or operator< or str() or getters...)
  • what locks must be taken while reading/comparing data that other threads may want to update

So, it's kind of nice to have an error until you've explicitly thought about what comparison should mean for your specific structure, rather than letting it compile but not give you a meaningful result at run-time.

All that said, it'd be good if C++ let you say bool operator==() const = default; when you'd decided a "naive" member-by-member == test was ok. Same for !=. Given multiple members/bases, "default" <, <=, >, and >= implementations seem hopeless though - cascading on the basis of order of declaration's possible but very unlikely to be what's wanted, given conflicting imperatives for member ordering (bases being necessarily before members, grouping by accessibility, construction/destruction before dependent use). To be more widely useful, C++ would need a new data member/base annotation system to guide choices - that would be a great thing to have in the Standard though, ideally coupled with AST-based user-defined code generation... I expect it'll happen one day.

Typical implementation of equality operators

A plausible implementation

It's likely that a reasonable and efficient implementation would be:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return lhs.my_struct2 == rhs.my_struct2 &&
           lhs.an_int     == rhs.an_int;
}

Note that this needs an operator== for MyStruct2 too.

Implications of this implementation, and alternatives, are discussed under the heading Discussion of specifics of your MyStruct1 below.

A consistent approach to ==, <, > <= etc

It's easy to leverage std::tuple's comparison operators to compare your own class instances - just use std::tie to create tuples of references to fields in the desired order of comparison. Generalising my example from here:

inline bool operator==(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) ==
           std::tie(rhs.my_struct2, rhs.an_int);
}

inline bool operator<(const MyStruct1& lhs, const MyStruct1& rhs)
{
    return std::tie(lhs.my_struct2, lhs.an_int) <
           std::tie(rhs.my_struct2, rhs.an_int);
}

// ...etc...

When you "own" (i.e. can edit, a factor with corporate and 3rd party libs) the class you want to compare, and especially with C++14's preparedness to deduce function return type from the return statement, it's often nicer to add a "tie" member function to the class you want to be able to compare:

auto tie() const { return std::tie(my_struct1, an_int); }

Then the comparisons above simplify to:

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

If you want a fuller set of comparison operators, I suggest boost operators (search for less_than_comparable). If it's unsuitable for some reason, you may or may not like the idea of support macros (online):

#define TIED_OP(STRUCT, OP, GET_FIELDS) \
    inline bool operator OP(const STRUCT& lhs, const STRUCT& rhs) \
    { \
        return std::tie(GET_FIELDS(lhs)) OP std::tie(GET_FIELDS(rhs)); \
    }

#define TIED_COMPARISONS(STRUCT, GET_FIELDS) \
    TIED_OP(STRUCT, ==, GET_FIELDS) \
    TIED_OP(STRUCT, !=, GET_FIELDS) \
    TIED_OP(STRUCT, <, GET_FIELDS) \
    TIED_OP(STRUCT, <=, GET_FIELDS) \
    TIED_OP(STRUCT, >=, GET_FIELDS) \
    TIED_OP(STRUCT, >, GET_FIELDS)

...that can then be used a la...

#define MY_STRUCT_FIELDS(X) X.my_struct2, X.an_int
TIED_COMPARISONS(MyStruct1, MY_STRUCT_FIELDS)

(C++14 member-tie version here)

Discussion of specifics of your MyStruct1

There are implications to the choice to provide a free-standing versus member operator==()...

Freestanding implementation

You have an interesting decision to make. As your class can be implicitly constructed from a MyStruct2, a free-standing / non-member bool operator==(const MyStruct2& lhs, const MyStruct2& rhs) function would support...

my_MyStruct2 == my_MyStruct1

...by first creating a temporary MyStruct1 from my_myStruct2, then doing the comparison. This would definitely leave MyStruct1::an_int set to the constructor's default parameter value of -1. Depending on whether you include an_int comparison in the implementation of your operator==, a MyStruct1 might or might not compare equal to a MyStruct2 that itself compares equal to the MyStruct1's my_struct_2 member! Further, creating a temporary MyStruct1 can be a very inefficient operation, as it involves copying the existing my_struct2 member to a temporary, only to throw it away after the comparison. (Of course, you could prevent this implicit construction of MyStruct1s for comparison by making that constructor explicit or removing the default value for an_int.)

Member implementation

If you want to avoid implicit construction of a MyStruct1 from a MyStruct2, make the comparison operator a member function:

struct MyStruct1
{
    ...
    bool operator==(const MyStruct1& rhs) const
    {
        return tie() == rhs.tie(); // or another approach as above
    }
};

Note the const keyword - only needed for the member implementation - advises the compiler that comparing objects doesn't modify them, so can be allowed on const objects.

Comparing the visible representations

Sometimes the easiest way to get the kind of comparison you want can be...

    return lhs.to_string() == rhs.to_string();

...which is often very expensive too - those strings painfully created just to be thrown away! For types with floating point values, comparing visible representations means the number of displayed digits determines the tolerance within which nearly-equal values are treated as equal during comparison.

Tony Delroy
  • 102,968
  • 15
  • 177
  • 252
  • Well, actually for the comparison operators <, >, <=, >= it should only be required to implement <. The rest follows, and there is no meaningful way to implement them meaning anything different than the implementation that can be generated automatically. It is bizarre you have to implement them all yourself. – André Sep 30 '15 at 13:18
  • @André: more often a manually written `int cmp(x, y)` or `compare` function returning a negative value for `x < y`, 0 for equality and a positive value for `x > y` is used as the basis for `<`, `>`, `<=`, `>=`, `==`, and `!=`; it's very easy to use the CRTP to inject all those operators into a class. I'm sure I've posted implementation in an old answer, but couldn't find it quickly. – Tony Delroy Oct 01 '15 at 03:05
  • @TonyD Sure you can do that, but it is just as easy to implement `>`, `<=` and `>=` in terms of `<`. You _could_ also implement `==` and `!=` that way, but that would usually not be a very efficient implementation I guess. It would be nice if no CRTP or other tricks would be needed for all this, but the standard would just mandate auto-generation of these operators if not explicitly defined by the user and `<` is defined. – André Oct 01 '15 at 07:03
  • @André: it's because `==` and `!=` might not be efficiently expressed using `<` that using compare for everything is common. *"It would be nice if no CRTP or other tricks would be needed"* - perhaps, but then CRTP can easily be used to generate lots of other operators (e.g. bitwise `|`, `&`, `^` from `|=`, `&=` and `^=`; `+` `-` `*` `/` `%` from their assignment forms; binary `-` from unary negation and `+`) - so many potentially useful variations on this theme that just providing a language feature for one pretty arbitrary slice of that isn't particularly elegant. – Tony Delroy Oct 01 '15 at 08:03
  • Would you mind adding to *A plausible implementation* a version that uses `std::tie` to do the comparison of multiple members? – NathanOliver Aug 10 '16 at 12:52
  • @NathanOliver: I don't think tie adds much value when only implementing `==`, so I've extended the discussion to highlight how tie helps when implementing `<`, `<=`, `>`, `>=` too.... Cheers. – Tony Delroy Aug 11 '16 at 04:12
  • @TonyD Awesome. Thank you so much for adding that. Added to my list as a dupe target. – NathanOliver Aug 11 '16 at 11:36
  • @NathanOliver: I'm impressed by your organisational drive! Cheers – Tony Delroy Aug 11 '16 at 11:50
  • 99% of your answer is very class-centric. I just want to compare simple structs. I would never think of putting any of the things you mention (methods, pointers, arrays, maps, unions, complex objects, etc) in a struct as that's very bad design. Structs should be simple unsynchronized public value containers, unlike classes which should safeguarded it's data and may contain logic. The only thing I truly agree about is the NaN issue. Calling naive memory comparisons 'unlikely what I wanted' is exactly opposite! – Mark Jeronimus Dec 22 '21 at 08:48
  • @MarkJeronimus: "[things in structs].,..very bad design" - well, I'm not going to try to change your mind about it here, but you do you. The C++ language itself makes little distinction between classes and structs, but you can apply whatever conventions you find best in your codebases. – Tony Delroy Dec 22 '21 at 10:47
18

You need to explicitly define operator == for MyStruct1.

struct MyStruct1 {
  bool operator == (const MyStruct1 &rhs) const
  { /* your logic for comparision between "*this" and "rhs" */ }
};

Now the == comparison is legal for 2 such objects.

iammilind
  • 68,093
  • 33
  • 169
  • 336
15

Starting in C++20, it should be possible to add a full set of default comparison operators (==, <=, etc.) to a class by declaring a default three-way comparison operator ("spaceship" operator), like this:

struct Point {
    int x;
    int y;
    auto operator<=>(const Point&) const = default;
};

With a compliant C++20 compiler, adding that line to MyStruct1 and MyStruct2 may be enough to allow equality comparisons, assuming the definition of MyStruct2 is compatible.

Joe Lee
  • 506
  • 3
  • 6
3

By default structs do not have a == operator. You'll have to write your own implementation:

bool MyStruct1::operator==(const MyStruct1 &other) const {
    ...  // Compare the values, and return a bool result.
  }
Jonathan Livni
  • 101,334
  • 104
  • 266
  • 359
1

Comparison doesn't work on structs in C or C++. Compare by fields instead.

Rafe Kettler
  • 75,757
  • 21
  • 156
  • 151
1

Out of the box, the == operator only works for primitives. To get your code to work, you need to overload the == operator for your struct.

Babak Naffas
  • 12,395
  • 3
  • 34
  • 49
1

Because you did not write a comparison operator for your struct. The compiler does not generate it for you, so if you want comparison, you have to write it yourself.