Mentions légales du service

Skip to content
Snippets Groups Projects
Commit a3811818 authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Minor changes... that includes a bug in our example (wrong type used).

parent 254715c2
No related branches found
No related tags found
1 merge request!43Modifications done while reading again the formation
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Comparison operators](./2-Comparison.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#Operator-<,->,-<=,->=" data-toc-modified-id="Operator-<,->,-<=,->=-1">Operator &lt;, &gt;, &lt;=, &gt;=</a></span></li><li><span><a href="#Operator-==-and-!=" data-toc-modified-id="Operator-==-and-!=-2">Operator == and !=</a></span></li><li><span><a href="#C++-20-refinements" data-toc-modified-id="C++-20-refinements-3">C++ 20 refinements</a></span><ul class="toc-item"><li><span><a href="#Spaceship-operator" data-toc-modified-id="Spaceship-operator-3.1">Spaceship operator</a></span></li><li><span><a href="#Default-behaviour" data-toc-modified-id="Default-behaviour-3.2">Default behaviour</a></span></li></ul></li></ul></div>
%% Cell type:markdown id: tags:
## Operator <, >, <=, >=
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:
**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:
``` C++17
// Xeus-cling issue: doesn't compile!
#include <iostream>
class Rational
{
public :
explicit Rational(int numerator, int denominator);
explicit operator double() const;
private :
int numerator_ = 0;
int denominator_ = 0;
};
Rational::Rational(int numerator, int denominator)
: numerator_(numerator),
denominator_(denominator)
{ }
Rational::operator double() const
{
return static_cast<double>(numerator_) / denominator_;
}
bool operator<(const Rational& lhs, const Rational& rhs)
{
return static_cast<double>(lhs) < static_cast<double>(rhs);
}
int main(int argc, char** argv)
{
Rational r1(15, 7);
Rational r2(27, 4);
std::cout << (r1 < r2) << std::endl;
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
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<(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!
## Operator == and !=
* None is defined by default.
* They are independant from each other: defining one **doesn't** define the other one...
* ... 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!
* Make sure you're thought well the result of your comparison:
%% Cell type:code id: tags:
``` C++17
class Vector
{
public:
Vector(int x, int y, int z);
~Vector();
// 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.
bool operator==(const Vector& rhs) const
{
return array_ == rhs.array_; // BUG!
}
private:
int* array_ = nullptr;
};
```
%% Cell type:code id: tags:
``` C++17
Vector::Vector(int x, int y, int z)
{
array_ = new int[3];
array_[0] = x;
array_[1] = y;
array_[2] = z;
}
```
%% Cell type:code id: tags:
``` C++17
Vector::~Vector()
{
delete[] array_;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Vector v1(3, 4, 5);
Vector v2(3, 4, 5);
std::cout << "Are equal? : " << (v1 == v2) << std::endl;
}
```
%% Cell type:markdown id: tags:
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:
## C++ 20 refinements
%% Cell type:markdown id: tags:
### 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)):
%% Cell type:code id: tags:
``` C++17
// Xeus-cling issue: doesn't compile!
#include <iostream>
#include <cmath>
class Rational
{
public :
explicit Rational(int numerator, int denominator);
explicit operator double() const;
private :
int numerator_ = 0;
int denominator_ = 0;
};
Rational::Rational(int numerator, int denominator)
: numerator_(numerator),
denominator_(denominator)
{ }
Rational::operator double() const
{
return static_cast<double>(numerator_) / static_cast<double>(denominator_);
}
bool operator<(const Rational& lhs, const Rational& rhs)
{
return static_cast<double>(lhs) < static_cast<double>(rhs);
}
bool operator==(const Rational& lhs, const Rational& rhs)
{
return std::fabs(static_cast<double>(lhs) - static_cast<double>(rhs) ) < 1.e-16; // crude...
}
bool operator!=(const Rational& lhs, const Rational& rhs)
{
return !operator==(lhs, rhs);
}
bool operator>(const Rational& lhs, const Rational& rhs)
{
return operator<(rhs, lhs);
}
bool operator>=(const Rational& lhs, const Rational& rhs)
{
return !operator<(lhs, rhs);
}
bool operator<=(const Rational& lhs, const Rational& rhs)
{
return !operator>(lhs, rhs);
}
int main()
{
Rational a(5, 2);
Rational b(17, 5);
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;
}
```
%% Cell type:markdown id: tags:
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:
``` C++17
// Xeus-cling issue: doesn't compile! (and C++ 20 not yet supported there anyway)
#include <iostream>
#include <cmath>
class Rational20
{
public :
explicit Rational20(int numerator, int denominator);
explicit operator double() const;
private :
int numerator_ = 0;
int denominator_ = 0;
};
Rational20::Rational20(int numerator, int denominator)
: numerator_(numerator),
denominator_(denominator)
{ }
Rational20::operator double() const
{
return static_cast<double>(numerator_) / static_cast<double>(denominator_);
}
bool operator==(const Rational20& lhs, const Rational20& rhs)
{
return std::fabs(static_cast<double>(lhs) - static_cast<double>(rhs)) < 1.e-16; // crude...
}
std::partial_ordering operator<=>(const Rational20& lhs, const Rational20& rhs)
{
return static_cast<double>(lhs) <=> static_cast<double>(rhs);
}
int main()
{
Rational20 a(5, 2);
Rational20 b(17, 5);
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;
}
```
%% Cell type:markdown id: tags:
### Default behaviour
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:
%% Cell type:code id: tags:
``` C++17
struct Toolbox
{
unsigned int Nscrewdriver_;
unsigned int Nhammer_;
unsigned int Nnails_;
};
```
%% Cell type:code id: tags:
``` C++17
// Xeus-cling issue: doesn't compile!
bool operator==(const Toolbox& lhs, const Toolbox& rhs)
{
return lhs.Nscrewdriver_ == rhs.Nscrewdriver_
&& lhs.Nhammer_ == rhs.Nhammer_
&& lhs.Nails_ == rhs.Nnails_;
}
```
%% Cell type:markdown id: tags:
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/fa889df647c73a7f)):
%% Cell type:code id: tags:
``` C++17
#include <iostream>
struct Toolbox
{
bool operator==(const Toolbox&) const = default;
auto operator<=>(const Toolbox&) const = default;
unsigned int Nscrewdriver_;
unsigned int Nhammer_;
unsigned int Nnails_;
};
int main(int argc, char** argv)
{
Toolbox toolbox1;
toolbox1.Nscrewdriver_ = 5;
toolbox1.Nhammer_ = 4;
toolbox1.Nnails_ = 200;
Toolbox toolbox2;
toolbox2.Nscrewdriver_ = 5;
toolbox2.Nhammer_ = 4;
toolbox2.Nnails_ = 200;
std::cout << std::boolalpha << (toolbox1 == toolbox2) << std::endl;
std::cout << std::boolalpha << (toolbox1 < toolbox2) << std::endl;
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
As you can experiment, the `operator<` will compare data attributes in order of their declaration as if it was implemented this way:
%% Cell type:code id: tags:
``` C++17
// What the operator< would look like if we wrote it ourselves:
bool operator<(const Rational& lhs, const Rational& rhs)
{
if !(toolbox1.Nscrewdriver_ == toolbox2.Nscrewdriver_)
return toolbox1.Nscrewdriver_ < toolbox2.Nscrewdriver_;
if !(toolbox1.Nhammer_ == toolbox2.Nhammer_)
return toolbox1.Nhammer_ < toolbox2.Nhammer_;
if !(toolbox1.Nnails_ == toolbox2.Nnails_)
return toolbox1.Nnails_ < toolbox2.Nnails_;
return false;
}
```
%% Cell type:markdown id: tags:
[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).
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2022_
_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)_
......
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Functors](./5-Functors.ipynb)
%% Cell type:markdown id: tags:
<h1>Table of contents<span class="tocSkip"></span></h1>
<div class="toc"><ul class="toc-item"><li><span><a href="#What-is-a-functor?" data-toc-modified-id="What-is-a-functor?-1">What is a functor?</a></span></li><li><span><a href="#Functors-in-STL" data-toc-modified-id="Functors-in-STL-2">Functors in STL</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## What is a functor?
We can provide a class with an `operator()` that allows to give its objects a function behavior. In other words: if you write an expression in which an object is used as if it were a function, it is its execution operator that is invoked.
We can also see these *functional objects*, or *functors*, as functions to which we would have added state parameters from one call to another.
The return type and the arguments are not constrained and might be defined as you wish in the class (`int` is used here for return type, and an `int` is required as argument):
%% Cell type:code id: tags:
``` C++17
class LinearFunction
{
public :
LinearFunction(int constant);
int operator()(int value)
{
return constant_ * value; // here due to usual Xeus-cling issue when out of class
// definition is used for operators
}
private :
int constant_ = 0.;
} ;
```
%% Cell type:code id: tags:
``` C++17
LinearFunction::LinearFunction(int constant)
: constant_(constant)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
int values[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
LinearFunction Double(2);
LinearFunction Triple(3);
for (double value : values)
for (int value : values)
std::cout << value << "\t" << Double(value) << "\t" << Triple(value) << std::endl;
}
```
%% Cell type:markdown id: tags:
This might seem inconsequential, but they are sometimes extremely useful.
## Functors in STL
STL itself defines some functors: imagine you want to sort a `std::vector` decreasingly; you can't directly put `>` as the comparison operation in `sort` and therefore may use a specific STL-defined `greater`:
%% Cell type:code id: tags:
``` C++17
#include <algorithm>
#include <iostream>
#include <vector>
{
std::vector<int> values { -980, 12, 2987, -8323, 4, 275, -8936, 27, -988, 92784 };
std::sort(values.begin(), values.end(), std::greater<int>());
for (auto value : values)
std::cout << value << " ";
}
```
%% Cell type:markdown id: tags:
C++ 11 and above limits the need for functors with lambda functions (see [earlier](../1-ProceduralProgramming/4-Functions.ipynb#Lambda-functions)), but in older codes, functors were one of the way to pass your own rules to some STL algorithms, along with pointer to functions.
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2022_
_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)_
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment