Comparing two instances of the following struct, I receive an error:
struct MyStruct1 {
MyStruct1(const MyStruct2 &_my_struct_2, const int _an_int =
Comparison doesn't work on structs in C or C++. Compare by fields instead.
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.
}
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.
In C++, struct
s 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 */
}
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.
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:
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 possiblyvector
, 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 doneunion
to compareoperator==
themselves (but might have compare()
or operator<
or str()
or getters...)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.
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.
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)
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 MyStruct1
s 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.
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 string
s 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.