Commit 3e380de7 authored by GILLES Sebastien's avatar GILLES Sebastien
Browse files

Cleaning-up operators part.

parent fb1b3e73
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Operators](/notebooks/3-Operators/0-main.ipynb)
# [Getting started in C++](/) - [Operators](./0-main.ipynb)
%% Cell type:markdown id: tags:
* [Introduction to the concept of operator overload](/notebooks/3-Operators/1-Intro.ipynb)
* [TP 9](/notebooks/3-Operators/1b-TP.ipynb)
* [Comparison operators](/notebooks/3-Operators/2-Comparison.ipynb)
* [Stream operators](/notebooks/3-Operators/3-Stream.ipynb)
* [TP 10](/notebooks/3-Operators/3b-TP.ipynb)
* [Affectation operator and the canonical form of a class](/notebooks/3-Operators/4-CanonicalForm.ipynb)
* [Functors](/notebooks/3-Operators/5-Functors.ipynb)
* [TP 11](/notebooks/3-Operators/5b-TP.ipynb)
* [Introduction to the concept of operator overload](./1-Intro.ipynb)
* [TP 9](./1b-TP.ipynb)
* [Comparison operators](./2-Comparison.ipynb)
* [Stream operators](./3-Stream.ipynb)
* [TP 10](./3b-TP.ipynb)
* [Affectation operator and the canonical form of a class](./4-CanonicalForm.ipynb)
* [Functors](./5-Functors.ipynb)
* [TP 11](./5b-TP.ipynb)
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_
© _CNRS 2016_ - _Inria 2018-2020_
_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](/notebooks/3-Operators/0-main.ipynb) - [Introduction](/notebooks/3-Operators/1-Intro.ipynb)
# [Getting started in C++](/) - [Operators](./0-main.ipynb) - [Introduction](./1-Intro.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="#Motivation" data-toc-modified-id="Motivation-1">Motivation</a></span></li><li><span><a href="#Overloading-an-operator" data-toc-modified-id="Overloading-an-operator-2">Overloading an operator</a></span></li><li><span><a href="#Operator-between-different-types" data-toc-modified-id="Operator-between-different-types-3">Operator between different types</a></span></li><li><span><a href="#Limitations" data-toc-modified-id="Limitations-4">Limitations</a></span></li><li><span><a href="#Conversion-operators" data-toc-modified-id="Conversion-operators-5">Conversion operators</a></span></li></ul></div>
%% Cell type:markdown id: tags:
## Motivation
We've seen at length in the object programming part that classes are basically new types defined by the developer. However sometimes we would like to use exactly the same syntax as for the base type. Let's see for instance a basic class to handle tri-dimensional vectors:
%% Cell type:code id: tags:
``` C++17
class Vector
{
// Friendship because `Add()` needs to access private members and no accessors were defined.
friend Vector Add(const Vector& v1, const Vector& v2);
public :
Vector(double x, double y, double z);
Vector() = default;
void Print() const;
private :
double x_ = 0.;
double y_ = 0.;
double z_ = 0.;
};
```
%% Cell type:code id: tags:
``` C++17
Vector::Vector(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
Vector Add(const Vector& v1, const Vector& v2)
{
Vector ret;
ret.x_ = v1.x_ + v2.x_;
ret.y_ = v1.y_ + v2.y_;
ret.z_ = v1.z_ + v2.z_;
return ret;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Vector::Print() const
{
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
Vector v1(3., 5., 7.);
Vector v2(7., 5., 3.);
Vector v3 = Add(v1, v2);
v3.Print();
}
```
%% Cell type:markdown id: tags:
Now the same with a _plain old data type_ is much more natural to write with no (apparent) method:
%% Cell type:code id: tags:
``` C++17
{
double x1 = 3.;
double x2 = 7.;
double x3 = x1 + x2;
std::cout << x3 << std::endl;
}
```
%% Cell type:markdown id: tags:
C++ provides the way to mimic this behaviour with **operator overloading**. This is a very powerful conceit, but also one that should be approached with some care...
We will see the general way to define such an operator in this notebook and see in dedicated notebooks which are the ones specifically useful.
## Overloading an operator
To overload an operator, the syntax is just the keyword **operator** followed by the operator to overload.
%% Cell type:code id: tags:
``` C++17
class Vector2
{
public :
Vector2(double x, double y, double z);
Vector2() = default;
void Print() const;
// I would rather put the definition outside but Xeus-cling doesn't seem to accept this.
Vector2 operator+(const Vector2& v) const
{
Vector2 ret;
ret.x_ = x_ + v.x_;
ret.y_ = y_ + v.y_;
ret.z_ = z_ + v.z_;
return ret;
}
private :
double x_ = 0.;
double y_ = 0.;
double z_ = 0.;
};
```
%% Cell type:code id: tags:
``` C++17
Vector2::Vector2(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Vector2::Print() const
{
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
{
Vector2 v1(3., 5., 7.);
Vector2 v2(7., 5., 3.);
Vector2 v3 = v1 + v2;
v3.Print();
}
```
%% Cell type:markdown id: tags:
We see in the definition of the operator+ that both `Vector2` added aren't symmetric: one is the data attribute while the other is the data attribute of an object given as an argument.
As a side note, please remark the `operator+` implementation is able to reach the private data attributes of the argument `v`; this means the private status is set **at class level** and not at object level.
It is actually possible to define the operator as a free function, thus providing a more symmetric implementation:
**Xeus-cling issue**: cling doesn't accept operator definition outside of class; please use [@Coliru](https://coliru.stacked-crooked.com/a/626efa4fb6a02915):
%% Cell type:code id: tags:
``` C++17
// Xeus-cling issue: doesn't compile!
#include <iostream>
class Vector3
{
public :
Vector3(double x, double y, double z);
Vector3() = default;
void Print() const;
friend Vector3 operator+(const Vector3& v1, const Vector3& v2);
private :
double x_ = 0.;
double y_ = 0.;
double z_ = 0.;
};
Vector3::Vector3(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
void Vector3::Print() const
{
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
}
Vector3 operator+(const Vector3& v1, const Vector3& v2)
{
// Provides a symmetric implementation of operator +: both vectors are at the same level!
Vector3 ret;
ret.x_ = v1.x_ + v2.x_;
ret.y_ = v1.y_ + v2.y_;
ret.z_ = v1.z_ + v2.z_;
return ret;
}
int main(int argc, char** argv)
{
Vector3 v1(3., 5., 7.);
Vector3 v2(7., 5., 3.);
Vector3 v3 = v1 + v2;
v3.Print();
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
## Operator between different types
It is also possible to define an operator which acts upon two objects of different nature:
%% Cell type:code id: tags:
``` C++17
class Vector4
{
public :
Vector4(double x, double y, double z);
Vector4() = default;
void Print() const;
// Defined in the class declaration due to Xeus-cling limitation.
Vector4 operator+(double value) const
{
Vector4 ret;
ret.x_ = x_ + value;
ret.y_ = y_ + value;
ret.z_ = z_ + value;
return ret;
}
private :
double x_ = 0.;
double y_ = 0.;
double z_ = 0.;
};
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
void Vector4::Print() const
{
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
}
```
%% Cell type:code id: tags:
``` C++17
Vector4::Vector4(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
```
%% Cell type:code id: tags:
``` C++17
{
Vector4 vector(5., 3.2, -1.);
Vector4 vector_plus_5 = vector + 5.;
vector_plus_5.Print();
}
```
%% Cell type:markdown id: tags:
However, pay attention to the fact this operator is not commutative: when you call
````
Vector4 vector_plus_5 = vector + 5.;
````
it is indeed a shortcut to
````
Vector4 vector_plus_5 = vector.operator+(5.);
````
and the following won't compile:
%% Cell type:code id: tags:
``` C++17
{
Vector4 vector(5., 3.2, -1.);
Vector4 vector_plus_5 = 5. + vector; // COMPILATION ERROR!
}
```
%% Cell type:markdown id: tags:
If you want it to be possible, you have to define the operator with arguments in both orders; you therefore need to use out-of-class prototype of the function (can't show it currently due to Xeus-cling limitation).
Of course, if you do so you should define one in way of the other:
%% Cell type:code id: tags:
``` C++17
// Won't compile in Xeus-cling
#include <cstdlib>
#include <iostream>
class Vector5
{
public :
Vector5(double x, double y, double z);
Vector5() = default;
void Print() const;
friend Vector5 operator+(const Vector5& v, double value);
friend Vector5 operator+(double value, const Vector5& v);
private :
double x_ = 0.;
double y_ = 0.;
double z_ = 0.;
};
void Vector5::Print() const
{
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
}
Vector5::Vector5(double x, double y, double z)
: x_(x),
y_(y),
z_(z)
{ }
Vector5 operator+(const Vector5& v, double value)
{
Vector5 ret;
ret.x_ = v.x_ + value;
ret.y_ = v.y_ + value;
ret.z_ = v.z_ + value;
return ret;
}
Vector5 operator+(double value, const Vector5& v)
{
return v + value;
}
int main(int argc, char** argv)
{
Vector5 vector(5., 3.2, -1.);
Vector5 vector_plus_5 = vector + 5.;
Vector5 vector_plus_5_commutated = 5. + vector;
vector_plus_5.Print();
vector_plus_5_commutated.Print();
return EXIT_SUCCESS;
}
```
%% Cell type:markdown id: tags:
## Limitations
You cannot change:
* The number of operators arguments
* The precedence rules (between `+` and `*` for instance)
You can't _invent_ new operators, but only redefine operators in the following list (that might be incomplete: I learnt about `""` operator very recently on [this blog post](https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/)):
````
+ - * / % ^ & | ~ !
= < > += -= *= /= %= ^= &=
|= << >> >>= <<= == != <= >= &&
|| ++ -- ->* , -> [] () new delete
""
````
(plus **conversion operators** - see next section).
If not defined, some of them exist by default:
````
=
. -> .* ->*
new delete sizeof
-> ->*
new delete
````
Some can never be redefined:
````
: :: . .* ? ?: sizeof
````
## Conversion operators
A conversion operator is a method of transforming an object into a given type. When the compiler needs to force the type of an object, implicitly or explicitly, it is this operator that will be called. A conversion operator is required for each type of conversion.
%% Cell type:code id: tags:
``` C++17
#include <iostream>
class Rational
{
public :
Rational(int numerator, int denominator);
operator int() const;
operator double() const;
private :
int numerator_ = 0;
int denominator_ = 0;
};
```
%% Cell type:code id: tags:
``` C++17
Rational::Rational(int numerator, int denominator)
: numerator_(numerator),
denominator_(denominator)
{ }
```
%% Cell type:code id: tags:
``` C++17
Rational::operator int() const
{
return numerator_ / denominator_;
}
```
%% Cell type:code id: tags:
``` C++17
Rational::operator double() const
{
return static_cast<double>(numerator_) / denominator_;
}
```
%% Cell type:code id: tags:
``` C++17
#include <iostream>
{
Rational val_r(15, 7);
double val_d {val_r};
int val_i{val_r};
std::cout << "val as double: " << val_d << std::endl ;
std::cout << "val as integer: " << val_i << std::endl ;
}
```
%% Cell type:markdown id: tags:
As for constructors, you might add the keyword `explicit` in the class declaration to ensure no implicit conversion occurs:
````
explicit operator int() const;
explicit operator double() const;
````
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018-2019_
© _CNRS 2016_ - _Inria 2018-2020_
_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](/notebooks/3-Operators/0-main.ipynb) - [Comparison operators](/notebooks/3-Operators/2-Comparison.ipynb)
# [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></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;
}
```