How to implement C++ (in)equality operators for aggregate structs? - c++

Sometimes I have structs such as this --
struct aggregate1 {
std::string name;
std::vector<ValueT> options;
size_t foobar;
// ...
};
-- where (in)equality is simply defined as (in)equality of all members: lhs_name == rhs_name && lhs_options == rhs_options && lhs_foobar == rhs_foobar.
What's the "best" way to implement this? (Best as in: (Runtime-)Efficiency, Maintainability, Readability)
operator== in terms of operator!=
operator!= in terms of operator==
Separate implementations for == and !=
As member or as free functions?
Note that this question is only about the (in)equality ops, as comparison (<, <=, ...) doesn't make too much sense for such aggregates.

I would do this but maybe move operator== definition to cpp file. Leave operator!= to be inline
Remember to compare member variables that are most likely to differ first so the rest are short-circuited and performance is better.
struct aggregate1 {
bool operator==(const aggregate1& rhs) const
{
return (name == rhs.name)
&& (options == rhs.options)
&& (foobar == rhs.foobar);
}
bool operator!=(const aggregate1& rhs) const
{
return !operator==(rhs);
}
std::string name;
std::vector<ValueT> options;
size_t foobar;
// ...
};

Member or free function is a matter of taste, and writing separate implementations of == and != seems to me boring, error-prone (you may forget a member in just one of the two operators, and it will take time to notice) without adding anything in terms of efficiency (calling the other operator and applying ! has a negligible cost).
The decision is restricted to "is it better to implement operator== in terms of operator!= or the contrary?
In my opinion, in terms of maintainability/readability/efficiency it's the same; I'd only recommend to do it in the same way everywhere for the sake of consistency. The only case where you'd want to prefer to use one or the other as the "base operator" is when you know that, in the types contained in your structure, that operator is faster than its negation, but I don't know when this could happen.

In C++20, implementing equality and inequality operators can be as simple as declaring operator== as default:
struct S {
int x;
// ...
// As member function
bool operator==(S const &) const = default;
// As non-member function (hidden friend)
// friend bool operator==(S const &, S const &) = default;
};
If only operator== is provided, a!=b is interpreted as !(a==b) according to overload resolution, so there is no need for providing an explicit overload for operator!=.
I would argue that defaulting operator== as a hidden friend is preferable because it works with reference-wrapped objects:
S s;
auto rs{std::ref(s)};
rs==rs; // OK for hidden friend; ill-formed if declared as member function
In this example, operator== is not defined for std::reference_wrapper<S>, but argument-dependent lookup (ADL) can select the hidden friend with operands implicitly-converted to S const &. Notice, however, that ::operator==(rs,rs) will only work if operator== is defined as a free function because ADL is not triggered for qualified names.

IMHO, implement as friends and implement the operator== (some STL algorithms will rely on this for example) and the operator!= should be implemented as the negation of the equals operator.

(-: Self answer :-)
I would like to highlight one aspect of aggregates WRT efficiency:
The order of evaluation of op== and op!= is irrelevant for (average) performance.
Assuming separate implementations for now and given the two extremes (a-eq) all subelements equal and (b-neq) all subelements inequal, we have these cases:
(a-eq) + operator== : Needs to compare all sub elements to return true
(a-eq) + operator!= : Needs to compare all sub elements to return false
(b-neq) + operator== : Returns false after 1st sub element is determined inequal
(b-neq) + operator!= : Returns true after 1st sub element is determined inequal
Since performance on average is the same either way it seems -- at least to me -- more natural to implement op!= in terms of op==, as it feels more natural to me to implement the equality op.

Related

Do we need to overload operator< in order to use set.find?

Let's say I have a class called Person and another class called School. In the second class I have a private field which is a set (of the standard library). The set represents the students (objects of the first class). In one of the functions of School, I want to check if a studet does not exist in the set of students. For that I do:
if (students.find(student) == students.end()) {
// throw exception
}
I was wondering if I need to overload one of the comparison operators of Person because otherwise how find knows to compare the objects? Reading the docs I see that this method is only using operator<. Does it mean that I have to overload it in Person? If I don't overload it, does it use a default one? Does the compiler creates a default operator less? Otherwise, as I understand, it should fail building?
1.: Actually overloading operator< is just 1 way the other way is to define a functor and give it to the set eg.:
class Comper_functor{
bool operator()(const Person& a, const Person& b);
}
...
std::set<Person,Comper_functor> s;
works just fine if you want to avoid overloading operator<
2.: operator< (or the compere function) does not have to be a "logical" operator just a consistent one for std::set. So it is good if for any Person a,b,c
a) if a<b and b<c -> a<c
b) if a<b -> !b>a
c) if !a<b and !b<a -> a==b
The easiest way to make it work is compering the 2 person member by member.
Eg.: for a
class Ponit{
double x,y;
public:
bool operator<(const point& o)const{
if (x!=o.x) return x<o.x;
return y<o.y;
}
}
works fine. Even if a point is can't be "smaller" then an other one.
3.: in c++20 you can say in the class definition
std::weak_ordering operator<=>(const Person& o)const=default;
bool operator==(const Person& o)const=default;
To generate all operators <,>,<=,>=,==,!=
In older versions you have to define operator< manually
Overloading the "less than" operator for a type is an option, but not necessarily the only way.
When you create a std::set, you can provide a comparator to compare objects. By default, the comparator it uses is std::less, which will use operator<, but your comparator can do anything else, provided it meets the requirements of ordering properly.
Some examples:
#include <set>
#include <functional>
struct EvenOddIncreasing {
bool operator()(int a, int b) const noexcept {
if ((a&1) < (b&1)) return true;
if ((a&1) > (b&1)) return false;
return a < b;
}
};
std::set<int, EvenOddIncreasing> evenOddIncreasing{1,2,3,4,5};
std::set<int, std::greater<>> decreasing{1,2,3,4,5};
std::set<int, std::less<>> increasing{1,2,3,4,5};
The code above uses an int, which has operator< obviously defined for it, but since the std::set is passed explicit comparator types, it will use those for ordering. Note, evenOddIncreasing is an example of a custom comparator that puts evens first, then odds, and in increasing order.
The above sets will have the following orderings:
even odd increasing...2 4 1 3 5
decreasing...5 4 3 2 1
increasing...1 2 3 4 5
In C++20, you could even use a lambda, but not with older versions of C++.
See it live:
https://godbolt.org/z/9ExhEGbb6

What is the use of operator<?

In the code snippet below, please, if someone can clarify what is the function of bool operator<... and why it is used as a function?
bool operator<(const RankedFace& other) const
{
if (lastDelay == other.lastDelay)
return face.getId() < other.face.getId();
return lastDelay < other.lastDelay;
}
It's the (in class) definition of the operator< for a user defined type (RankedFace I guess).
Thanks to that code, you will be able to compare two objects of type RankedFace with the <, e.g. if( r1 < r2 ) // do something...
It gives the type RankedFace a less-than comparison (operator<). As declared; it looks like a member method. It could also have been a non-member method with the following signature;
bool operator<(const RankedFace& lys, const RankedFace& rhs)
It is typically required for use in the standard library associative containers (std::set etc.).
The associative containers require a comparator to order the objects in them. A custom comparator can be used, but the standard one is std::less which is simply a lhs < rhs.
It allows client code to use the less than comparison on objects of that type (face1 < face2). It is often (not always) implemented together with other comparators (==, !=, <= etc.). If operator< and operator== have been implemented, the remaining ones can be implemented using std::rel_ops.
This is RankedFace's less than operator. It compares two RankedFace objects. For example:
RankedFace foo;
RankedFace bar;
cout << foo < bar ? "foo is smaller than bar" : "bar is greater than or equal to foo";
In the code above foo < bar causes C++ to call foo.operator<(bar).
Dissection of RankedFace::operator< reveals that it:
Considers the object with the lower lastDelay member the smaller object
For objects with identical lastDelays it considers the object which returns a lower getId() the smaller object.
An actual in code comparison between RankedFaces may not exist. The motivation for implementing the less than operator may have been that the less than operator is required to use a RankedFace as in the key in any associative container or unordered associative container.

No == operator found while comparing structs in C++

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?
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 */
}
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.
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.
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.
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.
}
Comparison doesn't work on structs in C or C++. Compare by fields instead.
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.
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.

C++ - Alphabetizing Strings

I am currently reading information from an input file. Of that information, there is a name. All the information is read into a struct. There is an array of these structs.
I need to alphabetize the structs by the last name, using a Binary Search Tree.
Do I need to write an operator overload function for ==, <, and >. If so can someone help me get started on that?
Yes, you will want to write operator overloads for == and <. (> is not needed; just use the else case after checking e.g. if (a < b); else if (a == b).)
In our case, since we are alphabetizing by last name, one struct is "less than" another if and only if its last name comes before the other's last name alphabetically.
So what exactly is the problem? Do you know how to write operator overloads? Do you know how to compare strings and determine which comes first alphabetically?
You need a way to compare any two instances of the struct. Writing a comparison operator, say, operator<(), might be a convenient way to go about it.
class Record {
friend bool operator<(const Record&, const Record&);
std::string name;
// ...
};
bool operator<(const Record& a, const Record& b)
{
// return true if the name of a is less than the name of b
}
Because a node inserts either on the left subtree or the right subtree you only need to know if a node is "less than" another node. If it isn't, then it doesn't matter whether it's greater or equal to the other node; it goes on the other subtree either way.
Of course, you might need the equality comparison for some other task. If you do then it's a good idea to go all the way and provide the inequality operator as well.
Also of interest is the rel_ops namespace.
Operator overloads work just like functions or methods. They get a return type and arguments in just the same way. For instance, a binary operator (like <) would be a member function with one argument or a free function with two, one each for each side of the operator. the only thing that's different is instead of having an identifier function name, they use a special syntax, the keyword operator followed by the operator being overloaded. So if we wanted to have a comparable type, we could do it this way:
class MyUserType
{
private:
std::string sort_significant;
std::string sort_insignificant;
public:
bool operator<(const MyUserType &) const;
};
bool MyUserType::operator<(const MyUserType & other) const
{
return sort_significant < other.sort_significant;
}
You would normally need to overload <, but if there are other elements in the struct that you might sometime want to sort by, it doesn't really make sense to do that. You should write a separate function that accepts two parameters of your struct, and compares them by last name, returning true if the first should come before the second, false otherwise. Then pass that function to a std::sort. Something like this:
bool compare_by_last_name(const MyStruct & lhs, const MyStruct & rhs)
{
return lhs.last_name < rhs.last_name;
}
// later
vector<MyStruct> v;
// put some elements in v
std::sort(v.begin(), v.end(), compare_by_last_name);
You will notice I have ignored your statement "Using a binary search tree", because I don't quite know what you mean, but it's probably irrelevant. Did you make your own container class or something?

Canonical operator overloading?

Is there a canonical or recommended pattern for implementing arithmetic operator overloading in C++ number-like classes?
From the C++ FAQ, we have an exception-safe assignment operator that avoids most problems:
class NumberImpl;
class Number {
NumberImpl *Impl;
...
};
Number& Number::operator=(const Number &rhs)
{
NumberImpl* tmp = new NumberImpl(*rhs.Impl);
delete Impl;
Impl = tmp;
return *this;
}
But for other operators (+, +=, etc..) very little advice is given other than to make them behave like the operators on built-in types.
Is there a standard way of defining these? This is what I've come up with - are there pitfalls I'm not seeing?
// Member operator
Number& Number::operator+= (const Number &rhs)
{
Impl->Value += rhs.Impl->Value; // Obviously this is more complicated
return *this;
}
// Non-member non-friend addition operator
Number operator+(Number lhs, const Number &rhs)
{
return lhs += rhs;
}
In Bjarne Stroustrup's book "The C++ Programming Language", in chapter 11 (the one devoted to Operator Overloading) he goes through witting a class for a complex number type (section 11.3).
One thing I do notice from that section is that he implements mixed type operations... this is probably expected for any numeric class.
In general, what you've got looks good.
The big thing to consider when writing any operator is that member operators do not undergo conversions on the left parameter:
struct example {
example(int);
example operator + (example);
};
void foo() {
example e(3), f(6);
e + 4; // okay: right operand is implicitly converted to example
e + f; // okay: no conversions needed.
6 + e; // BAD: no matching call.
}
This is because conversion never applies to this for member functions, and this extends to operators. If the operator was instead example operator + (example, example) in the global namespace, it would compile (or if pass-by-const-ref was used).
As a result, symmetric operators like + and - are generally implemented as non-members, whereas the compound assignment operators like += and -= are implemented as members (they also change data, meaning they should be members). And, since you want to avoid code duplication, the symmetric operators can be implemented in terms of the compound assignment ones (as in your code example, although convention recommends making the temporary inside the function).
The convention is to write operator+(const T&) and operator-(const T&) in terms of operator+=(const T&) and operator-=(const T&). If it makes sense to add and subtract to/from primitive types then you should write a constructor that constructs the object from the primitive type. Then the overloaded operators will also work for primitive types, because the compiler will call the appropriate constructor implicitly.
As you mentioned yourself, you should avoid giving access privileges to functions that don't need it. But in your code above for operator+(Number, const Number&) I'd personally make both parameters const references and use a temp. I think it isn't surprising the commenter below your question missed this; unless you have a good reason not to, avoid surprises and tricks and be as obvious as possible.
If you want your code to integrate with other numeric types, say std::complex, watch out for cyclic conversions. That is, don't supply operator OtherNumeric() in Numeric if OtherNumeric supplies a constructor that takes a Numeric parameter.
It is traditional to write the operator X in terms of the operator =X
It is also traditional the all parameters to the standard operators are const
// Member operator
// This was OK
Number& Number::operator+= (Number const& rhs)
{
Impl->Value += rhs.Impl->Value; // Obviously this is more complicated
return *this;
}
// Non-member non-friend addition operator
Number operator+(Number const& lhs,Number const& rhs)
{
// This I would set the lhs side to const.
// Make a copy into result.
// Then use += add the rhs
Number result(lhs);
return result += rhs;
}
You mention assignment operator.
But you did not mention the copy constructor. Since your class has ownership of a RAW pointer I would expect you to define this as well. The Assignment operator is then traditionally written in terms of the copy constructor.