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.)
Related
#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.
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
Let's say I have a class:
class A
{
//...
};
With well defined operator +=:
A& operator +=(const A&) {return *this;} //class without members
So let's try to overload operator+ also (as non-friend). I don't want to use class name to call constructor of temporary object (kinda want this make generic code):
A operator +(const A& other) const
{
return auto(*this)(*this) += other; //error: invalid use of auto
// /\/\/\/\/\ /\
// type of *this ||
// constructor call
}
auto is no good here. Let's try decltype.
A operator +(const A& other) const
{
return decltype(*this)(*this) += other; //error: 'A& A::operator+=(const A&)' discards
// /\/\/\/\/\/\/\ /\ qualifiers [-fpermissive] return
// type of *this || decltype(*this)(*this) += other;
// constructor call ^
}
This managed to get the type out of *this, but operator+ is declared const, so we got const A deduced (that's what I thought). So let's go on:
A operator +(const A& other) const
{
return typename std::remove_const<decltype(*this)>::type(*this) += amount;
//same error as previous
}
Now I got mindblown. Even thought I removed constness, it still discards
qualifier. Well, maybe that's because all I was doing was just CASTING. So stupid. To call a constructor I would have to generate code that (besides type) has ::Constructor (so I even tried to make an alias for constructor, but at this point I failed so hard). My broken brain gave up, but rest of my consciousness gave me an solution (which is generic in writing, so that's good):
// outside of class
template<class A>
inline A&& make_rvalue(A copy)
{
return std::move(copy);
}
// inside of class
A operator +(const A& other) const
{
return make_rvalue(*this) += other; // such generic code
}
That's what I ended with. But is there some trick that doesn't involve any other function call?
EDIT: I know classic methods of doing this, but what I search is described below:
operator is reduced to {return /*...*/;}
doesn't involve names of class methods or global functions
takes to account overloads with other types - it cannot take argument(s) by value, since class A != class B, argument int over const int& doesn't help much with Matrix class (but proxy operator that calls target operator with exchanged arguments is OK)
takes to account (possible) order of operation (x#y != y#x), where both should should have same return statement
return statement should be exacly the same for given operator# is every class that has overloaded operator +=
If you create a function like this:
template <typename T>
T add(T temp,const T &b)
{
temp += b;
return temp;
}
You can use it like this:
A operator+(const A& other) const { return add(*this,other); }
I'm going to go on record as saying this whole line of thinking/coding is going the wrong way. You already know that (in this case) the return type is A. Your code seems to be putting a lot of work into saying A in the most complex, indirect way possible.
A operator +(const A& other) const {
A ret {*this};
ret += other;
return ret;
}
Since you seem to really want to use C++11 features (even though in this case almost none of them provides any real help) I used a braced initializer, so it uses something from C++11. And no, this isn't one line, but it is readable.
Note that the same style is fine in generic code as well. For example, in a non-member overload of operator+, we might have something like this:
template <class T>
T operator+(T const &a, T const &b) {
T ret {a};
ret += b;
return ret;
}
This can be simplified a bit as well though:
template <class T>
T operator+(T ret, t const &b) {
ret += b;
return ret;
}
Again, we lose precisely nothing from the fact that older compilers can and will accept the code without problem.
I have this class definition:
class foo{
public:
foo();
foo(const int& var);
foo(const foo& var);
~foo();
const foo operator +(const foo& secondOp) const;
private:
int a;
//plus other values, pointers, e.t.c.
};
Also I have made this implementation for '+' operator overloading:
const foo foo::operator +(const foo& secondOp) const{
//I want here to check if i have one operand or two...
if ((FIRST_OPERAND.a!=0) && (secondOp.a==0)){
cout << "ERROR, second operand is zero";
return FIRST_OPERAND;
}
else if ((FIRST_OPERAND.a==0) && (secondOp.a!=0)){
cout << "ERROR, first operand is zero";
return secondOp;
}
}
When i write in main():
foo a(2);
foo b;
foo c;
//I want here to print the ERROR and
//return only the values of a
c = a + b;
Ηow can i return the value of the first operand if the second operand is zero and vice versa?
You're almost there. Since it's a member function, the first operand is *this, so replace FIRST_OPERAND.a with this->a or just a.
However, it might be better to make it a non-member function to allow conversions on both operands (i.e. to be able to write a + 2 or 2 + a). It will need to be a friend in order to access the private member(s).
friend foo operator +(const foo& firstOp, const foo& secondOp);
Also, it's best not to return a const value as that prevents moving from the return value.
It is the compiler that checks the syntaxical correctness of the program. It is unable to distinguish whether you indeed wanted to write
c = a;
that is to do the assignment or you wanted to write
c = a + b;
The both statements are correct.
It is a so-called logical error. The compiler is unable to see what we thoght.
For your line c = a; The assignment operator is implemented by the compiler (just shallow copying the object memory).
That is the reason your code compiles "without the second operand".
If you wish to disallow using the assignment operator - hide it. E.g. by implementing with the private access modifier.
You have to define this function as a friend function
friend Int operator + (int first, Int &second);
this function can perform obj + 2 or 2 + obj.
Int operator + (int first, Int& second){
second.a += first;
return second;
}
I have a strange issue defining == for one of my class.
I simplified the code here to an example that i tested on my visual 2013;
MyClass is defined in namespace N
This does compile:
N::MyClass a, b;
bool test = a == b;
This too:
const N::MyClass a, b;
bool test = a == b;
This does not compile
std::map<int, N::MyClass> a, b;
bool test = a == b;
For your information the == operator is declared like this :
bool operator==(const N::MyClass & a, const N::MyClass & b);
here is the error I get : error C2678: binary '==' : no operator found which takes a left-hand operand of type 'const MyClass' (or there is no acceptable conversion)
But from what I know the map operator is defined : map == reference on cppreference.com
could someone explain me why this is wrong ?
Thank you in advance.
I did not find an answer and I'm sorry if this is stupid.
[solution]
If I mode the operator into the namespace it works :
bool N::operator==(const MyClass & a, const MyClass & b);
But I don't know why I need to do that, is it in the language definition ? (I guess yes)
Based on your description, I guess your equality operator is not defined in the same namespace as your class. The following demonstrates the situation:
#include <map>
namespace foo
{
class bar
{
};
}
using namespace foo;
bool operator== (bar const&, bar const&) { return true; }
int main()
{
bar const b;
b == b; // OK
std::map<int, bar> mb;
mb == mb; // ERROR
}
The obvious fix is to define the equality operator in the same namespace as the class. The reason it doesn't work is the two-phase name look-up in templates: when a templates is instantiated it only does second phase name look-up, i.e., it only finds functions relating to template arguments based on explicit qualification or argument dependent look-up. The equality operator isn't qualified in std::map<K, V> (well, it is never qualified when using operator notation to call it) and, thus, it needs to be found by argument dependent look-up.
Your MyClass::operator== needs to be marked const. For example:
bool operator ==(const MyClass&) const;
Note that your operator:
bool operator==(const MyClass & a, const MyClass & b);
will not work. operator== only takes one argument and compares this and the passed argument.
Unless it's not a class member operator, but a non-member one. In this case, it's correct and should work. Since it doesn't, it suggests to me that you forgot to #include the header file where your operator is declared.