"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...\n",
"C++ provides the way to mimic this behaviour with **operator overloading**. This is a very powerful concept, but also one that should be approached with some care...\n",
"\n",
"\n",
"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.\n",
"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.\n",
"\n",
"\n",
...
@@ -248,7 +248,7 @@
...
@@ -248,7 +248,7 @@
"cell_type": "markdown",
"cell_type": "markdown",
"metadata": {},
"metadata": {},
"source": [
"source": [
"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.\n",
"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.\n",
"\n",
"\n",
"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.\n",
"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.\n",
"\n",
"\n",
...
@@ -413,18 +413,25 @@
...
@@ -413,18 +413,25 @@
"cell_type": "markdown",
"cell_type": "markdown",
"metadata": {},
"metadata": {},
"source": [
"source": [
"However, pay attention to the fact this operator is not commutative: when you call \n",
"However, pay attention to the fact this operator is not commutative. It is indeed a shortcut to"
"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).\n",
"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, available [@Coliru](https://coliru.stacked-crooked.com/a/bb8130aec13bdf26)).\n",
"\n",
"\n",
"Of course, if you do so you should define one in way of the other:\n",
"Of course, it is a **good practice** to define one in way of the other:"
"\n"
]
]
},
},
{
{
...
@@ -658,12 +664,17 @@
...
@@ -658,12 +664,17 @@
"cell_type": "markdown",
"cell_type": "markdown",
"metadata": {},
"metadata": {},
"source": [
"source": [
"As for constructors, you might add the keyword `explicit` in the class declaration to ensure no implicit conversion occurs:\n",
"As for constructors, you might add the keyword `explicit` in the class declaration to ensure no implicit conversion occurs:"
"\n",
]
"````\n",
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"explicit operator int() const;\n",
"explicit operator int() const;\n",
"explicit operator double() const;\n",
"explicit operator double() const;"
"````"
]
]
},
},
{
{
...
...
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Introduction](./1-Intro.ipynb)
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Introduction](./1-Intro.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>
<divclass="toc"><ulclass="toc-item"><li><span><ahref="#Motivation"data-toc-modified-id="Motivation-1">Motivation</a></span></li><li><span><ahref="#Overloading-an-operator"data-toc-modified-id="Overloading-an-operator-2">Overloading an operator</a></span></li><li><span><ahref="#Operator-between-different-types"data-toc-modified-id="Operator-between-different-types-3">Operator between different types</a></span></li><li><span><ahref="#Limitations"data-toc-modified-id="Limitations-4">Limitations</a></span></li><li><span><ahref="#Conversion-operators"data-toc-modified-id="Conversion-operators-5">Conversion operators</a></span></li></ul></div>
<divclass="toc"><ulclass="toc-item"><li><span><ahref="#Motivation"data-toc-modified-id="Motivation-1">Motivation</a></span></li><li><span><ahref="#Overloading-an-operator"data-toc-modified-id="Overloading-an-operator-2">Overloading an operator</a></span></li><li><span><ahref="#Operator-between-different-types"data-toc-modified-id="Operator-between-different-types-3">Operator between different types</a></span></li><li><span><ahref="#Limitations"data-toc-modified-id="Limitations-4">Limitations</a></span></li><li><span><ahref="#Conversion-operators"data-toc-modified-id="Conversion-operators-5">Conversion operators</a></span></li></ul></div>
%% Cell type:markdown id: tags:
%% Cell type:markdown id: tags:
## Motivation
## 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:
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:
%% Cell type:code id: tags:
``` C++17
``` C++17
class Vector
class Vector
{
{
// Friendship because `Add()` needs to access private members and no accessors were defined.
// Friendship because `Add()` needs to access private members and no accessors were defined.
Now the same with a _plain old data type_ is much more natural to write with no (apparent) method:
Now the same with a _plain old data type_ is much more natural to write with no (apparent) method:
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` C++17
``` C++17
{
{
double x1 = 3.;
double x1 = 3.;
double x2 = 7.;
double x2 = 7.;
double x3 = x1 + x2;
double x3 = x1 + x2;
std::cout << x3 << std::endl;
std::cout << x3 << std::endl;
}
}
```
```
%% Cell type:markdown id: tags:
%% 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...
C++ provides the way to mimic this behaviour with **operator overloading**. This is a very powerful concept, 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.
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
## Overloading an operator
To overload an operator, the syntax is just the keyword **operator** followed by the operator to overload.
To overload an operator, the syntax is just the keyword **operator** followed by the operator to overload.
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` C++17
``` C++17
class Vector2
class Vector2
{
{
public :
public :
Vector2(double x, double y, double z);
Vector2(double x, double y, double z);
Vector2() = default;
Vector2() = default;
void Print() const;
void Print() const;
// I would rather put the definition outside but Xeus-cling doesn't seem to accept this.
// I would rather put the definition outside but Xeus-cling doesn't seem to accept this.
Vector2 operator+(const Vector2& v) const
Vector2 operator+(const Vector2& v) const
{
{
Vector2 ret;
Vector2 ret;
ret.x_ = x_ + v.x_;
ret.x_ = x_ + v.x_;
ret.y_ = y_ + v.y_;
ret.y_ = y_ + v.y_;
ret.z_ = z_ + v.z_;
ret.z_ = z_ + v.z_;
return ret;
return ret;
}
}
private :
private :
double x_ = 0.;
double x_ = 0.;
double y_ = 0.;
double y_ = 0.;
double z_ = 0.;
double z_ = 0.;
};
};
```
```
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` C++17
``` C++17
// How the definition outside the class would look like: nothing new or surprising here...
// How the definition outside the class would look like: nothing new or surprising here...
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.
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.
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:
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):
**Xeus-cling issue**: cling doesn't accept operator definition outside of class; please use [@Coliru](https://coliru.stacked-crooked.com/a/626efa4fb6a02915):
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).
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, available [@Coliru](https://coliru.stacked-crooked.com/a/bb8130aec13bdf26)).
Of course, if you do so you should define one in way of the other:
Of course, it is a **good practice** to define one in way of the other:
* The precedence rules (between `+` and `*` for instance)
* 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/)):
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
|| ++ -- ->* , -> [] () new delete
""
""
````
````
(plus **conversion operators** - see next section).
(plus **conversion operators** - see next section).
If not defined, some of them exist by default:
If not defined, some of them exist by default:
````
````
=
=
-> ->*
-> ->*
new delete
new delete
````
````
Some can never be redefined:
Some can never be redefined:
````
````
: :: . .* ? ?: sizeof
: :: . .* ? ?: sizeof
````
````
## Conversion operators
## 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.
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:
%% Cell type:code id: tags:
``` C++17
``` C++17
#include <iostream>
#include <iostream>
class Rational
class Rational
{
{
public :
public :
Rational(int numerator, int denominator);
Rational(int numerator, int denominator);
operator int() const;
operator int() const;
operator double() const;
operator double() const;
private :
private :
int numerator_ = 0;
int numerator_ = 0;
int denominator_ = 0;
int denominator_ = 0;
};
};
```
```
%% Cell type:code id: tags:
%% Cell type:code id: tags:
``` C++17
``` C++17
Rational::Rational(int numerator, int denominator)
Rational::Rational(int numerator, int denominator)
_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)_