"Defining `<` operator does not automatically defines the others, but all may be defined once `operator<` and `operator!=` are defined:\n",
"Defining `operator<` does not automatically defines the others, but a **good practice** is to define the other comparison operators once `operator<` and `operator!=` are defined, and in function of these two as follows:\n",
"\n",
"\n",
"* `operator>(lhs, rhs)` is `operator<(rhs, lhs)`\n",
"* `operator>(lhs, rhs)` is `operator<(rhs, lhs)`\n",
"* `operator>=(lhs, rhs)` is `!operator<(lhs, rhs)`\n",
"* `operator>=(lhs, rhs)` is `!operator<(lhs, rhs)`\n",
...
@@ -100,7 +100,7 @@
...
@@ -100,7 +100,7 @@
"\n",
"\n",
"* None is defined by default.\n",
"* None is defined by default.\n",
"* They are independant from each other: defining one **doesn't** define the other one...\n",
"* They are independant from each other: defining one **doesn't** define the other one...\n",
"* ... but **never** define `operator!=` as something other than `!operator==`!\n",
"* ... but **never** define `operator!=` as something other than `!(operator==)`\n",
"* As we've seen above, they are not involved at all in implicit definitions of `<=` or `>=` if only `<` or `>` is explicitly defined. Same remark as the line above though!\n",
"* As we've seen above, they are not involved at all in implicit definitions of `<=` or `>=` if only `<` or `>` is explicitly defined. Same remark as the line above though!\n",
"* Make sure you're thought well the result of your comparison:\n"
"* Make sure you're thought well the result of your comparison:\n"
]
]
...
@@ -284,7 +284,7 @@
...
@@ -284,7 +284,7 @@
"cell_type": "markdown",
"cell_type": "markdown",
"metadata": {},
"metadata": {},
"source": [
"source": [
"C++ 20 introduces the so-called spaceship operator, which enables defining more concisely all those operators:"
"C++ 20 introduces the so-called spaceship operator, which enables defining more concisely all those operators (the following code doesn't work in Xeus-cling; you may use [@Coliru](https://coliru.stacked-crooked.com/a/1cec8ddda8a1eece)):"
]
]
},
},
{
{
...
@@ -400,7 +400,7 @@
...
@@ -400,7 +400,7 @@
"source": [
"source": [
"This is rather cumbersome to type, but it is even dangerous: if at some point we extend the class and add `Nsaw_` for instance, we need not to forget to update the operator as well!\n",
"This is rather cumbersome to type, but it is even dangerous: if at some point we extend the class and add `Nsaw_` for instance, we need not to forget to update the operator as well!\n",
"\n",
"\n",
"C++ 20 will enable default behaviour for the comparison operators, so in this case you would be able to write instead ([@Coliru](https://coliru.stacked-crooked.com/a/11c22df77c7065fe)):"
"C++ 20 will enable default behaviour for the comparison operators, so in this case you would be able to write instead ([@Coliru](https://coliru.stacked-crooked.com/a/fa889df647c73a7f)):"
"[Examples](https://www.modernescpp.com/index.php/c-20-more-details-to-the-spaceship-operator) on [the](https://devblogs.microsoft.com/cppblog/simplify-your-code-with-rocket-science-c20s-spaceship-operator/) Web often show the defaulting of a comparison used directly upon the spaceship operator, which is something I will have to investigate when I upgrade to C++ 20 as it seems at first sight rather dangerous to me (see the `Rational` example, where even the default `operator==` is unlikely to be what we want)."
"As you can experiment, the `operator<` will compare data attributes in order of their declaration as if it was implemented this way:"
"[This example](https://www.modernescpp.com/index.php/c-20-more-details-to-the-spaceship-operator), or [that example](https://devblogs.microsoft.com/cppblog/simplify-your-code-with-rocket-science-c20s-spaceship-operator/) show other defaulting of a comparison used directly upon the spaceship operator.\n",
"\n",
"**Be aware** the default implementation may not be what you expected (see the `Rational` example, where even the default `operator==` is unlikely to be what we want)."
]
]
},
},
{
{
...
...
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Comparison operators](./2-Comparison.ipynb)
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Comparison operators](./2-Comparison.ipynb)
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
<h1>Table of contents<spanclass="tocSkip"></span></h1>
<h1>Table of contents<spanclass="tocSkip"></span></h1>
These 4 operators are **not** implicitly defined for a class (and that is very natural: what would be the rationale to devise the default `<` operator for a `ComplexNumber` or a `Car` class?)
These 4 operators are **not** implicitly defined for a class (and that is very natural: what would be the rationale to devise the default `<` operator for a `ComplexNumber` or a `Car` class?)
Let's take again our `Rational` class to illustrate this:
Let's take again our `Rational` class to illustrate this:
**Xeus-cling** issue: cling doesn't accept operator definition outside of class; please use [@Coliru](https://coliru.stacked-crooked.com/a/6f0dc54ac63f476c):
**Xeus-cling** issue: cling doesn't accept operator definition outside of class; please use [@Coliru](https://coliru.stacked-crooked.com/a/6f0dc54ac63f476c):
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` C++17
``` C++17
// Xeus-cling issue: doesn't compile!
// Xeus-cling issue: doesn't compile!
#include <iostream>
#include <iostream>
class Rational
class Rational
{
{
public :
public :
explicit Rational(int numerator, int denominator);
explicit Rational(int numerator, int denominator);
explicit operator double() const;
explicit operator double() const;
private :
private :
int numerator_ = 0;
int numerator_ = 0;
int denominator_ = 0;
int denominator_ = 0;
};
};
Rational::Rational(int numerator, int denominator)
Rational::Rational(int numerator, int denominator)
Defining `<`operator does not automatically defines the others, but all may be defined once `operator<` and `operator!=` are defined:
Defining `operator<` does not automatically defines the others, but a**good practice** is to define the other comparison operators once `operator<` and `operator!=` are defined, and in function of these two as follows:
*`operator>(lhs, rhs)` is `operator<(rhs, lhs)`
*`operator>(lhs, rhs)` is `operator<(rhs, lhs)`
*`operator>=(lhs, rhs)` is `!operator<(lhs, rhs)`
*`operator>=(lhs, rhs)` is `!operator<(lhs, rhs)`
*`operator<=(lhs, rhs)` is `!operator>(lhs, rhs)`
*`operator<=(lhs, rhs)` is `!operator>(lhs, rhs)`
As always with operators overloading, make sure your implementation is consistent: it's not because you might define an operator `<` which is not the negation of `>=` that you should do it!
As always with operators overloading, make sure your implementation is consistent: it's not because you might define an operator `<` which is not the negation of `>=` that you should do it!
## Operator == and !=
## Operator == and !=
* None is defined by default.
* None is defined by default.
* They are independant from each other: defining one **doesn't** define the other one...
* They are independant from each other: defining one **doesn't** define the other one...
* ... but **never** define `operator!=` as something other than `!operator==`!
* ... but **never** define `operator!=` as something other than `!(operator==)`
* As we've seen above, they are not involved at all in implicit definitions of `<=` or `>=` if only `<` or `>` is explicitly defined. Same remark as the line above though!
* As we've seen above, they are not involved at all in implicit definitions of `<=` or `>=` if only `<` or `>` is explicitly defined. Same remark as the line above though!
* Make sure you're thought well the result of your comparison:
* Make sure you're thought well the result of your comparison:
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` C++17
``` C++17
class Vector
class Vector
{
{
public:
public:
Vector(int x, int y, int z);
Vector(int x, int y, int z);
~Vector();
~Vector();
// Working around the Xeus-cling bug but avoid direct definition in class declaration...
// Working around the Xeus-cling bug but avoid direct definition in class declaration...
// As mentioned previously free functions should be preferred, but still the Xeus-cling bug.
// As mentioned previously free functions should be preferred, but still the Xeus-cling bug.
The issue here is that we compare the `array_` pointers of `v1` and `v2`; the content might be the same but the address in memory is clearly not! So make sure in overloading these operators you are really comparing what you think you are comparing (and remember unit tests are your friends for this kind of checks!).
The issue here is that we compare the `array_` pointers of `v1` and `v2`; the content might be the same but the address in memory is clearly not! So make sure in overloading these operators you are really comparing what you think you are comparing (and remember unit tests are your friends for this kind of checks!).
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
## C++ 20 refinements
## C++ 20 refinements
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
### Spaceship operator
### Spaceship operator
Currently (in C++ 17 and below) you have to define all the comparison operators, which can quickly become rather tedious (the following code doesn't work in Xeus-cling; you may use [@Coliru](https://coliru.stacked-crooked.com/a/122ec1b6a6ac3d0f):
Currently (in C++ 17 and below) you have to define all the comparison operators, which can quickly become rather tedious (the following code doesn't work in Xeus-cling; you may use [@Coliru](https://coliru.stacked-crooked.com/a/122ec1b6a6ac3d0f):
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` C++17
``` C++17
// Xeus-cling issue: doesn't compile!
// Xeus-cling issue: doesn't compile!
#include <iostream>
#include <iostream>
#include <cmath>
#include <cmath>
class Rational
class Rational
{
{
public :
public :
explicit Rational(int numerator, int denominator);
explicit Rational(int numerator, int denominator);
explicit operator double() const;
explicit operator double() const;
private :
private :
int numerator_ = 0;
int numerator_ = 0;
int denominator_ = 0;
int denominator_ = 0;
};
};
Rational::Rational(int numerator, int denominator)
Rational::Rational(int numerator, int denominator)
std::cout << std::boolalpha << "a < b ? -> " << (a < b) << std::endl;
std::cout << std::boolalpha << "a < b ? -> " << (a < b) << std::endl;
std::cout << std::boolalpha << "a > b ? -> " << (a > b) << std::endl;
std::cout << std::boolalpha << "a > b ? -> " << (a > b) << std::endl;
std::cout << std::boolalpha << "a <= b ? -> " << (a <= b) << std::endl;
std::cout << std::boolalpha << "a <= b ? -> " << (a <= b) << std::endl;
std::cout << std::boolalpha << "a >= b ? -> " << (a >= b) << std::endl;
std::cout << std::boolalpha << "a >= b ? -> " << (a >= b) << std::endl;
std::cout << std::boolalpha << "a == b ? -> " << (a == b) << std::endl;
std::cout << std::boolalpha << "a == b ? -> " << (a == b) << std::endl;
std::cout << std::boolalpha << "a != b ? -> " << (a != b) << std::endl;
std::cout << std::boolalpha << "a != b ? -> " << (a != b) << std::endl;
return EXIT_SUCCESS;
return EXIT_SUCCESS;
}
}
```
```
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
C++ 20 introduces the so-called spaceship operator, which enables defining more concisely all those operators:
C++ 20 introduces the so-called spaceship operator, which enables defining more concisely all those operators (the following code doesn't work in Xeus-cling; you may use [@Coliru](https://coliru.stacked-crooked.com/a/1cec8ddda8a1eece)):
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` C++17
``` C++17
// Xeus-cling issue: doesn't compile! (and C++ 20 not yet supported there anyway)
// Xeus-cling issue: doesn't compile! (and C++ 20 not yet supported there anyway)
#include <iostream>
#include <iostream>
#include <cmath>
#include <cmath>
class Rational20
class Rational20
{
{
public :
public :
explicit Rational20(int numerator, int denominator);
explicit Rational20(int numerator, int denominator);
explicit operator double() const;
explicit operator double() const;
private :
private :
int numerator_ = 0;
int numerator_ = 0;
int denominator_ = 0;
int denominator_ = 0;
};
};
Rational20::Rational20(int numerator, int denominator)
Rational20::Rational20(int numerator, int denominator)
std::cout << std::boolalpha << "a < b ? -> " << (a < b) << std::endl;
std::cout << std::boolalpha << "a < b ? -> " << (a < b) << std::endl;
std::cout << std::boolalpha << "a > b ? -> " << (a > b) << std::endl;
std::cout << std::boolalpha << "a > b ? -> " << (a > b) << std::endl;
std::cout << std::boolalpha << "a <= b ? -> " << (a <= b) << std::endl;
std::cout << std::boolalpha << "a <= b ? -> " << (a <= b) << std::endl;
std::cout << std::boolalpha << "a >= b ? -> " << (a >= b) << std::endl;
std::cout << std::boolalpha << "a >= b ? -> " << (a >= b) << std::endl;
std::cout << std::boolalpha << "a == b ? -> " << (a == b) << std::endl;
std::cout << std::boolalpha << "a == b ? -> " << (a == b) << std::endl;
std::cout << std::boolalpha << "a != b ? -> " << (a != b) << std::endl;
std::cout << std::boolalpha << "a != b ? -> " << (a != b) << std::endl;
return EXIT_SUCCESS;
return EXIT_SUCCESS;
}
}
```
```
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
### Default behaviour
### Default behaviour
In C++ 17 and below, you also can't define a default behaviour for the comparison operators for your class.
In C++ 17 and below, you also can't define a default behaviour for the comparison operators for your class.
Sometimes it really is appropriate - for the case of `Rational` the default behaviour would probably be something inappropriate, but in other cases this lack of default behaviour may be irksome when you for instance want to check all data attributes are equal:
Sometimes it really is appropriate - for the case of `Rational` the default behaviour would probably be something inappropriate, but in other cases this lack of default behaviour may be irksome when you for instance want to check all data attributes are equal:
This is rather cumbersome to type, but it is even dangerous: if at some point we extend the class and add `Nsaw_` for instance, we need not to forget to update the operator as well!
This is rather cumbersome to type, but it is even dangerous: if at some point we extend the class and add `Nsaw_` for instance, we need not to forget to update the operator as well!
C++ 20 will enable default behaviour for the comparison operators, so in this case you would be able to write instead ([@Coliru](https://coliru.stacked-crooked.com/a/11c22df77c7065fe)):
C++ 20 will enable default behaviour for the comparison operators, so in this case you would be able to write instead ([@Coliru](https://coliru.stacked-crooked.com/a/fa889df647c73a7f)):
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` C++17
``` C++17
#include <iostream>
#include <iostream>
struct Toolbox
struct Toolbox
{
{
bool operator==(const Toolbox&) const = default;
bool operator==(const Toolbox&) const = default;
// < apparently only works with the internal declaration for operator==
[Examples](https://www.modernescpp.com/index.php/c-20-more-details-to-the-spaceship-operator) on [the](https://devblogs.microsoft.com/cppblog/simplify-your-code-with-rocket-science-c20s-spaceship-operator/) Web often show the defaulting of a comparison used directly upon the spaceship operator, which is something I will have to investigate when I upgrade to C++ 20 as it seems at first sight rather dangerous to me (see the `Rational` example, where even the default `operator==` is unlikely to be what we want).
As you can experiment, the `operator<` will compare data attributes in order of their declaration as if it was implemented this way:
[This example](https://www.modernescpp.com/index.php/c-20-more-details-to-the-spaceship-operator), or [that example](https://devblogs.microsoft.com/cppblog/simplify-your-code-with-rocket-science-c20s-spaceship-operator/) show other defaulting of a comparison used directly upon the spaceship operator.
**Be aware** the default implementation may not be what you expected (see the `Rational` example, where even the default `operator==` is unlikely to be what we want).
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_This notebook is an adaptation of a lecture prepared by David Chamont (CNRS) under the terms of the licence [Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0)](http://creativecommons.org/licenses/by-nc-sa/4.0/)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_
_The present version has been written by Sébastien Gilles and Vincent Rouvreau (Inria)_