Why should operator< be non-member function? - c++

I remeber C++ Primer tells us operator< should be non-member function, and I always obey the rule. But now I want to know the reason.
I wrote following code:
#include <iostream>
using std::cout;
using std::endl;
struct Point1
{
int x, y;
Point1(const int a, const int b): x(a), y(b) { }
};
inline bool operator<(const Point1& lhs, const Point1& rhs)
{
return lhs.x < rhs.x || (lhs.x == rhs.x && lhs.y < rhs.y);
}
struct Point2
{
int x, y;
Point2(const int a, const int b): x(a), y(b) { }
bool operator<(const Point2& rhs)
{
return x < rhs.x || (x == rhs.x && y < rhs.y);
}
};
int main()
{
Point1 a(1, 2), b(1, 3);
cout << (a < b) << " " << (b < a) << endl;
Point2 c(2, 3), d(2, 4);
cout << (c < d) << " " << (d < c) << endl;
}
In this case, It seems they don't make difference and member function seems much simpler.
But in this case:
#include <iostream>
using std::cout;
using std::endl;
// Usually I write it for comparing floats
class Float1
{
long double _value;
public:
static const long double EPS = 1e-8;
Float1(const long double value): _value(value) { }
const long double Get() const { return _value; }
};
inline bool operator<(const Float1& lhs, const Float1& rhs)
{
return rhs.Get() - lhs.Get() > Float1::EPS;
}
inline bool operator<(const Float1& lhs, const long double rhs)
{
return rhs - lhs.Get() > Float1::EPS;
}
class Float2
{
long double _value;
public:
static const long double EPS = 1e-8;
Float2(const long double value): _value(value) { }
const long double Get() const { return _value; }
bool operator<(const Float2& rhs)
{
return rhs._value - _value > Float2::EPS;
}
bool operator<(const long double rhs)
{
return rhs - _value > Float2::EPS;
}
};
int main()
{
Float1 x(3.14);
Float2 y(2.17);
long double zero = .0;
cout << (x < zero) << " " << (zero < x) << endl;
//cout << (y < zero) << " " << (zero < y) << endl; Compile Error!
}
Both (x < zero) and (zero < x) work! (is long double converted to Float?)
But (zero < y) don't, because zero is not a Float.
You see, in first case, member function costs less code length, and in second case, non-member function makes comparing easier. So I want to know
In first case, should I use member function instead of non-member function?
Why C++ Primer suggests binary operators be non-member function?
Is there any other case that member function and non-member function make difference?
Thanks for helping!

I think the basic answer is that non-member functions play better with implicit conversion. So if you can write your binary operator as a non-member function, you should.

The question can be answered in different levels. In the highest level, from a design standpoint, operator< is a binary operator. It is no more of an operation on the left hand side that it is on the right. Member functions, on the other hand are bound to the first argument, they are an operation on the first type.
From a technical point of view, and going right to the C++ language, this comes down to what you already noticed: member functions are not symmetric with respect to the types. That lack of symmetry means that an operator< declared as a member function can only be applied when the left hand side is of the type that contains the member. No conversions can be applied before a member operator is called. On the other hand, because free functions are not more bound to the first argument than they are for the second, the free function operator< will be picked up by ADL whenever any of the two arguments is of the appropriate type, allowing the same conversions to the first and second arguments.

As a non-member the comparison operator works also for derived class (left hand side) arguments.
EDIT: and as #jonathan points out in a comment, it also allows value conversions, such as e.g. an int left hand side argument.
If constructors and conversion operators are not explicit, such conversions can allow meaningless and probably unintended code, e.g. comparing a T instance to 5.

Related

What does < (less-than sign) between a function identifier and a function parameter mean? [duplicate]

This question already has answers here:
What are the basic rules and idioms for operator overloading?
(8 answers)
Closed last month.
struct point {
int x, y;
bool operator<(const point &p) {
if (x == p.x) return y < p.y;
else return x < p.x;
}
};
What does < between operator and (const point &p) signify?
What does < between operator and (const point &p) signify?
It's a part of the member function name. It is a definition of a member function which is an overload of the < operator. It is what makes this work:
point a{};
point b{};
if(a < b) { // point::operator<(const point&) is used here.
//
}
Note: The member function should really be const qualified because calling the function does not change *this (the lefthand side of <).
bool operator<(const point &p) const {
// ^^^^^
... and it's also preferable if it's not even a member function, but a free function (taking two const point&):
bool operator<(const point& lhs, const point &rhs) {
return std::tie(lhs.x, lhs.y) < std::tie(rhs.x, rhs.y);
}
This is because if point has an implicit conversion constructor, it will allow an instance of a type that is implicitly convertible to point to appear on both the lefthand and righthand side of <.
Example:
struct point {
point(int X, int Y) :x{X}, y{Y} {}
point(const std::pair<int, int>& pair) : x{pair.first}, y{pair.second} {}
int x, y;
};
bool operator<(const point& lhs, const point &rhs) {
return std::tie(lhs.x, lhs.y) < std::tie(rhs.x, rhs.y);
}
int main() {
std::pair<int, int> ip{10,20};
point pnt{10,19};
// This works with operator< as a member function or a free function:
if(pnt < ip) std::cout << "pnt < ip\n";
// This will not work if `operator<` is a member function:
if(ip < pnt) std::cout << "ip < pnt\n";
// ^^
// implicitly converted to `point`
}

incorrect function call on overload operator

In the code, why is (10 != i) calling == instead of !=? The other two call !=
#include <iostream>
class Integer
{
int x;
public:
bool
operator== (const Integer &i)
{
std::cout << "==";
return x == i.x;
}
bool
operator!= (const Integer &i)
{
std::cout << "!=";
return x != i.x;
}
Integer (int t = 0) { x = t; }
};
int
main ()
{
Integer i;
std::cout << (i != i) << '\n'; // calls !=
std::cout << (i != 100) << '\n'; // calls !=
std::cout << (10 != i) << '\n'; // calls ==
}
Prior to C++20, you'd need to add two free functions for the comparison where the int is on the left-hand side:
bool operator==(int lhs, const Integer& rhs) {
return rhs == lhs;
}
bool operator!=(int lhs, const Integer& rhs) {
return rhs != lhs;
}
You should also make the member comparison operators const qualified:
class Integer {
public:
//...
bool operator==(const Integer &i) const { // note const
std::cout << "==";
return x == i.x;
}
bool operator!=(const Integer &i) const { // note const
std::cout << "!=";
return x != i.x;
}
//...
};
You could also remove the member operators to simplify things. Now the left-hand side int will be implicitly converted to Integer and then compared with the right-hand side:
class Integer {
int x;
public:
Integer(int t = 0) : x{t} {}
friend bool operator==(const Integer& lhs, const Integer& rhs) {
return rhs.x == lhs.x;
}
friend bool operator!=(const Integer& lhs, const Integer& rhs) {
return !(rhs == lhs);
}
};
Since you've tagged this C++20, you can let operator== do all the work. See Default comparisons. It'll be used for operator!= too.
class Integer {
int x;
public:
bool operator==(const Integer &i) const {
std::cout << "==";
return x == i.x;
}
Integer(int t = 0) : x{t} {}
};
... and it'll correctly show that it used operator== for all your != comparisons (and negated it).
More from Defaulted equality comparison:
A class can define operator== as defaulted, with a return value of bool. This will generate an equality comparison of each base class and member subobject, in their declaration order. Two objects are equal if the values of their base classes and members are equal. The test will short-circuit if an inequality is found in members or base classes earlier in declaration order.
Per the rules for operator==, this will also allow inequality testing
This means that you will in fact get away with the below only since C++20:
class Integer {
int x;
public:
bool operator==(const Integer &i) const = default;
Integer(int t = 0) : x{t} {}
};
or even better, get all the comparison operators for free by defaulting the spaceship operator <=>:
class Integer {
int x;
public:
auto operator<=>(const Integer &i) const = default;
Integer(int t = 0) : x{t} {}
};
There are two new additions to C++20 that made this possible (note that your code doesn't compile in earlier standard versions).
Compiler will attempt to replace a != b with !(a == b) if there is no suitable != for these arguments.
If there is no suitable a == b, compiler will attempt b == a as well.
So, what happens - compiler first notices that it doesn't know how to compare 10 != i, so it tries !(10 == i). There is still no suitable comparison, so it tries !(i == 10) and it can finally be done using your implicit constructor to convert 10 to Integer.
It can be easily verified by adding more info to debug print:
bool
operator== (const Integer &i) const
{
std::cout << x << "==" << i.x << ' ';
return x == i.x;
}
will print 0==10 1 in the last line (see it online).
As noticed in comments, you don't even need operator !=, due to aforementioned behaviour C++20 compiler will automatically convert any such call to operator ==.

Struct property operator overload

I'm writing a game in C++ for my Arduino and I've recently found the joys of operator overloading in structs. So far so good! I'm now stuck on the syntax to overload operators on properties. I want to implement something like this so that if my x or y values increase over the screen width I wrap the value back to 0. Many thanks!
// My guess :(
x& operator++(x &newx, int){
if (x == SCREEN_WIDTH - 1)
return 0;
else
return x + 1;
}
My struct definition is:
struct point_t
{
uint8_t x;
uint8_t y;
x& operator++(x &newx, int){
if (x == SCREEN_WIDTH - 1)
return 0;
else
return x + 1;
}
point_t& operator=(const point_t &p)
{
x = p.x;
y = p.y;
return *this;
}
bool operator==(const point_t &p) const
{
return (x == p.x && y == p.y);
}
bool operator!=(const point_t &p) const
{
return !(x == p.x && y == p.y);
}
};
You can't do this exactly as written. The return type of operator should be type.
What you can do is to create a new type, say coordinate, overload operator++ for it, and have your x (and y) have coordinate type, not uint8_t.
Possible solution (based on code by #MrMase):
template<uint8_t MAX>
class coordinate_t
{
private:
int8_t _p;
public:
coordinate_t(int8_t p = 0): _p(p) {}
// Postfix ++
coordinate_t operator++(int)
{
coordinate_t p(_p);
++_p;
if (_p > MAX - 1)
_p = 0;
return p;
}
// Postfix --
coordinate_t operator--(int)
{
coordinate_t p(_p);
--_p;
if (_p < 0)
_p = MAX - 1;
return p;
}
int get() const {return _p;}
};
typedef coordinate_t<SCREEN_WIDTH> xcoordinate_t;
typedef coordinate_t<SCREEN_HEIGHT> ycoordinate_t;
I've made it a template to allow for a simple typedef to define different coordinates; you might also have made MAX a private field and subclass it for different coordinates. In fact, it seems that the template solution is more safe, as it will not allow you to mix different coordinates; however, you might want to reconsider this based on your actual usage.
See full example: http://coliru.stacked-crooked.com/a/5a34261310d05a54
When overloading unary operators (operators with only a single operand, like the increase/decrease operators ++ and --) as member functions, they don't have an argument since they are performed on this. The exceptions being the dummy int arguments for the postfix increase/decrease operators.
The returned value differs depending on which operator you overload, but generally for unary operator you return a reference to object, i.e. *this.
Example
struct point_t
{
int x;
// Prefix increase operator
point_t& operator++()
{
++x;
return *this;
}
// Postfix increase operator
point_t operator++(int)
{
point_t old(*this); // Create new object using copy-constructor
operator++(); // Call prefix operator++ on `this`
return old; // Return old value, before increment
}
...
};
It's all documented in this operator overloading reference, as well as plenty of tutorials all over the Internet.
On an unrelated note, that operator!= can be implemented by using the == operator that you have already implemented:
bool operator!=(const point_t& p) const
{
return !(*this == p);
}
It's always a good idea to use existing operators when creating related operator overloads.
It seems it's not possible to overload a property of an enum so I'm going to do the next best thing: create a new type called xcordinate and overload the ++operator
Thanks very much to everyone who helped. I'm grateful! :)

Overloading operators for class with cast operator and single-argument constructor

I have such code
class Number
{
int m_value;
public :
Number(const int value) :
m_value(value)
{
}
operator const int() const
{
return m_value;
}
int GetValue() const
{
return m_value;
}
};
bool operator==(const Number& left, const Number& right)
{
return left.GetValue() == right.GetValue();
}
class Integer
{
int m_value;
public :
Integer(const int value) :
m_value(value)
{
}
operator const int() const
{
return m_value;
}
bool operator==(const Integer& right) const
{
return m_value == right.m_value;
}
bool operator==(const int right) const
{
return m_value == right;
}
int GetValue() const
{
return m_value;
}
};
bool operator==(const int left, const Integer& right)
{
return left == right.GetValue();
}
int main()
{
Number n1 = 1;
Number n2 = 1;
int x3 = 1;
n1 == n2;
n1 == x3; // error C2666: 'operator ==' : 3 overloads have similar conversions
x3 == n1; // error C2666: 'operator ==' : 2 overloads have similar conversions
Integer i4 = 1;
Integer i5 = 1;
i4 == i5;
i4 == x3;
x3 == i4;
return 0;
}
For class Number I have two errors as shown in the code above. For class Integer everything is OK. The problem is, I want to keep in resulting class single-parameter constructor, cast operator and equality operations (MyClass == int, int == MyClass, MyClass == MyClass), but I want to implement only one version of operator== as in class Number. I don't see any way to do this. Is that even possible or I must have all three implementations as in class Integer? I know why I get these errors I just don't like the solution I have.
In class Number you define a conversion operator to int and your constructor allows converting an int to a Number. Therefore, when comparing a Number n and an int x for equality, ambiguity arises: should the compiler invoke the built-in operator == for ints and convert n to an int, or should it rather pick your operator and convert x to a Number? Both conversions are equally good, and it can't choose one.
So yes you have to define three versions, or add a template operator which can perfectly match the type of all arguments and forward to your operator explicitly, like this one (but you most likely want to guard it with some enable_if to limit its applicability only to the appropriate T and U):
template<typename T, typename U> // beware: this will match anything. to be constrained
bool operator == (T n, U const& u)
{
return (Number(n) == Number(u));
}
You can define only one operator== as member function:
bool operator==(const int& right) const
{
std::cout << "custom\n";
return this->GetValue() == right;
}
Then,
n1==n2: n2 will be converted to int and custom operator will be used.
n1 == n3: custom operator will be used
n3==n1: built-in operator will be used
Note, that you want your operator== be const to be able to compare constant Numbers
In C++11 you can make operator int explicit.
Another approach would be to use SFINAE to have a template == that works for one or more Number args, but that is using a bazooka to kill an ant.

Overloading operators in typedef structs (c++)

I want to make a typedef struct called pos (from position) that stores coordinates x and y. I am trying to overload some operators for this struct, but it does not compile.
typedef struct {
int x;
int y;
inline pos operator=(pos a) {
x=a.x;
y=a.y;
return a;
}
inline pos operator+(pos a) {
return {a.x+x,a.y+y};
}
inline bool operator==(pos a) {
if (a.x==x && a.y== y)
return true;
else
return false;
}
} pos;
I also wanted to know the difference between this:
inline bool operator==(pos a) {
if(a.x==x && a.y== y)
return true;
else
return false;
}
And this:
bool operator==(pos a) const {
if(a.x==x && a.y== y)
return true;
else
return false;
}
The breakdown of your declaration and its members is somewhat littered:
Remove the typedef
The typedef is neither required, not desired for class/struct declarations in C++. Your members have no knowledge of the declaration of pos as-written, which is core to your current compilation failure.
Change this:
typedef struct {....} pos;
To this:
struct pos { ... };
Remove extraneous inlines
You're both declaring and defining your member operators within the class definition itself. The inline keyword is not needed so long as your implementations remain in their current location (the class definition)
Return references to *this where appropriate
This is related to an abundance of copy-constructions within your implementation that should not be done without a strong reason for doing so. It is related to the expression ideology of the following:
a = b = c;
This assigns c to b, and the resulting value b is then assigned to a. This is not equivalent to the following code, contrary to what you may think:
a = c;
b = c;
Therefore, your assignment operator should be implemented as such:
pos& operator =(const pos& a)
{
x = a.x;
y = a.y;
return *this;
}
Even here, this is not needed. The default copy-assignment operator will do the above for you free of charge (and code! woot!)
Note: there are times where the above should be avoided in favor of the copy/swap idiom. Though not needed for this specific case, it may look like this:
pos& operator=(pos a) // by-value param invokes class copy-ctor
{
this->swap(a);
return *this;
}
Then a swap method is implemented:
void pos::swap(pos& obj)
{
// TODO: swap object guts with obj
}
You do this to utilize the class copy-ctor to make a copy, then utilize exception-safe swapping to perform the exchange. The result is the incoming copy departs (and destroys) your object's old guts, while your object assumes ownership of there's. Read more the copy/swap idiom here, along with the pros and cons therein.
Pass objects by const reference when appropriate
All of your input parameters to all of your members are currently making copies of whatever is being passed at invoke. While it may be trivial for code like this, it can be very expensive for larger object types. An exampleis given here:
Change this:
bool operator==(pos a) const{
if(a.x==x && a.y== y)return true;
else return false;
}
To this: (also simplified)
bool operator==(const pos& a) const
{
return (x == a.x && y == a.y);
}
No copies of anything are made, resulting in more efficient code.
Finally, in answering your question, what is the difference between a member function or operator declared as const and one that is not?
A const member declares that invoking that member will not modifying the underlying object (mutable declarations not withstanding). Only const member functions can be invoked against const objects, or const references and pointers. For example, your operator +() does not modify your local object and thus should be declared as const. Your operator =() clearly modifies the local object, and therefore the operator should not be const.
Summary
struct pos
{
int x;
int y;
// default + parameterized constructor
pos(int x=0, int y=0)
: x(x), y(y)
{
}
// assignment operator modifies object, therefore non-const
pos& operator=(const pos& a)
{
x=a.x;
y=a.y;
return *this;
}
// addop. doesn't modify object. therefore const.
pos operator+(const pos& a) const
{
return pos(a.x+x, a.y+y);
}
// equality comparison. doesn't modify object. therefore const.
bool operator==(const pos& a) const
{
return (x == a.x && y == a.y);
}
};
EDIT OP wanted to see how an assignment operator chain works. The following demonstrates how this:
a = b = c;
Is equivalent to this:
b = c;
a = b;
And that this does not always equate to this:
a = c;
b = c;
Sample code:
#include <iostream>
#include <string>
using namespace std;
struct obj
{
std::string name;
int value;
obj(const std::string& name, int value)
: name(name), value(value)
{
}
obj& operator =(const obj& o)
{
cout << name << " = " << o.name << endl;
value = (o.value+1); // note: our value is one more than the rhs.
return *this;
}
};
int main(int argc, char *argv[])
{
obj a("a", 1), b("b", 2), c("c", 3);
a = b = c;
cout << "a.value = " << a.value << endl;
cout << "b.value = " << b.value << endl;
cout << "c.value = " << c.value << endl;
a = c;
b = c;
cout << "a.value = " << a.value << endl;
cout << "b.value = " << b.value << endl;
cout << "c.value = " << c.value << endl;
return 0;
}
Output
b = c
a = b
a.value = 5
b.value = 4
c.value = 3
a = c
b = c
a.value = 4
b.value = 4
c.value = 3
Instead of typedef struct { ... } pos; you should be doing struct pos { ... };. The issue here is that you are using the pos type name before it is defined. By moving the name to the top of the struct definition, you are able to use that name within the struct definition itself.
Further, the typedef struct { ... } name; pattern is a C-ism, and doesn't have much place in C++.
To answer your question about inline, there is no difference in this case. When a method is defined within the struct/class definition, it is implicitly declared inline. When you explicitly specify inline, the compiler effectively ignores it because the method is already declared inline.
(inline methods will not trigger a linker error if the same method is defined in multiple object files; the linker will simply ignore all but one of them, assuming that they are all the same implementation. This is the only guaranteed change in behavior with inline methods. Nowadays, they do not affect the compiler's decision regarding whether or not to inline functions; they simply facilitate making the function implementation available in all translation units, which gives the compiler the option to inline the function, if it decides it would be beneficial to do so.)
try this:
struct Pos{
int x;
int y;
inline Pos& operator=(const Pos& other){
x=other.x;
y=other.y;
return *this;
}
inline Pos operator+(const Pos& other) const {
Pos res {x+other.x,y+other.y};
return res;
}
const inline bool operator==(const Pos& other) const {
return (x==other.x and y == other.y);
}
};
bool operator==(pos a) const{ - this method doesn't change object's elements.
bool operator==(pos a) { - it may change object's elements.