I have implemented two classes (parent and derived) comparison operator, but testing it I have noticed a confusing behavior in case of using pointers. In addition, I have some other questions about good practices.
Here it is the code:
struct A
{
int getN() { return _n; }
virtual bool equals(A &other) {
return getN() == other.getN();
}
friend bool operator==(const A &one, const A &other);
A(int n) : _n(n) { }
private:
int _n;
};
bool operator==(const A &one, const A &other) {
return one._n == other._n;
}
struct B : public A
{
friend bool operator==(const B &one, const B &other);
B(int n, int m = 0) : A(n), _m(m) { }
private:
int _m;
};
bool operator==(const B &one, const B &other) {
if( operator==(static_cast<const A&>(one), static_cast<const A&>(other)) ){
return one._m == other._m;
} else {
return false;
}
}
int main()
{
A a(10), a2(10);
B b(10, 20), b2(10, 20), b3(10, 30);
A *a3 = new B(10, 20);
bool x1 = a == a2; // calls A operator (1)
bool x2 = b == b2; // calls B operator (2)
bool x3 = a == b; // calls A operator (3)
bool x4 = b == a; // calls A operator (4)
bool x5 = b == *a3; // calls A operator (5)
bool x6 = a == b3; // calls A operator (6)
bool x7 = b3 == a; // calls A operator (7)
return 0;
}
Questions
Comparing A instances with B ones, the A class operator is called, is this the correct behavior?
The point 5 is the one that seems confusing to me. a3 is declared as A but instanced as B, but the class A operator is called. Is there any way to solve this?
If the operator was implemented as a instance method, depending on it is called with an A object or a B one, the executed method is different. For example:
a == b // executes A::operator==
b == a // executes B::operator==
I assume that this is confusing and error prone, and must be avoided. Am I right?
Comparing A instances with B ones, the A class operator is called, is this the correct behavior?
Yes, because this is the only applicable overload.
If the operator was implemented as a instance method, depending on it is called with an A object or a B one, the executed method is different. [...] I assume that this is confusing and error prone, and must be avoided.
Right, this is not a good idea, because equality operator must be symmetric. Although it is possible to implement it symmetrically with two separate operators, it introduces a maintenance liability in the code.
One approach to solve this is to expan your equals member function, and have each subclass implement equality for its own type:
struct A {
int getN() const { return _n; }
virtual bool equals(A &other) const {
return getN() == other.getN();
}
friend bool operator==(const A &one, const A &other);
A(int n) : _n(n) { }
private:
int _n;
};
struct B : public A
{
B(int n, int m = 0) : A(n), _m(m) { }
virtual bool equals(B &other) const {
return A::equals(*this, other) && _m == other._m;
}
private:
int _m;
};
bool operator==(const A &one, const A &other) {
return typeid(one)==typeid(two) && one.equals(two);
}
"At the heart" of this implementation is typeid operator, which lets you check for type equality at run-time. The virtual call to one.equals(two) happens only if the types of one and two are exactly the same.
This approach puts responsibility for equality comparison with the class itself. In other words, each class needs to know how to compare its own instances, and optionally rely on its base for comparison of its state.
Comparing A instances with B ones, the A class operator is called, is
this the correct behavior?
B& can be converted to const A& but A& can't be converted to const B&. So comparing a == b there's really only one choice.
"Class operators" isn't a thing. In your code you've implemented operator== as non-member functions, that you've also made friends of A and B.
The point 5 is the one that seems confusing to me. a3 is declared as A
but instanced as B, but the class A operator is called. Is there any
way to solve this?
Dereferencing a3 returns A&, not B&.
If the operator was implemented as a instance method, depending on it
is called with an A object or a B one, the executed method is
different. For example:
a == b // executes A::operator==
b == a // executes B::operator==
I assume that this is confusing and error prone, and must be avoided. Am
I right?
If operator== is implemented as an instance method it's called on the left hand operand. In the first case it's a, in the second it's b. So b == a is only valid if you've implemented bool B::operator==(const A&).
Comparing A instances with B ones, the A class operator is called, is this the correct behavior?
Since you do not have any virtual functions in A you only deal with function overloading which means that compiler decides which function to call at compile time. As B publicly inherited from A, pointer or reference to B can be implicitly converted to A but not vice versa. In this case it means "unless both arguments are B only first resolution would be called". Note when you dereference a pointer only type of pointer matters to determine type at compile time, so if A *pointer points to instance of A or B does not matter in this case at all, *pointer always has type A.
If you want functions to be called based on actual type you need to use virtual functions, details on how to do that for operator== can be found here implementing operator== when using inheritance
Related
I have a class for which I would like to overload the addition operators. For my use case, it makes sense only to allow addition in the case where the a variable of each object is equal.
What is the best way to handle the case where they are not? Throw an exception, something else?
class A {
private:
int a, b;
public:
A(int a, int b)
:a(a), b(b) {}
A& operator+=(const A& rhs) {
if (this->a == rhs.a) {
this->b += rhs.b;
return *this;
}
else { //this->a != rhs.a
//what should I put here?
}
}
};
.
Edits:
These objects are created at run-time during file io.
These objects represent a data point in a spectrum. It only makes sense to add the intensities of two data points if they are at the same position.
a is limited to the range (-180.0, 360.0)
This smells like a is the property of the type, not a property of the value... What exactly does this class represent?
The minimally viable (IMHO) way to approach this is to make explicit the transition from an "anything goes" type to a "type compatible with a particular value of a". I.e.:
MyClass x(1,2), y(1,5);
x += y; // won't compile
x.makeCompatibleWith(y) += y; // will compile
It's usually a pessimization to have arithmetic operators like += throw. Instead, have something else assume the cost - then the cost is explicit, and you can keep += nothrow. It's also easy to search the project for costly operations (well, makeCompatibleWith is not super expensive, just more expensive than += since it adds the overhead of exception handling).
Assuming that the invalid cases are meant to be caught in testing, the makeCompatibleWith function could assert the condition it requires, but in release builds it would return some dummy object that turns the += into a no-op since it won't modify x - while still keeping += very simple and quick.
As to what exactly should makeCompatibleWith return: it's up to you. It can be a type that carries a reference, for example:
class MyClass
{
int a, b;
struct Internal
{
MyClass &val;
Internal(MyClass &val) : val(val) {}
MyClass &operator+=(const MyClass &o) noexcept {
val.b += o.b;
return val;
}
MyClass operator+(const MyClass &o) const noexcept {
return { val.a, val.b + o.b };
}
};
public:
MyClass() : a{}, b{} {}
MyClass(int a, int b) : a(a), b(b) {}
Internal makeCompatibleWith(const MyClass &o) noexcept {
thread_local static MyClass dummy;
assert(a == o.a);
if (a != o.a)
return { dummy };
return { *this };
}
};
Note that makeCompatibleWith would be undefined behavior when used from multiple threads if dummy wasn't thread-local.
#include <compare>
#include <iostream>
int main()
{
auto comp1 = 1.1 <=> 2.2;
auto comp2 = -1 <=> 1;
std::cout << typeid(comp1).name()<<"\n"<<typeid(comp2).name();
}
Output:
struct std::partial_ordering
struct std::strong_ordering
I know that if the operands have an integral type, the operator returns a PRvalue of type std::strong_ordering. I also know if the operands have a floating-point type, the operator yields a PRvalue of type std::partial_ordering.
But why should I use a three-way comparison operator instead of two-way operators (==, !=, <, <=, >, >=)? Is there an advantage this gives me?
It makes it possible to determine the ordering in one operation.
The other operators require two comparisons.
Summary of the other operators:
If a == b is false, you don't know whether a < b or a > b
If a != b is true, you don't know whether a < b or a > b
If a < b is false, you don't know whether a == b or a > b
If a > b is false, you don't know whether a == b or a < b
If a <= b is true, you don't know whether a == b or a < b
If a >= b is true, you don't know whether a == b or a > b
A neat side effect is that all the other operators can be implemented in terms of <=>, and a compiler can generate them for you.
Another side effect is that people might be confused by the use of <=> as the equivalence arrow in mathematics, which it has been pretty much since typewriters got those three symbols.
(I'm personally pretty miffed by how a <=> b is "truthy" if and only if a and b are not equivalent.)
The main advantage (at least for me) is the fact that this operator can be defaulted for the class, which will automatically support all possible comparisons for your class. I.e.
#include <compare>
struct foo {
int a;
float b;
auto operator<=>(const foo& ) const = default;
};
// Now all operations used before are defined for you automatically!
auto f1(const foo& l, const foo& r) {
return l < r;
}
auto f2(const foo& l, const foo& r) {
return l > r;
}
auto f3(const foo& l, const foo& r) {
return l == r;
}
auto f4(const foo& l, const foo& r) {
return l >= r;
}
auto f5(const foo& l, const foo& r) {
return l <= r;
}
auto f6(const foo& l, const foo& r) {
return l != r;
}
Previously, all those operations would have to be defined within the class, which is cumbersome and error-prone - as one would have to remember to revisit those whenever new members are added to the class.
Use your own judgment.
The point of the spaceship operator is not specifically for comparing objects. The main point of it is permitting the compiler to synthesize the other comparison operators from the spaceship operator.
If you don't specifically need to answer the question less-than, equal-to, or greater-than, then you don't need to invoke it directly. Use the operator that makes sense for you.
But should you need to make a type comparable, you only have to write 2 functions (spaceship and equality) rather than 6. And when writing such a function, you can use the spaceship operator on the individual types in question (should they be comparable in such a way). That makes it even easier to implement such functions.
The other useful thing that the spaceship operator allows you to do is tell what kind of ordering a comparison will provide. Partial, strong, or whatever. This can theoretically be useful, but it's fairly rare overall.
The spaceship operator was proposed by Herb Sutter and was adopted by the committee to be implemented with C++ 20, detailed report can be consulted here, or you if are more into lectures, here you can see a video of the man himself making the case for it. In pages 3/4 of the report you can see the main use case:
The comparison operators implementation, needed for pre C++20, in the following class:
class Point
{
int x;
int y;
public:
friend bool operator==(const Point &a, const Point &b) { return a.x == b.x && a.y == b.y; }
friend bool operator<(const Point &a, const Point &b) { return a.x < b.x || (a.x == b.x && a.y < b.y); }
friend bool operator!=(const Point &a, const Point &b) { return !(a == b); }
friend bool operator<=(const Point &a, const Point &b) { return !(b < a); }
friend bool operator>(const Point &a, const Point &b) { return b < a; }
friend bool operator>=(const Point& a, const Point& b) { return !(a < b); }
// ... non-comparisonfunctions ...
};
Would be replaced by:
class Point
{
int x;
int y;
public:
auto operator<=>(const Point &) const = default;
// ... non-comparison functions ...
};
So to answer your question, overloading operator<=> as class member allows you to use all comparison operators for the class objects without having to implement them, defaulting it also defaults operator==, if it's not otherwise declared, which in term automatically implements operator!=, making all comparison operations available with a single expression. This functionality is the main use case.
I would like to point out that the spaceship operator would not be possible without the introduction of the default comparison feature with C++20.
Defaulted three-way comparison
[...]
Let R be the return type, each pair of subobjects a, b is compared as follows:
[...]
... if R is std::strong_ordering, the result is:
a == b ? R::equal : a < b ? R::less : R::greater
Otherwise, if R is std::weak_ordering, the result is:
a == b ? R::equivalent : a < b ? R::less : R::greater
Otherwise (R is std::partial_ordering), the result is:
a == b ? R::equal : a < b ? R::less : b < a ? R::greater : R::unordered
Per the rules for any operator<=> overload, a defaulted <=> overload will also allow the type to be compared with <, <=, >, and >=.
If operator<=> is defaulted and operator== is not declared at all, then operator== is implicitly defaulted.
It also allows to default operator == only, which will implement operator !=, albeit not as versatile as the former, it's also an interesting possibility.
In C++, operators can be overloaded. If I have two classes A and B and for instance I can overload operator == in class A for comparison with an instance of B:
class A {
// Some attributes, methods
public:
bool operator ==(const B &b) {
// Implementation of operator ==
}
}
then I can run code like:
A a;
B b;
if(a == b) {
// Some code
}
However, if I write b == a, the compiler will look for
bool operator ==(const A &a) in class B. If it is not found, is there a default behavior in C++ normalization? Will the compiler, whatever it is, exit on failure or run bool operator ==(const B &b) instead?
And even better, if it does not find operator < () in class B, will it attempt operator >=() in class A?
No, it won't do either. There's nothing special about overloaded operators; they're just overloaded functions with funny-looking syntax. So a == b in the code above is nothing more than a call to a.operator==(const B&).
The reason that b == a fails, of course, is that there is no b.operator==(const A&) (and, similarly, no global operator).
The usual way to deal with this issue is to have a global operator== rather than a member: bool operator==(const A&, const B&), and another global operator== that goes the other way: bool operator==(const B&, const A&); if the comparison is, in fact, symmetric, then the second one can be trivially implemented by calling the first: bool operator==(const B& b, const A& a) { return a == b; }.
The same principle applies to operator<: it doesn't go the other way, and if you want one that does, you have to write it.
(small print: please ignore -- there is std::relops (spell-correction wants to change "relops" to "relapse") that will provide these variants; don't use it; it's far too invaisve.)
I define a global non-member method for + overloading
Like
ClassA operator+(const ClassA& a, const ClassB& b);
{
blah blah ;
}
In the communicative one, I can use
1)
ClassA operator+(const ClassB& b, const ClassA& a)
{
return operator+(a,b);
}
2)
ClassA operator+(const ClassB& b, const ClassA& a)
{
return a + b;
}
Any difference between 1 and 2?
Beyond the obvious difference pointed out first by #barakmanos in the comments ("the second one is more readable"), there is another technical difference.
Assume for a second, the following definitions:
struct B;
struct A
{
void operator+(B const&) const { ::std::cout << "member\n"; }
friend void operator+(A const&, B const&) { ::std::cout << "friend\n"; }
};
struct B { };
Now, consider the following three statements:
operator+(A(), B());
A().operator+(B());
A() + B();
The first one obviously calls the friend (free) function, the second one calls the member. However, the third one would call either if the other was not defined. In this special case, it cannot find a better match and would thus be ill formed. There are a few ways to modify this behaviour, e.g. if the member function was different in const-requirements, the expression could be correct and just lead to a larger overload set.
This demonstrates the difference in your two expressions: operator+(a,b) only considers free functions and not member functions overloading the binary plus operator.
In fact, the C++ standard has an example for another difference:
A() + B() only does argument dependent lookup, while operator+(A(), B()) does a normal function lookup. This means that the following is an error (taken from C++11 ยง13.3.1.2.10):
struct A { };
void operator + (A, A);
struct B {
void operator + (B);
void f ();
};
A a;
void B::f() {
operator+ (a,a); // error: global operator hidden by member
a + a; // OK: calls global operator+
}
Let's say we have this class A:
class A
{
public:
int a;
A(int b)
{
a = b;
}
};
I would like to create a + overload such that I could use it like this
A a(1),b(2),c(3),&d;
d = a + b + c;
without modifying the content of each object. The next logical thing would be allocating a new chunk of memory each time like this:
A &operator+ (const A &b)
{
A *c = new A(a+b.a);
return *c;
}
But this would create a new problem: intermediate results are lost, causing memory leaks.
I could have easily solved this problem by making a static function that takes three A object references and stores the sum of the first two in the third, but I'm willing to bet that there must be some way to make + overload happen the way I want.
So the question is: is there any way I could use a chain of operator overloads that do not modify the operands without causing memory leaks?
You can simply use pass by value and do this:
A operator+ (A other) //pass by value
{
other.a += a;
return other;
}
Or, since the member a is publicly accessible, you can (rather should) make operator+ a non-member function as:
A operator+(A left, A const &right)
{
left.a += right.a;
return left;
}
Notice that the first argument is accepted by value, and second by reference. In this way, you don't need to declare a local variable in the function. You can use the first parameter; after all it is local to the function, you can do whatever you want to do with it: in this case, we just add right.a to it, and return it.
A better design of class would be this: (read the comments)
class A
{
int a; //make it private
public:
A(int b) : a(b) //use member initialization list
{
}
A& operator+=(A const & other) //add `+=` overload, as member of the class
{
a += other.a;
return *this;
}
};
//and make `+` non-member and non-friend
A operator+(A left, A const & right)
{
left += right; //compute this in terms of `+=` which is a member function
return left;
}
There is no need to use pointers inside operator+. You can allocate the intermediate object in the stack and then return it:
A operator+ (const A &b)
{
A c(a+b.a);
return c;
}
Or just:
A operator+ (const A &b)
{
return A(a+b.a);
}
Or even simpler:
A operator+ (const A &b)
{
return a+b.a;
}
Since this implicitly calls A::A(int).
Note that I removed the reference from the return type. You can't return a non-const reference to a local.
Then you would use it this way:
A a(1),b(2),c(3),d;
d = a + b + c;
Note that d is no longer a reference.