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

TP for operators done. override keyword forgotten in object programming...

TP for operators done. override keyword forgotten in object programming solution has been added back.
parent b932f1fd
......@@ -14,11 +14,10 @@ macro(add_cxx_compiler_flag _flag)
endmacro()
# Don't do such hardcoding in a real project: you want to be able to supersede these settings on command line.
set(CMAKE_CXX_COMPILER clang++ )
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_COMPILER clang++ CACHE STRING "C++ compiler")
set(CMAKE_C_COMPILER clang CACHE STRING "C compiler")
set(CMAKE_CXX_STANDARD 17 CACHE INTEGER "Version of the C++ standard to use")
set(CMAKE_CXX_EXTENSIONS OFF CACHE BOOL "If ON the GNU version of the standard is used.")
project(GettingStartedWithModernCpp_TP_Procedural)
......
......@@ -14,11 +14,10 @@ macro(add_cxx_compiler_flag _flag)
endmacro()
# Don't do such hardcoding in a real project: you want to be able to supersede these settings on command line.
set(CMAKE_CXX_COMPILER clang++ )
set(CMAKE_C_COMPILER clang)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_CXX_COMPILER clang++ CACHE STRING "C++ compiler")
set(CMAKE_C_COMPILER clang CACHE STRING "C compiler")
set(CMAKE_CXX_STANDARD 17 CACHE INTEGER "Version of the C++ standard to use")
set(CMAKE_CXX_EXTENSIONS OFF CACHE BOOL "If ON the GNU version of the standard is used.")
project(GettingStartedWithModernCpp_TP_Object)
......
......@@ -148,7 +148,7 @@ public:
//! Print the output for the values 0.65 and 0.35.
void Do(int Nbits) const;
void Do(int Nbits) const override;
private:
......
......@@ -148,7 +148,7 @@ public:
//! Print the output for the values 0.65 and 0.35.
void Do(int Nbits) const;
void Do(int Nbits) const override;
private:
......
......@@ -148,7 +148,7 @@ public:
//! Print the output for the values 0.65 and 0.35.
void Do(int Nbits) const;
void Do(int Nbits) const override;
private:
......
......@@ -148,7 +148,7 @@ public:
//! Print the output for the values 0.65 and 0.35.
void Do(int Nbits) const;
void Do(int Nbits) const override;
private:
......
......@@ -148,7 +148,7 @@ public:
//! Print the output for the values 0.65 and 0.35.
void Do(int Nbits) const;
void Do(int Nbits) const override;
private:
......
......@@ -148,7 +148,7 @@ public:
//! Print the output for the values 0.65 and 0.35.
void Do(int Nbits) const;
void Do(int Nbits) const override;
private:
......
......@@ -148,7 +148,7 @@ public:
//! Print the output for the values 0.65 and 0.35.
void Do(int Nbits) const;
void Do(int Nbits) const override;
private:
......
......@@ -12,10 +12,13 @@
"metadata": {},
"source": [
"* [Introduction to the concept of operator overload](/notebooks/3-Operators/1-Intro.ipynb)\n",
" * [TP 8](/notebooks/3-Operators/1b-TP.ipynb)\n",
"* [Comparison operators](/notebooks/3-Operators/2-Comparison.ipynb)\n",
"* [Stream operators](/notebooks/3-Operators/3-Stream.ipynb)\n",
" * [TP 9](/notebooks/3-Operators/3b-TP.ipynb)\n",
"* [Affectation operator and the canonical form of a class](/notebooks/3-Operators/4-CanonicalForm.ipynb)\n",
"* [Functors](/notebooks/3-Operators/5-Functors.ipynb)"
"* [Functors](/notebooks/3-Operators/5-Functors.ipynb)\n",
" * [TP 10](/notebooks/3-Operators/5b-TP.ipynb)"
]
},
{
......
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Operators](/notebooks/3-Operators/0-main.ipynb)
%% Cell type:markdown id: tags:
* [Introduction to the concept of operator overload](/notebooks/3-Operators/1-Intro.ipynb)
* [TP 8](/notebooks/3-Operators/1b-TP.ipynb)
* [Comparison operators](/notebooks/3-Operators/2-Comparison.ipynb)
* [Stream operators](/notebooks/3-Operators/3-Stream.ipynb)
* [TP 9](/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 10](/notebooks/3-Operators/5b-TP.ipynb)
%% Cell type:markdown id: tags:
© _CNRS 2016_ - _Inria 2018_
_This notebook is an adaptation of a lecture prepared and redacted 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 redacted by Sébastien Gilles and Vincent Rouvreau (Inria)_
......
......@@ -14,7 +14,7 @@
},
"source": [
"<h1>Table of contents<span class=\"tocSkip\"></span></h1>\n",
"<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=\"#Limitations\" data-toc-modified-id=\"Limitations-3\">Limitations</a></span></li><li><span><a href=\"#Conversion-operators\" data-toc-modified-id=\"Conversion-operators-4\">Conversion operators</a></span></li></ul></div>"
"<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>"
]
},
{
......@@ -311,6 +311,259 @@
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Operator between different types\n",
"\n",
"It is also possible to define an operator which acts upon two objects of different nature:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"class Vector4\n",
"{\n",
" public :\n",
"\n",
" Vector4(double x, double y, double z);\n",
" \n",
" Vector4() = default;\n",
" \n",
" void Print() const;\n",
" \n",
" Vector4 operator+(double value) const\n",
" {\n",
" Vector4 ret;\n",
" ret.x_ = x_ + value;\n",
" ret.y_ = y_ + value;\n",
" ret.z_ = z_ + value;\n",
" \n",
" return ret;\n",
" }\n",
"\n",
" private :\n",
" \n",
" double x_ = 0.;\n",
" double y_ = 0.;\n",
" double z_ = 0.;\n",
"}; "
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"#include <iostream>\n",
"\n",
"void Vector4::Print() const\n",
"{\n",
" std::cout << \"(\" << x_ << \", \" << y_ << \", \" << z_ << \")\" << std::endl;\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[1minput_line_12:1:10: \u001b[0m\u001b[0;1;31merror: \u001b[0m\u001b[1mredefinition of 'Vector4'\u001b[0m\n",
"Vector4::Vector4(double x, double y, double z)\n",
"\u001b[0;1;32m ^\n",
"\u001b[0m\u001b[1minput_line_9:1:10: \u001b[0m\u001b[0;1;30mnote: \u001b[0mprevious definition is here\u001b[0m\n",
"Vector4::Vector4(double x, double y, double z)\n",
"\u001b[0;1;32m ^\n",
"\u001b[0m"
]
},
{
"ename": "Interpreter Error",
"evalue": "",
"output_type": "error",
"traceback": [
"Interpreter Error: "
]
}
],
"source": [
"Vector4::Vector4(double x, double y, double z)\n",
": x_(x),\n",
"y_(y),\n",
"z_(z)\n",
"{ }"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"(10, 8.2, 4)\n"
]
}
],
"source": [
"{\n",
" Vector4 vector(5., 3.2, -1.);\n",
" Vector4 vector_plus_5 = vector + 5.;\n",
" vector_plus_5.Print();\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"However, pay attention to the fact this operator is not commutative: when you call \n",
"\n",
"````\n",
"Vector4 vector_plus_5 = vector + 5.;\n",
"````\n",
"\n",
"it is indeed a shortcut to\n",
"\n",
"````\n",
"Vector4 vector_plus_5 = vector.operator*(5.);\n",
"````\n",
"\n",
"and the following won't compile:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"\u001b[1minput_line_14:4:32: \u001b[0m\u001b[0;1;31merror: \u001b[0m\u001b[1minvalid operands to binary expression ('double' and 'Vector4')\u001b[0m\n",
" Vector4 vector_plus_5 = 5. + vector; // COMPILATION ERROR!\n",
"\u001b[0;1;32m ~~ ^ ~~~~~~\n",
"\u001b[0m"
]
},
{
"ename": "Interpreter Error",
"evalue": "",
"output_type": "error",
"traceback": [
"Interpreter Error: "
]
}
],
"source": [
"{\n",
" Vector4 vector(5., 3.2, -1.);\n",
" Vector4 vector_plus_5 = 5. + vector; // COMPILATION ERROR!\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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",
"\n",
"Of course, if you do so you should define one in way of the other:\n",
"\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"// Won't compile in Xeus-cling\n",
"\n",
"#include <cstdlib>\n",
"#include <iostream>\n",
"\n",
"class Vector5\n",
"{\n",
" public :\n",
"\n",
" Vector5(double x, double y, double z);\n",
" \n",
" Vector5() = default;\n",
" \n",
" void Print() const;\n",
" \n",
" friend Vector5 operator+(const Vector5& v, double value);\n",
" \n",
" friend Vector5 operator+(double value, const Vector5& v);\n",
"\n",
" private :\n",
" \n",
" double x_ = 0.;\n",
" double y_ = 0.;\n",
" double z_ = 0.;\n",
"}; \n",
"\n",
"\n",
"\n",
"void Vector5::Print() const\n",
"{\n",
" std::cout << \"(\" << x_ << \", \" << y_ << \", \" << z_ << \")\" << std::endl;\n",
"}\n",
"\n",
"\n",
"Vector5::Vector5(double x, double y, double z)\n",
": x_(x),\n",
"y_(y),\n",
"z_(z)\n",
"{ }\n",
"\n",
"\n",
"\n",
"Vector5 operator+(const Vector5& v, double value)\n",
"{\n",
" Vector5 ret;\n",
" \n",
" ret.x_ = v.x_ + value;\n",
" ret.y_ = v.y_ + value;\n",
" ret.z_ = v.z_ + value;\n",
" \n",
" return ret;\n",
"}\n",
"\n",
"\n",
"Vector5 operator+(double value, const Vector5& v)\n",
"{\n",
" return v + value;\n",
"}\n",
"\n",
"\n",
"int main(int argc, char** argv)\n",
"{\n",
" Vector5 vector(5., 3.2, -1.);\n",
" Vector5 vector_plus_5 = vector + 5.;\n",
" Vector5 vector_plus_5_commutated = 5. + vector;\n",
" \n",
" vector_plus_5.Print();\n",
" vector_plus_5_commutated.Print();\n",
" \n",
" return EXIT_SUCCESS;\n",
"}\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
......
%% Cell type:markdown id: tags:
# [Getting started in C++](/) - [Operators](/notebooks/3-Operators/0-main.ipynb) - [Introduction](/notebooks/3-Operators/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="#Limitations" data-toc-modified-id="Limitations-3">Limitations</a></span></li><li><span><a href="#Conversion-operators" data-toc-modified-id="Conversion-operators-4">Conversion operators</a></span></li></ul></div>
<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
{
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 wuth some care...
We will see the general wao 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();
}
```
%%%% Output: stream
(10, 10, 10)
%% 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;
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)
{ }
```
%%%% Output: stream
input_line_12:1:10: error: redefinition of 'Vector4'
Vector4::Vector4(double x, double y, double z)
 ^
input_line_9:1:10: note: previous definition is here
Vector4::Vector4(double x, double y, double z)
 ^

%%%% Output: error
Interpreter Error:
%% Cell type:code id: tags:
``` C++17
{
Vector4 vector(5., 3.2, -1.);
Vector4 vector_plus_5 = vector + 5.;
vector_plus_5.Print();
}
```
%%%% Output: stream
(10, 8.2, 4)
%% 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!
}
```
%%%% Output: stream
input_line_14:4:32: error: invalid operands to binary expression ('double' and 'Vector4')
Vector4 vector_plus_5 = 5. + vector; // COMPILATION ERROR!
 ~~ ^ ~~~~~~

%%%% Output: error
Interpreter 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.;