Mentions légales du service

Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • formations/cpp/gettingstartedwithmoderncpp
  • sbenamor/gettingstartedwithmoderncpp
  • steff/gettingstartedwithmoderncpp
  • sgilles/gettingstartedwithmoderncpp
  • vrouvrea/gettingstartedwithmoderncpp
  • fvergnet/gettingstartedwithmoderncpp
  • jediaz/gettingstartedwithmoderncpp
  • mmalanda/gettingstartedwithmoderncpp
  • bnguyenv/gettingstartedwithmoderncpp
9 results
Select Git revision
Show changes
Showing
with 790 additions and 614 deletions
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Encapsulation](./4-encapsulation.ipynb) # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Encapsulation](./4-encapsulation.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Public and private ## Public and private
We've now played quite a bit with objects now, but we are still using `struct` and not `class` keyword. So it's time to try to define a class: We've now played quite a bit with objects now, but we are still using `struct` and not `class` keyword. So it's time to try to define a class:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class FirstClass class FirstClass
{ {
FirstClass(); FirstClass();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
FirstClass::FirstClass() FirstClass::FirstClass()
{ {
std::cout << "Hello world!" << std::endl; std::cout << "Hello world!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
FirstClass object; // COMPILATION ERROR! FirstClass object; // COMPILATION ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The compiler informs you that a **private** constructor was called. The compiler informs you that a **private** constructor was called.
In fact, in C++ you we may define the level of access of attributes with three levels (we'll let the third slip until the [chapter about inheritance](./6-inheritance.ipynb#Protected-status)): In fact, in C++ you we may define the level of access of attributes with three levels (we'll let the third slip until the [chapter about inheritance](./6-inheritance.ipynb#Protected-status)):
* **private** means only the member functions of the class are entitled to access it (and [friends](./4-encapsulation.ipynb#Friendship) as we shall see later in this notebook) * **private** means only the member functions of the class are entitled to access it (and [friends](./4-encapsulation.ipynb#Friendship) as we shall see later in this notebook)
* **public** means everyone may access it. * **public** means everyone may access it.
To determine which one is used, `public:` and `private:` may be added in a class declaration: To determine which one is used, `public:` and `private:` may be added in a class declaration:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class SecondClass class SecondClass
{ {
public: public:
SecondClass(int a); SecondClass(int a);
private: private:
int a_ { -9999999 }; // stupid default value. int a_ { -9999999 }; // stupid default value.
void SetValue(int a); void SetValue(int a);
public: public:
void Print() const; void Print() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
SecondClass::SecondClass(int a) SecondClass::SecondClass(int a)
{ {
SetValue(a); // Ok: a member function (here the constructor) may call private method SetValue(a); // Ok: a member function (here the constructor) may call private method
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void SecondClass::Print() const void SecondClass::Print() const
{ {
std::cout << "The value is " << a_ << std::endl; // Ok: a member function may use private data attribute. std::cout << "The value is " << a_ << std::endl; // Ok: a member function may use private data attribute.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void SecondClass::SetValue(int a) void SecondClass::SetValue(int a)
{ {
a_ = a; // Ok: a member function may use private data attribute. a_ = a; // Ok: a member function may use private data attribute.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
SecondClass object(5); SecondClass object(5);
object.Print(); // Ok: public method object.Print(); // Ok: public method
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
SecondClass object(5); SecondClass object(5);
object.SetValue(7); // COMPILATION ERROR: trying to call publicly a private method object.SetValue(7); // COMPILATION ERROR: trying to call publicly a private method
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As you may see on our example, there might be as many public and private sections as you wish, and their ordering doesn't matter (often coding standards recommend such an ordering, saying for instance to put public interface first, but the language itself does not care in the least). As you may see on our example, there might be as many public and private sections as you wish, and their ordering doesn't matter (often coding standards recommend such an ordering, saying for instance to put public interface first, but the language itself does not care in the least).
One side note for those accustomed to other languages: C++ is really hell bent about privacy status. It is not a gentleman's agreement as in Python where the `_` prefix is an indication an attribute should not be used publicly but a user may supersede the choice anyway; in C++ you can't call directly a private method of a class One side note for those accustomed to other languages: C++ is really hell bent about privacy status. It is not a gentleman's agreement as in Python where the `_` prefix is an indication an attribute should not be used publicly but a user may supersede the choice anyway; in C++ you can't call directly a private method of a class
without modifying the class interface yourself - which is ill-advised, especially if we're talking about code from a third-party library. without modifying the class interface yourself - which is ill-advised, especially if we're talking about code from a third-party library.
## Struct and class ## Struct and class
The difference in a (C++) `struct` and a `class` is in fact thin-veiled: The difference in a (C++) `struct` and a `class` is in fact thin-veiled:
* A `struct` assumes implicitly that attributes are public if nothing is specified. * A `struct` assumes implicitly that attributes are public if nothing is specified.
* A `class` assumes implicitly that attributes are private if nothing is specified. * A `class` assumes implicitly that attributes are private if nothing is specified.
I would advise personally to use classes and specify explicitly the sections (as we shall see very soon it is advised to get at least some private parts) but you now understand why we stick with `struct` in the former chapters: it allowed not to meddle with public/private concerns. I would advise personally to use classes and specify explicitly the sections (as we shall see very soon it is advised to get at least some private parts) but you now understand why we stick with `struct` in the former chapters: it allowed not to meddle with public/private concerns.
## Why encapsulation? ## Why encapsulation?
So far we've described the mechanism, but not provided much insight on why such a hurdle was introduced in the first place. Let's see a concrete example in which the benefits of encapsulation appear clearly: So far we've described the mechanism, but not provided much insight on why such a hurdle was introduced in the first place. Let's see a concrete example in which the benefits of encapsulation appear clearly:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct Rectangle struct Rectangle
{ {
Rectangle(double length, double width); Rectangle(double length, double width);
double length_; double length_;
double width_; double width_;
double area_; double area_;
void Print() const; void Print() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Rectangle::Rectangle(double length, double width) Rectangle::Rectangle(double length, double width)
: length_(length), : length_(length),
width_(width), width_(width),
area_(length * width) area_(length * width)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void Rectangle::Print() const void Rectangle::Print() const
{ {
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Rectangle rect(5., 4.); Rectangle rect(5., 4.);
rect.Print(); // OK rect.Print(); // OK
rect.length_ = 23.; rect.length_ = 23.;
rect.Print(); // Not so much... rect.Print(); // Not so much...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Encapsulation may protect from that: Encapsulation may protect from that:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class MoreSecureRectangle class MoreSecureRectangle
{ {
public: public:
MoreSecureRectangle(double length, double width); MoreSecureRectangle(double length, double width);
void Print() const; void Print() const;
private: private:
double length_; double length_;
double width_; double width_;
double area_; double area_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
MoreSecureRectangle::MoreSecureRectangle(double length, double width) MoreSecureRectangle::MoreSecureRectangle(double length, double width)
: length_(length), : length_(length),
width_(width), width_(width),
area_(length * width) area_(length * width)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void MoreSecureRectangle::Print() const void MoreSecureRectangle::Print() const
{ {
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
MoreSecureRectangle rect(5., 4.); MoreSecureRectangle rect(5., 4.);
rect.Print(); // OK rect.Print(); // OK
rect.length_ = 0.; // can't do that! rect.length_ = 0.; // can't do that!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Of course, we have lost functionality here... If we want to add the functionality to change the values, we need more member functions: Of course, we have lost functionality here... If we want to add the functionality to change the values, we need more member functions:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class Rectangle3 class Rectangle3
{ {
public: public:
Rectangle3(double length, double widgth); Rectangle3(double length, double widgth);
void Print() const; void Print() const;
void SetLength(double x); void SetLength(double x);
void SetWidth(double x); void SetWidth(double x);
private: private:
double length_; double length_;
double width_; double width_;
double area_; double area_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Rectangle3::Rectangle3(double length, double width) Rectangle3::Rectangle3(double length, double width)
: length_(length), : length_(length),
width_(width), width_(width),
area_(length * width) area_(length * width)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void Rectangle3::Print() const void Rectangle3::Print() const
{ {
std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl; std::cout << "My rectangle is " << length_ << " x " << width_ << " so its area is " << area_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Rectangle3::SetLength(double x) void Rectangle3::SetLength(double x)
{ {
length_ = x; length_ = x;
area_ = length_ * width_; area_ = length_ * width_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Rectangle3::SetWidth(double x) void Rectangle3::SetWidth(double x)
{ {
width_ = x; width_ = x;
area_ = length_ * width_; area_ = length_ * width_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Rectangle3 rect(5., 4.); Rectangle3 rect(5., 4.);
rect.Print(); // OK rect.Print(); // OK
rect.SetLength(23.); rect.SetLength(23.);
rect.Print(); // OK rect.Print(); // OK
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It should be noticed the class above is safer, but not very good nonetheless: It should be noticed the class above is safer, but not very good nonetheless:
* The computation of the area is written in three different places: constructor, `SetLength()` and `SetWidth()`... It would be better to get a private `ComputeArea()` method that does this task (granted here for this example it might be overkill, but in real cases the operation might be much more than a mere multiplication...) * The computation of the area is written in three different places: constructor, `SetLength()` and `SetWidth()`... It would be better to get a private `ComputeArea()` method that does this task (granted here for this example it might be overkill, but in real cases the operation might be much more than a mere multiplication...)
* The idea to store the `area_` is not that right here: a public method `GetArea()` or `ComputeArea()` or whatever you want to call it would be preferable in this case. Of course in a more complex problem it might not: it depends whether the computation is costly or not (here: not) and how often you need to use the value in average before recomputing it. * The idea to store the `area_` is not that right here: a public method `GetArea()` or `ComputeArea()` or whatever you want to call it would be preferable in this case. Of course in a more complex problem it might not: it depends whether the computation is costly or not (here: not) and how often you need to use the value in average before recomputing it.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Good practice: data attributes should be private ## Good practice: data attributes should be private
In the example above, we saw that the publicly data attribute could lead to an inconsistent state within the object. In the example above, we saw that the publicly data attribute could lead to an inconsistent state within the object.
It is often advised (see for instance item 22 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) to make all data attributes private, with dedicated methods to access and eventually modify those data. This approach enables fine-tuning of access: you may define whether a given **accessor** or **mutator** should be public or private. It is often advised (see for instance item 22 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) to make all data attributes private, with dedicated methods to access and eventually modify those data. This approach enables fine-tuning of access: you may define whether a given **accessor** or **mutator** should be public or private.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class Rectangle4 class Rectangle4
{ {
public: public:
Rectangle4(double length, double width); Rectangle4(double length, double width);
// Mutators // Mutators
void SetLength(double x); void SetLength(double x);
void SetWidth(double x); void SetWidth(double x);
// Accessors // Accessors
double GetLength() const; double GetLength() const;
double GetWidth() const; double GetWidth() const;
private: private:
double length_ { -1.e20 }; // a stupid value which at least is deterministically known... double length_ { -1.e20 }; // a stupid value which at least is deterministically known...
double width_ { -1.e20 }; double width_ { -1.e20 };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Rectangle4:: Rectangle4(double length, double width) Rectangle4:: Rectangle4(double length, double width)
: length_(length), : length_(length),
width_(width) width_(width)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Rectangle4::SetLength(double x) void Rectangle4::SetLength(double x)
{ {
length_ = x; length_ = x;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Rectangle4::SetWidth(double x) void Rectangle4::SetWidth(double x)
{ {
width_ = x; width_ = x;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
double Rectangle4::GetLength() const double Rectangle4::GetLength() const
{ {
return length_; return length_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
double Rectangle4::GetWidth() const double Rectangle4::GetWidth() const
{ {
return width_; return width_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
double ComputeArea(const Rectangle4& r) // free function double ComputeArea(const Rectangle4& r) // free function
{ {
return r.GetLength() * r.GetWidth(); // ok: public methods! And no risk to inadvertably change the values here. return r.GetLength() * r.GetWidth(); // ok: public methods! And no risk to inadvertably change the values here.
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
Rectangle4 rect(4., 6.5); Rectangle4 rect(4., 6.5);
std::cout << "Area = " << ComputeArea(rect) << std::endl; std::cout << "Area = " << ComputeArea(rect) << std::endl;
rect.SetWidth(5.); rect.SetWidth(5.);
std::cout << "Area = " << ComputeArea(rect) << std::endl; std::cout << "Area = " << ComputeArea(rect) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Good practice: the default status of a method should be private and you should use mutators for quantities that might vary ## Good practice: the default status of a method should be private and you should use mutators for quantities that might vary
In our `Rectangle4` example, all mutators and accessors were public. It is clearly what is intended here so it's fine, but as a rule you should really put in the public area what is intended to be usable by an end-user of your class. Let's spin another variation of our `Rectangle` class to illustrate this: In our `Rectangle4` example, all mutators and accessors were public. It is clearly what is intended here so it's fine, but as a rule you should really put in the public area what is intended to be usable by an end-user of your class. Let's spin another variation of our `Rectangle` class to illustrate this:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class Rectangle5 class Rectangle5
{ {
public: public:
Rectangle5(double length, double width); Rectangle5(double length, double width);
// Mutators // Mutators
void SetLength(double x); void SetLength(double x);
void SetWidth(double x); void SetWidth(double x);
// Accessor // Accessor
double GetArea() const; // others accessors are not defined to limit steps to define the class double GetArea() const; // others accessors are not defined to limit steps to define the class
private: private:
// Mutator for the area // Mutator for the area
// Prototype is not bright (parameters are clearly intended to be the data attributes // Prototype is not bright (parameters are clearly intended to be the data attributes
// and a prototype with no parameters would be much wiser!) // and a prototype with no parameters would be much wiser!)
// but is useful to make my point below. // but is useful to make my point below.
void SetArea(double length, double width); void SetArea(double length, double width);
private: private:
double area_ { -1.e20 }; // a stupid value which at least is deterministic... double area_ { -1.e20 }; // a stupid value which at least is deterministic...
double length_ { -1.20 }; double length_ { -1.20 };
double width_ { -1.20 }; double width_ { -1.20 };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Rectangle5::SetArea(double length, double width) void Rectangle5::SetArea(double length, double width)
{ {
area_ = width * length; area_ = width * length;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Rectangle5:: Rectangle5(double length, double width) Rectangle5:: Rectangle5(double length, double width)
: length_(length), : length_(length),
width_(width) width_(width)
{ {
SetArea(width_, length_); SetArea(width_, length_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
double Rectangle5::GetArea() const double Rectangle5::GetArea() const
{ {
return area_; return area_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Rectangle5::SetLength(double x) void Rectangle5::SetLength(double x)
{ {
length_ = x; length_ = x;
SetArea(width_, length_); SetArea(width_, length_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Rectangle5::SetWidth(double x) void Rectangle5::SetWidth(double x)
{ {
width_ = x; width_ = x;
SetArea(width_, length_); SetArea(width_, length_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void Print5(const Rectangle5& r) void Print5(const Rectangle5& r)
{ {
std::cout << "Area is " << r.GetArea() << std::endl; std::cout << "Area is " << r.GetArea() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Rectangle5 rect(3., 4.); Rectangle5 rect(3., 4.);
Print5(rect); Print5(rect);
rect.SetLength(8.); rect.SetLength(8.);
Print5(rect); Print5(rect);
rect.SetWidth(2.); rect.SetWidth(2.);
Print5(rect); Print5(rect);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It's clear that `SetArea()` has no business being called publicly: this member function is introduced here to update the area each time a dimension has changed, but it is assumed to be called with very specific arguments (the data attributes). It's clear that `SetArea()` has no business being called publicly: this member function is introduced here to update the area each time a dimension has changed, but it is assumed to be called with very specific arguments (the data attributes).
A end-user of the class doesn't in fact even need to know there is such a method: for his purpose being able to change one dimension and to get the correct area is all that matters for them. A end-user of the class doesn't in fact even need to know there is such a method: for his purpose being able to change one dimension and to get the correct area is all that matters for them.
This rather dumb example illustrates the interest of a mutator: when `SetWidth()` or `SetLength()` are called, the value is assigned **and** another operation is also performed. If when extending a class you need another operations you didn't think of in the first place (imagine for instance you need for some reason to know how many times the length was modified) you have just to modify the code in one place: the definition of the mutator (plus defining a new data attribute to store this quantity and initializing it properly). If you didn't use a mutator, you would end-up search the code for all the locations the data attributes is modified and pray you didn't miss one... This rather dumb example illustrates the interest of a mutator: when `SetWidth()` or `SetLength()` are called, the value is assigned **and** another operation is also performed. If when extending a class you need another operations you didn't think of in the first place (imagine for instance you need for some reason to know how many times the length was modified) you have just to modify the code in one place: the definition of the mutator (plus defining a new data attribute to store this quantity and initializing it properly). If you didn't use a mutator, you would end-up search the code for all the locations the data attributes is modified and pray you didn't miss one...
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Friendship ## Friendship
Sometimes, you may have a need to open access to the private part of a class for a very specific other class or function. You may in this case use the keyword **friend** in the class declaration: Sometimes, you may have a need to open access to the private part of a class for a very specific other class or function. You may in this case use the keyword **friend** in the class declaration:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <limits> #include <limits>
class Vector class Vector
{ {
public: public:
Vector(double x, double y, double z); Vector(double x, double y, double z);
friend double Norm(const Vector&); friend double Norm(const Vector&);
friend class PrintVector; // In C++11 `class` became optional here. friend class PrintVector; // In C++11 `class` became optional here.
friend void Reset(Vector&); // inane design here, just to illustrate friendship grants write access as well - a method would be much more appropriate here! friend void Reset(Vector&); // inane design here, just to illustrate friendship grants write access as well - a method would be much more appropriate here!
private: private:
double x_ = std::numeric_limits<double>::min(); double x_ = std::numeric_limits<double>::min();
double y_ = std::numeric_limits<double>::min(); double y_ = std::numeric_limits<double>::min();
double z_ = std::numeric_limits<double>::min(); double z_ = std::numeric_limits<double>::min();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vector::Vector(double x, double y, double z) Vector::Vector(double x, double y, double z)
: x_(x), : x_(x),
y_(y), y_(y),
z_(z) z_(z)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <cmath> #include <cmath>
double Norm(const Vector& v) double Norm(const Vector& v)
{ {
return std::sqrt(v.x_ * v.x_ + v.y_ * v.y_ + v.z_ * v.z_); // OK! return std::sqrt(v.x_ * v.x_ + v.y_ * v.y_ + v.z_ * v.z_); // OK!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class PrintVector // not the cleverest class we could write... class PrintVector // not the cleverest class we could write...
{ {
public: public:
PrintVector(const Vector& v); PrintVector(const Vector& v);
void Print() const; void Print() const;
private: private:
const Vector& vector_; const Vector& vector_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
PrintVector::PrintVector(const Vector& v) PrintVector::PrintVector(const Vector& v)
: vector_(v) : vector_(v)
{} {}
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void PrintVector::Print() const void PrintVector::Print() const
{ {
std::cout << "Content of the vector is (" << vector_.x_ << ", " << vector_.y_ std::cout << "Content of the vector is (" << vector_.x_ << ", " << vector_.y_
<< ", " << vector_.z_ << ")." << std::endl; // OK because of friendship! << ", " << vector_.z_ << ")." << std::endl; // OK because of friendship!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vector v(2., 3., 5.); Vector v(2., 3., 5.);
PrintVector printer(v); PrintVector printer(v);
printer.Print(); printer.Print();
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Reset(Vector& vector) void Reset(Vector& vector)
{ {
vector.x_ = 0.; vector.x_ = 0.;
vector.y_ = 0.; vector.y_ = 0.;
vector.z_ = 0.; vector.z_ = 0.;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
printer.Print(); printer.Print();
Reset(v); Reset(v);
printer.Print(); printer.Print();
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Obviously, friendship should be used with parsimony... But it's not that much of a deal-breaker as it may seem: Obviously, friendship should be used with parsimony... But it's not that much of a deal-breaker as it may seem:
* The friendship must be defined in the class declaration. It means you can't use it to bypass a class encapsulation without modifying this code directly. * The friendship must be defined in the class declaration. It means you can't use it to bypass a class encapsulation without modifying this code directly.
* The friendship is granted to a very specific function or class, and this class must be known when the class is defined. So an ill-intentioned user can't use the function prototype to sneak into your class private parts (in fact to be completely honest we will see an exception to this statement later with [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration)). * The friendship is granted to a very specific function or class, and this class must be known when the class is defined. So an ill-intentioned user can't use the function prototype to sneak into your class private parts (in fact to be completely honest we will see an exception to this statement later with [forward declaration](../6-InRealEnvironment/2-FileStructure.ipynb#Forward-declaration)).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 7](./4b-hands-on.ipynb) # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 7](./4b-hands-on.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **EXERCISE 16: transform struct `PowerOfTwoApprox` into a class** ### **EXERCISE 16: transform struct `PowerOfTwoApprox` into a class**
Make `PowerOfTwoApprox` into a class, with proper encapsulation: Make `PowerOfTwoApprox` into a class, with proper encapsulation:
* Both data attributes should be made private. * Both data attributes should be made private.
* Constant accessors will therefore be needed (non-constant ones should not be required here) * Constant accessors will therefore be needed (non-constant ones should not be required here)
Expected output is the same as previously: Expected output is the same as previously:
``` ```
[With 2 bits]: 0.65 ~ 3 / 2^2 (0.75) [error = 15/100] [With 2 bits]: 0.65 ~ 3 / 2^2 (0.75) [error = 15/100]
[With 4 bits]: 0.65 ~ 10 / 2^4 (0.625) [error = 4/100] [With 4 bits]: 0.65 ~ 10 / 2^4 (0.625) [error = 4/100]
[With 6 bits]: 0.65 ~ 42 / 2^6 (0.65625) [error = 1/100] [With 6 bits]: 0.65 ~ 42 / 2^6 (0.65625) [error = 1/100]
[With 8 bits]: 0.65 ~ 166 / 2^8 (0.648438) [error = 0/100] [With 8 bits]: 0.65 ~ 166 / 2^8 (0.648438) [error = 0/100]
[With 2 bits]: 0.35 ~ 3 / 2^3 (0.375) [error = 7/100] [With 2 bits]: 0.35 ~ 3 / 2^3 (0.375) [error = 7/100]
[With 4 bits]: 0.35 ~ 11 / 2^5 (0.34375) [error = 2/100] [With 4 bits]: 0.35 ~ 11 / 2^5 (0.34375) [error = 2/100]
[With 6 bits]: 0.35 ~ 45 / 2^7 (0.351562) [error = 0/100] [With 6 bits]: 0.35 ~ 45 / 2^7 (0.351562) [error = 0/100]
[With 8 bits]: 0.35 ~ 179 / 2^9 (0.349609) [error = 0/100] [With 8 bits]: 0.35 ~ 179 / 2^9 (0.349609) [error = 0/100]
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000]
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000]
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000]
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000]
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000]
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000]
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000]
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **EXERCISE 17: transform `Multiply()` into a method `Multiply()` of `PowerOfTwoApprox`** ### **EXERCISE 17: transform `Multiply()` into a method `Multiply()` of `PowerOfTwoApprox`**
The method will take as argument only the integer coefficient. The method will take as argument only the integer coefficient.
`DisplaySumOfMultiply()` will of course need also some light rewriting to accommodate that change. `DisplaySumOfMultiply()` will of course need also some light rewriting to accommodate that change.
Expected output is the same as previously. Expected output is the same as previously.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **EXERCISE 18: transform `DisplayPowerOf2Approx()` into a class** ### **EXERCISE 18: transform `DisplayPowerOf2Approx()` into a class**
Create a class `TestDisplayPowerOfTwoApprox` which will be in charge of printing the display for the 0.65 and 0.35 values. Create a class `TestDisplayPowerOfTwoApprox` which will be in charge of printing the display for the 0.65 and 0.35 values.
Use two methods in this class: Use two methods in this class:
* A public method `Do()` which in its implementation will call the test for 0.65 and 0.35. This method will take the number of bits as argument. * A public method `Do()` which in its implementation will call the test for 0.65 and 0.35. This method will take the number of bits as argument.
* A private method `Display()` which will provide the display for a given double value (and will therefore be called twice in `Do()`: once for 0.65 and once for 0.35). * A private method `Display()` which will provide the display for a given double value (and will therefore be called twice in `Do()`: once for 0.65 and once for 0.35).
New main should look like: New main should look like:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
TestDisplayPowerOfTwoApprox test_display_approx; TestDisplayPowerOfTwoApprox test_display_approx;
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
test_display_approx.Do(nbits); test_display_approx.Do(nbits);
std::cout << std::endl; std::cout << std::endl;
for (int nbits = 1; nbits <= 8; ++nbits) for (int nbits = 1; nbits <= 8; ++nbits)
DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832); DisplaySumOfMultiply(nbits, 0.65, 3515, 0.35, 4832);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Output will be ordered differently: Output will be ordered differently:
``` ```
[With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100] [With 2 bits]: 0.65 ~ 0.75 (3/2^2) [error = 15/100]
[With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100] [With 2 bits]: 0.35 ~ 0.375 (3/2^3) [error = 7/100]
[With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100] [With 4 bits]: 0.65 ~ 0.625 (10/2^4) [error = 4/100]
[With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100] [With 4 bits]: 0.35 ~ 0.34375 (11/2^5) [error = 2/100]
[With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100] [With 6 bits]: 0.65 ~ 0.65625 (42/2^6) [error = 1/100]
[With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100] [With 6 bits]: 0.35 ~ 0.351562 (45/2^7) [error = 0/100]
[With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100] [With 8 bits]: 0.65 ~ 0.648438 (166/2^8) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100] [With 8 bits]: 0.35 ~ 0.349609 (179/2^9) [error = 0/100]
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000]
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000]
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000]
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000]
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000]
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000]
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000]
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **EXERCISE 19: transform `DisplaySumOfMultiply()` into a class** ### **EXERCISE 19: transform `DisplaySumOfMultiply()` into a class**
Likewise, create a class `TestDisplaySumOfMultiply` which will be in charge of printing the display for 0.65 * 3515 + 0.35 * 4832 with public method `Do(int nbits)` and private method `Display()` which will takes 5 arguments: Likewise, create a class `TestDisplaySumOfMultiply` which will be in charge of printing the display for 0.65 * 3515 + 0.35 * 4832 with public method `Do(int nbits)` and private method `Display()` which will takes 5 arguments:
* Number of bits. * Number of bits.
* The two floating point values. * The two floating point values.
* Their integer coefficient. * Their integer coefficient.
New `main()` is: New `main()` is:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
TestDisplayPowerOfTwoApprox test_display_approx; TestDisplayPowerOfTwoApprox test_display_approx;
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
test_display_approx.Do(nbits); test_display_approx.Do(nbits);
std::cout << std::endl; std::cout << std::endl;
TestDisplaySumOfMultiply test_display_sum_of_multiply; TestDisplaySumOfMultiply test_display_sum_of_multiply;
for (int nbits = 1; nbits <= 8; ++nbits) for (int nbits = 1; nbits <= 8; ++nbits)
test_display_sum_of_multiply.Do(nbits); test_display_sum_of_multiply.Do(nbits);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **EXERCISE 20: introduce common `PrintLine()` function for outputs** ### **EXERCISE 20: introduce common `PrintLine()` function for outputs**
Both `TestDisplay` classes are rather similar in the line in charge of printing content on standard output - so we would like to uniformize the implementation. Both `TestDisplay` classes are rather similar in the line in charge of printing content on standard output - so we would like to uniformize the implementation.
We will therefore introduce a `PrintLine()` function which will be in charge of printing the line for a given number of bits; there are dedicated arguments to enable the differences in current displays (for instance one round the floating point values and not the other). We know this function renege the sound principle of separating the functionalities, but please humor us for the moment... We will therefore introduce a `PrintLine()` function which will be in charge of printing the line for a given number of bits; there are dedicated arguments to enable the differences in current displays (for instance one round the floating point values and not the other). We know this function renege the sound principle of separating the functionalities, but please humor us for the moment...
```c++ ```c++
// Declarations // Declarations
//! Convenient enum used in \a PrintLine(). //! Convenient enum used in \a PrintLine().
enum class RoundToInteger { no, yes }; enum class RoundToInteger { no, yes };
/*! /*!
* \brief Print a line with information about error. * \brief Print a line with information about error.
* *
* \param[in] maximum_error_index The error is expressed as an integer over this quantity. * \param[in] maximum_error_index The error is expressed as an integer over this quantity.
* \param[in] optional_string1 String that might appear just after the "[With N bits]:". * \param[in] optional_string1 String that might appear just after the "[With N bits]:".
* \param[in] optional_string2 String that might appear just before the "[error = ...]". * \param[in] optional_string2 String that might appear just before the "[error = ...]".
* \param[in] do_round_to_integer If yes, the exact result is approximated as an integer. * \param[in] do_round_to_integer If yes, the exact result is approximated as an integer.
*/ */
void PrintLine(int Nbits, double exact, double approx, int maximum_error_index, void PrintLine(int Nbits, double exact, double approx, int maximum_error_index,
RoundToInteger do_round_to_integer, RoundToInteger do_round_to_integer,
std::string optional_string1 = "", std::string optional_string2 = ""); std::string optional_string1 = "", std::string optional_string2 = "");
// Definitions // Definitions
void PrintLine(int Nbits, double exact, double approx, int maximum_error_index, void PrintLine(int Nbits, double exact, double approx, int maximum_error_index,
RoundToInteger do_round_to_integer, RoundToInteger do_round_to_integer,
std::string optional_string1, std::string optional_string2) std::string optional_string1, std::string optional_string2)
{ {
int error = RoundAsInt(maximum_error_index * std::fabs(exact - approx) / exact); int error = RoundAsInt(maximum_error_index * std::fabs(exact - approx) / exact);
std::cout << "[With " << Nbits << " bits]: " << optional_string1 std::cout << "[With " << Nbits << " bits]: " << optional_string1
<< (do_round_to_integer == RoundToInteger::yes ? RoundAsInt(exact) : exact) << " ~ " << approx << (do_round_to_integer == RoundToInteger::yes ? RoundAsInt(exact) : exact) << " ~ " << approx
<< optional_string2 << optional_string2
<< " [error = " << error << "/" << maximum_error_index << "]" << " [error = " << error << "/" << maximum_error_index << "]"
<< std::endl; << std::endl;
} }
``` ```
Copy this function in your exercise and use it in both `Display()` methods. Output should remain unchanged. Copy this function in your exercise and use it in both `Display()` methods. Output should remain unchanged.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Static attributes](./5-static.ipynb) # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Static attributes](./5-static.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Static in C ## Static in C
As a reminder, we have seen in a [previous notebook](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb#Static-keyword) the `static` keyword inherited from C. As a reminder, we have seen in a [previous notebook](../1-ProceduralProgramming/7-StaticAndConstexpr.ipynb#Static-keyword) the `static` keyword inherited from C.
What follows is the same keyword used in a very different context (even if the one we already know will pop up in an idiom presented here). What follows is the same keyword used in a very different context (even if the one we already know will pop up in an idiom presented here).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Static methods ## Static methods
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Sometimes, a data is related to the _class_ itself rather than to the object. The way to indicate this is to put a `static` keyword in front of the attribute that is not especially related to the instantiated object but rather common to all instances. Sometimes, a data is related to the _class_ itself rather than to the object. The way to indicate this is to put a `static` keyword in front of the attribute that is not especially related to the instantiated object but rather common to all instances.
Static attributes are following the exact same rules as the standard ones regarding the access status (public or private). Static attributes are following the exact same rules as the standard ones regarding the access status (public or private).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
struct Class struct Class
{ {
static std::string ClassName(); static std::string ClassName();
Class() = default; Class() = default;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
std::string Class::ClassName() std::string Class::ClassName()
{ {
return "Class"; return "Class";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
std::cout << "A static method may be called without any object instantiated: " std::cout << "A static method may be called without any object instantiated: "
<< Class::ClassName() << std::endl; << Class::ClassName() << std::endl;
Class obj; Class obj;
std::cout << "But any object of the class may access it as if it was a regular method: " std::cout << "But any object of the class may access it as if it was a regular method: "
<< obj.ClassName() << std::endl; << obj.ClassName() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Static data attributes - to avoid... (see next section to understand why!) ## Static data attributes - to avoid... (see next section to understand why!)
**Xeus-Cling Warning:** cling doesn't enable proper initialization of a static data attribute... Please considered the following code, available [@Coliru](https://coliru.stacked-crooked.com/a/f43bcc4548a4f160):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics clang
// cling - used by our kernel - doesn't enable proper initialization of a static data attribute;
// we therefore use the native clang instead.
#include <iostream> #include <iostream>
#include <vector> #include <vector>
// ========================
// Declarations
// ========================
struct Class struct Class
{ {
Class(); Class();
~Class(); ~Class();
static unsigned int Ninstance_; static unsigned int Ninstance_;
}; };
void Print();
// ========================
// Definitions
// ========================
Class::Class() Class::Class()
{ {
++Ninstance_; ++Ninstance_;
} }
Class::~Class() Class::~Class()
{ {
--Ninstance_; --Ninstance_;
} }
// IMPORTANT: this line must be put in a compiled file! // IMPORTANT: this line must be put in a compiled file!
unsigned int Class::Ninstance_ = 0; unsigned int Class::Ninstance_ = 0;
void Print() void Print()
{ {
std::cout << "There are " << Class::Ninstance_ << " of class Class." << std::endl; std::cout << "There are " << Class::Ninstance_ << " of class Class." << std::endl;
} }
int main(int argc, char** argv) // ========================
// Main
// ========================
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
Print(); Print();
Class obj; Class obj;
std::cout << "Access by an object is still possible: " << obj.Ninstance_ << std::endl; std::cout << "Access by an object is still possible: " << obj.Ninstance_ << std::endl;
Print(); Print();
{ {
std::vector<Class> vec(5); std::vector<Class> vec(5);
Print(); Print();
} }
Print(); Print();
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Static order initialization fiasco - and its fix ## Static order initialization fiasco - and its fix
However there is a possible problem not easy to show: when a program is compiled, there are no guarantee whatsoever about the order in which the source files will be compiled. It is therefore completely possible to use a static data attribute in a file *before* its initial value is actually given in another file. This lead to undefined behaviour... The way to fix it is to use a static method instead: However there is a possible problem not easy to show: when a program is compiled, there are no guarantee whatsoever about the order in which the source files will be compiled. It is therefore completely possible to use a static data attribute in a file *before* its initial value is actually given in another file. This lead to undefined behaviour... The way to fix it is to use a static method instead:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
#include <vector> #include <vector>
struct Class3 struct Class3
{ {
Class3(); Class3();
~Class3(); ~Class3();
static unsigned int& Ninstance(); // notice the reference and the fact it's now a method static unsigned int& Ninstance(); // notice the reference and the fact it's now a method
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
unsigned int& Class3::Ninstance() unsigned int& Class3::Ninstance()
{ {
static unsigned int ret = 0; // the initial value, please notice the use of C static here! static unsigned int ret = 0; // the initial value, please notice the use of C static here!
return ret; return ret;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Class3::Class3() Class3::Class3()
{ {
Ninstance()++; Ninstance()++;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Class3::~Class3() Class3::~Class3()
{ {
Ninstance()--; Ninstance()--;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Print3() void Print3()
{ {
std::cout << "There are " << Class3::Ninstance() << " of class Class." << std::endl; std::cout << "There are " << Class3::Ninstance() << " of class Class." << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Print3(); Print3();
Class3 obj; Class3 obj;
std::cout << "Access by an object is still possible: " << obj.Ninstance() << std::endl; std::cout << "Access by an object is still possible: " << obj.Ninstance() << std::endl;
Print3(); Print3();
{ {
Class3* vec = new Class3[5]; Class3* vec = new Class3[5];
Print3(); Print3();
delete[] vec; delete[] vec;
} }
Print3(); Print3();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To understand better the possible issue and the fix proposed, you may have a look at: To understand better the possible issue and the fix proposed, you may have a look at:
* Item 26 of [More effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++) * Item 26 of [More effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)
* The dedicated item on [isocpp FAQ](https://isocpp.org/wiki/faq/ctors#static-init-order) * The dedicated item on [isocpp FAQ](https://isocpp.org/wiki/faq/ctors#static-init-order)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### New C++ 17 fix ### New C++ 17 fix
C++ 17 actually provides a way to define the value in the header file within the class declaration with the `inline` keyword: C++ 17 actually provides a way to define the value in the header file within the class declaration with the `inline` keyword:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct Class4 struct Class4
{ {
Class4(); Class4();
~Class4(); ~Class4();
static inline unsigned int Ninstance_ = 0; static inline unsigned int Ninstance_ = 0;
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Thanks to this [FluentCpp post](https://www.fluentcpp.com/2019/07/23/how-to-define-a-global-constant-in-cpp/) that gave me the hint! Thanks to this [FluentCpp post](https://www.fluentcpp.com/2019/07/23/how-to-define-a-global-constant-in-cpp/) that gave us the hint!
And make sure not to forget the `inline` keyword, without which you go straight back to the fiasco situation...
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Inheritance](./6-inheritance.ipynb) # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Inheritance](./6-inheritance.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Introduction to inheritance ## Introduction to inheritance
Sometimes, you might want to define two types that are related: one might be an extension of the other, or they may share some similarities we would like to put in common. Sometimes, you might want to define two types that are related: one might be an extension of the other, or they may share some similarities we would like to put in common.
Let's suppose for instance you are asked to register all vehicles owned by your company. You could define independent classes `Bicycle`, `Scooter` and `Car`, but as a result storing them in a same array (or even better `std::vector`) would be impossible. Let's suppose for instance you are asked to register all vehicles owned by your company. You could define independent classes `Bicycle`, `Scooter` and `Car`, but as a result storing them in a same array (or even better `std::vector`) would be impossible.
The idea of inheritance is to provide a **base class** from which our classes are derived: The idea of inheritance is to provide a **base class** from which our classes are derived:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
enum class motor_type { none, thermic, electric }; enum class motor_type { none, thermic, electric };
class Vehicle class Vehicle
{ {
public: public:
Vehicle(motor_type type); Vehicle(motor_type type);
void Print() const { std::cout << "I'm a Vehicle!" << std::endl; } void Print() const { std::cout << "I'm a Vehicle!" << std::endl; }
private: private:
motor_type type_; motor_type type_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vehicle::Vehicle(motor_type type) Vehicle::Vehicle(motor_type type)
: type_(type) : type_(type)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class ElectricBicycle : public Vehicle class ElectricBicycle : public Vehicle
{ {
public: public:
ElectricBicycle(); ElectricBicycle();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class ThermicCar : public Vehicle class ThermicCar : public Vehicle
{ {
public: public:
ThermicCar(); ThermicCar();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
ElectricBicycle::ElectricBicycle() ElectricBicycle::ElectricBicycle()
: Vehicle(motor_type::electric) : Vehicle(motor_type::electric)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
ThermicCar::ThermicCar() ThermicCar::ThermicCar()
: Vehicle(motor_type::thermic) : Vehicle(motor_type::thermic)
{ } { }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A bit of syntax first: A bit of syntax first:
* See the structure in declaring the derived classes: there is a `:` followed by the keyword `public` and the name of the class. * See the structure in declaring the derived classes: there is a `:` followed by the keyword `public` and the name of the class.
* The derived constructors must first call one of the base class constructor. If none specified, the default one without arguments is called *if existing*... * The derived constructors must first call one of the base class constructor. If none specified, the default one without arguments is called *if existing*...
The base class part is constructed first, and then the elements specific to the derived class are added (that will be important - we will come back to that). The base class part is constructed first, and then the elements specific to the derived class are added (that will be important - we will come back to that).
Likewise, destruction is performed in reverse order: first the specific parts of the derived class, then the base class (in most cases you don't have to care about that). Likewise, destruction is performed in reverse order: first the specific parts of the derived class, then the base class (in most cases you don't have to care about that).
### Multiple layer of inheritance ### Multiple layer of inheritance
A child class may also be the parent of another class (unless `final` keyword is used - we'll see this keyword in [polymorphism notebook](./7-polymorphism.ipynb#final-keyword)). A child class may also be the parent of another class (unless `final` keyword is used - we'll see this keyword in [polymorphism notebook](./7-polymorphism.ipynb#final-keyword)).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class ElectricVehicle : public Vehicle class ElectricVehicle : public Vehicle
{ {
public: public:
ElectricVehicle(); ElectricVehicle();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
ElectricVehicle::ElectricVehicle() ElectricVehicle::ElectricVehicle()
: Vehicle(motor_type::electric) : Vehicle(motor_type::electric)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class ElectricCar : public ElectricVehicle class ElectricCar : public ElectricVehicle
{ {
public: public:
ElectricCar() = default; ElectricCar() = default;
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Multiple inheritance ### Multiple inheritance
It is also possible for a class to inherit from several parents: It is also possible for a class to inherit from several parents:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class BlueObjects class BlueObjects
{ {
public: public:
BlueObjects() = default; BlueObjects() = default;
int Print() int Print()
{ {
std::cout << "I'm blue!" << std::endl; std::cout << "I'm blue!" << std::endl;
return 42; return 42;
} }
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class BlueVehicle : public Vehicle, public BlueObjects class BlueVehicle : public Vehicle, public BlueObjects
{ {
public: public:
BlueVehicle(motor_type type); BlueVehicle(motor_type type);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
BlueVehicle::BlueVehicle(motor_type type) BlueVehicle::BlueVehicle(motor_type type)
: Vehicle(type), // mandatory call of the non-default constructor : Vehicle(type), // mandatory call of the non-default constructor
BlueObjects() // not mandatory: default constructor called anyway if not specified explicitly BlueObjects() // not mandatory: default constructor called anyway if not specified explicitly
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
BlueVehicle blue_bike(motor_type::none); BlueVehicle blue_bike(motor_type::none);
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**Beware:** there might be ambiguity between some methods names, regardless of prototype: **Beware:** there might be ambiguity between some methods names, regardless of prototype:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
blue_bike.Print(); // COMPILATION ERROR: should it call int BlueObjects::Print() or void Vehicle::Print() const ? blue_bike.Print(); // COMPILATION ERROR: should it call int BlueObjects::Print() or void Vehicle::Print() const ?
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is possible to lift the ambiguity, but the syntax is not very pretty and you should strive to avoid the case that requires it in the first place: It is possible to lift the ambiguity, but the syntax is not very pretty and you should strive to avoid the case that requires it in the first place:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
blue_bike.BlueObjects::Print(); blue_bike.BlueObjects::Print();
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
blue_bike.Vehicle::Print(); blue_bike.Vehicle::Print();
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Diamond inheritance ### Diamond inheritance
In C++ you may also define so-called diamond inheritance, where an object `D` inherits from the same base class `A` twice through different parents `B` and `C`: In C++ you may also define so-called diamond inheritance, where an object `D` inherits from the same base class `A` twice through different parents `B` and `C`:
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
<img src="./Images/DiamondInheritance.png" alt="Diamond inheritance diagram, from Wikipedia page (public domain)" width="150"/> <img src="./Images/DiamondInheritance.png" alt="Diamond inheritance diagram, from Wikipedia page (public domain)" width="150"/>
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
I really advise you to avoid this kind of things whenever possible, but if you _really_ need this: I really advise you to avoid this kind of things whenever possible, but if you _really_ need this:
- First think again, do you _really really_ need this, or could you reorganize your code differently to avoid this situation? (keep in mind other languages chose - wisely to my mind - not to support this, so it is possible to work around it!) - First think again, do you _really really_ need this, or could you reorganize your code differently to avoid this situation? (keep in mind other languages chose - wisely to my mind - not to support this, so it is possible to work around it!)
- Have a look [here](https://www.cprogramming.com/tutorial/virtual_inheritance.html) to understand the caveats and do it properly (one key is not to forget the keywords `virtual` in the inheritance line). - Have a look [here](https://www.cprogramming.com/tutorial/virtual_inheritance.html) to understand the caveats and do it properly (one key is not to forget the keywords `virtual` in the inheritance line).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Public inheritance, private inheritance and composition ## Public inheritance, private inheritance and composition
### IS-A relationship of public inheritance ### IS-A relationship of public inheritance
So far, we have derived **publicly** the base class (hence the **public** keyword in the inheritance declaration), and this defines a **IS-A** relationship: the derived object is expected to be acceptable *everywhere* a base object is deemed acceptable. So far, we have derived **publicly** the base class (hence the **public** keyword in the inheritance declaration), and this defines a **IS-A** relationship: the derived object is expected to be acceptable *everywhere* a base object is deemed acceptable.
This might seem trivial, but is not as obvious at it seems. Let's consider a counter-intuitive example: This might seem trivial, but is not as obvious at it seems. Let's consider a counter-intuitive example:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
class Rectangle class Rectangle
{ {
public: public:
Rectangle(double width, double length); Rectangle(double width, double length);
void SetWidth(double x); void SetWidth(double x);
void SetLength(double x); void SetLength(double x);
double GetWidth() const; double GetWidth() const;
double GetLength() const; double GetLength() const;
void Print() const; void Print() const;
private: private:
double width_ { -1.e20 } ; // Stupid value that would play havoc if not properly initialized double width_ { -1.e20 } ; // Stupid value that would play havoc if not properly initialized
// - std::optional (C++17) would probably a better choice. // - std::optional (C++17) would probably a better choice.
double length_ { -1.e20 } ; double length_ { -1.e20 } ;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Rectangle::Rectangle(double width, double length) Rectangle::Rectangle(double width, double length)
: width_(width), : width_(width),
length_(length) length_(length)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
double Rectangle::GetWidth() const double Rectangle::GetWidth() const
{ {
return width_; return width_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
double Rectangle::GetLength() const double Rectangle::GetLength() const
{ {
return length_; return length_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Rectangle::SetWidth(double x) void Rectangle::SetWidth(double x)
{ {
width_ = x; width_ = x;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Rectangle::SetLength(double x) void Rectangle::SetLength(double x)
{ {
length_ = x; length_ = x;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void Rectangle::Print() const void Rectangle::Print() const
{ {
std::cout << "My rectangle gets a length of " << GetLength() << " and a width of " << GetWidth() << std::endl; std::cout << "My rectangle gets a length of " << GetLength() << " and a width of " << GetWidth() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class Square : public Rectangle // BAD IDEA! class Square : public Rectangle // BAD IDEA!
{ {
public: public:
Square(double side_dimension); Square(double side_dimension);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Square::Square(double side_dimension) Square::Square(double side_dimension)
: Rectangle(side_dimension, side_dimension) : Rectangle(side_dimension, side_dimension)
{ } { }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This is perfectly valid C++ code, and you might be happy with that... Now let's add a free-function that changes the shape of a rectangle: This is perfectly valid C++ code, and you might be happy with that... Now let's add a free-function that changes the shape of a rectangle:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void ModifyRectangle(Rectangle& r) void ModifyRectangle(Rectangle& r)
{ {
r.SetWidth(r.GetWidth() * 2.); r.SetWidth(r.GetWidth() * 2.);
r.SetLength(r.GetLength() * .5); r.SetLength(r.GetLength() * .5);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
std::cout << " ==== RECTANGLE ==== " << std::endl; std::cout << " ==== RECTANGLE ==== " << std::endl;
Rectangle r(3., 5.); Rectangle r(3., 5.);
r.Print(); r.Print();
ModifyRectangle(r); // ok ModifyRectangle(r); // ok
r.Print(); r.Print();
std::cout << " ==== SQUARE ==== " << std::endl; std::cout << " ==== SQUARE ==== " << std::endl;
Square c(4.); Square c(4.);
c.Print(); // ok c.Print(); // ok
ModifyRectangle(c); // ok from compiler standpoint... not so much from consistency! ModifyRectangle(c); // ok from compiler standpoint... not so much from consistency!
c.Print(); // urgh... c.Print(); // urgh...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So the language allows you to define this public relationship between `Rectangle` and `Square`, but you can see it is not a very bright idea... (this example is more detailed in item 32 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)). So the language allows you to define this public relationship between `Rectangle` and `Square`, but you can see it is not a very bright idea... (this example is more detailed in item 32 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)).
Don't get me wrong: public inheritance is very handy, as we shall see more below with the introduction of polymorphism. It's just that you need to assess properly first what your needs are, and decide which is the more appropriate answer - and sometimes the most obvious one is not the best. Don't get me wrong: public inheritance is very handy, as we shall see more below with the introduction of polymorphism. It's just that you need to assess properly first what your needs are, and decide which is the more appropriate answer - and sometimes the most obvious one is not the best.
The public inheritance is an application of the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle). The public inheritance is an application of the [Liskov substitution principle](https://en.wikipedia.org/wiki/Liskov_substitution_principle).
We will now "fix" our `Square` problem with two different idioms: private inheritance and composition. We will now "fix" our `Square` problem with two different idioms: private inheritance and composition.
### IS-IMPLEMENTED-IN-TERMS-OF relationship of private inheritance ### IS-IMPLEMENTED-IN-TERMS-OF relationship of private inheritance
What you might look for in fact is **private** inheritance, in which all the inherited attributes are considered private: What you might look for in fact is **private** inheritance, in which all the inherited attributes are considered private:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class Square2 : private Rectangle class Square2 : private Rectangle
{ {
public: public:
Square2(double side_dimension); Square2(double side_dimension);
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Square2::Square2(double side_dimension) Square2::Square2(double side_dimension)
: Rectangle(side_dimension, side_dimension) : Rectangle(side_dimension, side_dimension)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Square2 square(4.); Square2 square(4.);
square.SetWidth(5.); // COMPILATION ERROR! square.SetWidth(5.); // COMPILATION ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
And from there: And from there:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Square2 square(4.); Square2 square(4.);
ModifyRectangle(square); // COMPILATION ERROR! ModifyRectangle(square); // COMPILATION ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So this way, there is no assumption a `Square2` might pass any call a `Rectangle` would accept. So this way, there is no assumption a `Square2` might pass any call a `Rectangle` would accept.
And of course the point is to avoid redefining stuff and relying upon what was already implemented in the first class (it won't be impressive here but in other more complex cases it might prove extremely handy): And of course the point is to avoid redefining stuff and relying upon what was already implemented in the first class (it won't be impressive here but in other more complex cases it might prove extremely handy):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class Square3 : private Rectangle class Square3 : private Rectangle
{ {
public: public:
Square3(double side_dimension); Square3(double side_dimension);
void SetSideDimension(double x); void SetSideDimension(double x);
using Rectangle::Print; // special syntax to make explicitly available the Print() method from Rectangle using Rectangle::Print; // special syntax to make explicitly available the Print() method from Rectangle
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Square3::Square3(double side_dimension) Square3::Square3(double side_dimension)
: Rectangle(side_dimension, side_dimension) : Rectangle(side_dimension, side_dimension)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Square3::SetSideDimension(double x) void Square3::SetSideDimension(double x)
{ {
SetWidth(x); // the methods from Rectangle, accessible privately SetWidth(x); // the methods from Rectangle, accessible privately
SetLength(x); SetLength(x);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Square3 square(4.); Square3 square(4.);
square.Print(); square.Print();
square.SetSideDimension(3.); square.SetSideDimension(3.);
square.Print(); square.Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### CONTAINS-A relationship of composition ### CONTAINS-A relationship of composition
Private inheritance is not the only way to provide this kind of behaviour; it is possible as well to use **composition**, which is defining a data attribute object that handles part of the computation. Private inheritance is not the only way to provide this kind of behaviour; it is possible as well to use **composition**, which is defining a data attribute object that handles part of the computation.
I won't dwelve into too much details here, but the general idea is that composition is often preferable as the binding is less strong and you should strive for the looser relationship possible. Here is the same example as above implemented with composition: I won't dwelve into too much details here, but the general idea is that composition is often preferable as the binding is less strong and you should strive for the looser relationship possible. Here is the same example as above implemented with composition:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class Square4 class Square4
{ {
public: public:
Square4(double side_dimension); Square4(double side_dimension);
~Square4() = default; ~Square4() = default;
void Print() const; void Print() const;
void SetSideDimension(double x); void SetSideDimension(double x);
private: private:
const Rectangle& GetRectangle() const; const Rectangle& GetRectangle() const;
Rectangle& GetNonConstantRectangle(); // we could have named it `GetRectangle()` as well, but a really distinct name is more expressive! Rectangle& GetNonConstantRectangle(); // we could have named it `GetRectangle()` as well, but a really distinct name is more expressive!
private: private:
Rectangle rectangle_; Rectangle rectangle_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
const Rectangle& Square4::GetRectangle() const const Rectangle& Square4::GetRectangle() const
{ {
return rectangle_; return rectangle_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Rectangle& Square4::GetNonConstantRectangle() Rectangle& Square4::GetNonConstantRectangle()
{ {
return rectangle_; return rectangle_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Square4::Square4(double side_dimension) Square4::Square4(double side_dimension)
: rectangle_(Rectangle(side_dimension, side_dimension)) // you need the `Rectangle` constructor call here, as there are no default constructor in `Rectangle` class : rectangle_(Rectangle(side_dimension, side_dimension)) // you need the `Rectangle` constructor call here, as there are no default constructor in `Rectangle` class
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Square4::SetSideDimension(double x) void Square4::SetSideDimension(double x)
{ {
auto& rectangle = GetNonConstantRectangle(); auto& rectangle = GetNonConstantRectangle();
rectangle.SetWidth(x); rectangle.SetWidth(x);
rectangle.SetLength(x); rectangle.SetLength(x);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Square4::Print() const void Square4::Print() const
{ {
GetRectangle().Print(); GetRectangle().Print();
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Square4 square(4.); Square4 square(4.);
square.Print(); square.Print();
square.SetSideDimension(3.); square.SetSideDimension(3.);
square.Print(); square.Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Private inheritance vs composition ### Private inheritance vs composition
So what are the pros and cons of private inheritance and composition? So what are the pros and cons of private inheritance and composition?
- No granularity with private inheritance: you get all the interface available (privately) in the derived class. On the other hand, with composition you can choose which method you want to expose publicly in your new class (only `Print()` here). - No granularity with private inheritance: you get all the interface available (privately) in the derived class. On the other hand, with composition you can choose which method you want to expose publicly in your new class (only `Print()` here).
- But composition is more verbose: if you want most of the interface, you need for each method to define a method which under the hood calls your data attribute (as did `Square4::Print()` above). So if you need most of the API of the base class, private inheritance is less work. - But composition is more verbose: if you want most of the interface, you need for each method to define a method which under the hood calls your data attribute (as did `Square4::Print()` above). So if you need most of the API of the base class, private inheritance is less work.
As indicated before, I tend to use composition more, but it's still nice to know both are available. As indicated before, I tend to use composition more, but it's still nice to know both are available.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Protected status ## Protected status
Our discussion about public and private inheritance has highlighted the importance of the propagation of the status of public members: Our discussion about public and private inheritance has highlighted the importance of the propagation of the status of public members:
* With public inheritance, public members of the base class remains public. * With public inheritance, public members of the base class remains public.
* With private inheritance, public members of the base class become private. * With private inheritance, public members of the base class become private.
So far, we have not talked about what happens to private members... Now is the time; let's find out! So far, we have not talked about what happens to private members... Now is the time; let's find out!
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class BaseClass class BaseClass
{ {
public: public:
BaseClass() = default; BaseClass() = default;
private: private:
void DoNothing() { } void DoNothing() { }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class DerivedClass : public BaseClass class DerivedClass : public BaseClass
{ {
public: public:
DerivedClass(); DerivedClass();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
DerivedClass::DerivedClass() DerivedClass::DerivedClass()
{ {
DoNothing(); // COMPILATION ERROR DoNothing(); // COMPILATION ERROR
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So a private method is not accessible, even to the derived class. If you need such a functionality (and you should!), there is actually a third keyword: **protected**. This status means basically private except for the derived classes: So a private method is not accessible, even to the derived class. If you need such a functionality (and you should!), there is actually a third keyword: **protected**. This status means basically private except for the derived classes:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class BaseClass2 class BaseClass2
{ {
public: public:
BaseClass2() = default; BaseClass2() = default;
protected: protected:
void DoNothing() { } void DoNothing() { }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class DerivedClass2 : public BaseClass2 class DerivedClass2 : public BaseClass2
{ {
public: public:
DerivedClass2(); DerivedClass2();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
DerivedClass2::DerivedClass2() DerivedClass2::DerivedClass2()
{ {
DoNothing(); // Ok DoNothing(); // Ok
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
auto object = DerivedClass2(); auto object = DerivedClass2();
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
object.DoNothing(); // COMPILATION ERROR! object.DoNothing(); // COMPILATION ERROR!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Here `DoNothing` behaves exactly as a private method of `DerivedClass2`, but it was defined in `BaseClass2`. Here `DoNothing` behaves exactly as a private method of `DerivedClass2`, but it was defined in `BaseClass2`.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There is as well a **protected** status for inheritance even if I must admit I have never needed it. A contributor to [StackOverflow](https://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance) summed this up with this nice cheatsheet: There is as well a **protected** status for inheritance even if I must admit I have never needed it. A contributor to [StackOverflow](https://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance) summed this up with this nice cheatsheet:
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
![Diagram](Images/PublicProtectedPrivateDiagram.jpg "Inheritance diagram, courtesy of [StackOverflow](https://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance)") ![Diagram](Images/PublicProtectedPrivateDiagram.jpg "Inheritance diagram, courtesy of [StackOverflow](https://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance)")
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## **[WARNING]** Be wary of the slicing effect ## **[WARNING]** Be wary of the slicing effect
A note of caution about public inheritance: A note of caution about public inheritance:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class Base { }; class Base { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
class Derived : public Base class Derived : public Base
{ {
public: public:
Derived(int val) Derived(int val)
: value_ { val } : value_ { val }
{ } { }
private: private:
int value_; int value_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Base base; Base base;
Derived derived { 5 }; Derived derived { 5 };
base = derived; // dangerous line... base = derived; // dangerous line...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This code is perfectly valid, but there is a fat chance it's absolutely not what you intended: all the information that is in `Derived` but not in `Base` is lost in `base` object... This code is perfectly valid, but there is a fat chance it's absolutely not what you intended: all the information that is in `Derived` but not in `Base` is lost in `base` object...
This is the so-called **slicing effect**. This is the so-called **slicing effect**.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Here it may seem obvious, but it might be insidious if you use functions with pass-by-value: Here it may seem obvious, but it might be insidious if you use functions with pass-by-value:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void ComputePoorlyStuff(Base base) void ComputePoorlyStuff(Base base)
{ {
// ... // ...
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Derived derived { 5 }; Derived derived { 5 };
ComputePoorlyStuff(derived); // slicing effect: all defined only in `Derived` class is lost as far as `ComputePoorlyStuff` function is concerned! ComputePoorlyStuff(derived); // slicing effect: all defined only in `Derived` class is lost as far as `ComputePoorlyStuff` function is concerned!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
you're actually slicing the `derived` object and only the `Base` part of it is copied and passed in the function! you're actually slicing the `derived` object and only the `Base` part of it is copied and passed in the function!
The patch is rather easy and match what we already told for different reasons previously: don't pass objects by copy! The patch is rather easy and match what we already told for different reasons previously: don't pass objects by copy!
Indeed, the same with pass-by-reference (or pass-by-pointer if you like these sort of things...) works like a charm: Indeed, the same with pass-by-reference (or pass-by-pointer if you like these sort of things...) works like a charm:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void ComputeStuff(const Base& base) // ok, no slicing effect! void ComputeStuff(const Base& base) // ok, no slicing effect!
{ {
// ... // ...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
If you want to learn more about this effect, you should read this [great (and short) tutorial](https://www.learncpp.com/cpp-tutorial/object-slicing). If you want to learn more about this effect, you should read this [great (and short) tutorial](https://www.learncpp.com/cpp-tutorial/object-slicing).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Polymorphism](./7-polymorphism.ipynb) # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Polymorphism](./7-polymorphism.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Polymorphism ## Polymorphism
### Naïve approach to underline the need ### Naïve approach to underline the need
[So far](./6-inheritance.ipynb), I have not yet shown how objects could be stored in the same container, which was a justification I gave when introducing inheritance. [So far](./6-inheritance.ipynb), I have not yet shown how objects could be stored in the same container, which was a justification I gave when introducing inheritance.
The first idea would be to cram all items in a container whose type is the base class: The first idea would be to cram all items in a container whose type is the base class:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
class NotPolymorphicVehicle class NotPolymorphicVehicle
{ {
public: public:
NotPolymorphicVehicle() = default; NotPolymorphicVehicle() = default;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
class NotPolymorphicCar : public NotPolymorphicVehicle class NotPolymorphicCar : public NotPolymorphicVehicle
{ {
public: public:
NotPolymorphicCar(); NotPolymorphicCar();
void Print() const; void Print() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void NotPolymorphicCar::Print() const void NotPolymorphicCar::Print() const
{ {
std::cout << "I'm a car!" << std::endl; std::cout << "I'm a car!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
class NotPolymorphicBicycle : public NotPolymorphicVehicle class NotPolymorphicBicycle : public NotPolymorphicVehicle
{ {
public: public:
NotPolymorphicBicycle(); NotPolymorphicBicycle();
void Print() const; void Print() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void NotPolymorphicBicycle::Print() const void NotPolymorphicBicycle::Print() const
{ {
std::cout << "I'm a bike!" << std::endl; std::cout << "I'm a bike!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
NotPolymorphicBicycle b; NotPolymorphicBicycle b;
NotPolymorphicCar c; NotPolymorphicCar c;
NotPolymorphicVehicle list[2] = { b, c }; NotPolymorphicVehicle list[2] = { b, c };
list[0].Print(); // COMPILATION ERROR list[0].Print(); // COMPILATION ERROR
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The issue here is that the objects are stored as `NotPolymorphicVehicle`, and this class doesn't know any method called `Print()`, even if all its children do. The issue here is that the objects are stored as `NotPolymorphicVehicle`, and this class doesn't know any method called `Print()`, even if all its children do.
Defining `Print()` in the base class would not work either: Defining `Print()` in the base class would not work either:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class AlsoNotPolymorphicVehicle class AlsoNotPolymorphicVehicle
{ {
public: public:
AlsoNotPolymorphicVehicle() = default; AlsoNotPolymorphicVehicle() = default;
void Print() const; void Print() const;
}; };
class AlsoNotPolymorphicCar : public AlsoNotPolymorphicVehicle class AlsoNotPolymorphicCar : public AlsoNotPolymorphicVehicle
{ {
public: public:
AlsoNotPolymorphicCar() = default; AlsoNotPolymorphicCar() = default;
void Print() const; void Print() const;
}; };
class AlsoNotPolymorphicBicycle : public AlsoNotPolymorphicVehicle class AlsoNotPolymorphicBicycle : public AlsoNotPolymorphicVehicle
{ {
public: public:
AlsoNotPolymorphicBicycle() = default; AlsoNotPolymorphicBicycle() = default;
void Print() const; void Print() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void AlsoNotPolymorphicVehicle::Print() const void AlsoNotPolymorphicVehicle::Print() const
{ {
std::cout << "I am... hopefully a polymorphic vehicle?" << std::endl; std::cout << "I am... hopefully a polymorphic vehicle?" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void AlsoNotPolymorphicCar::Print() const void AlsoNotPolymorphicCar::Print() const
{ {
std::cout << "I'm a car!" << std::endl; std::cout << "I'm a car!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void AlsoNotPolymorphicBicycle::Print() const void AlsoNotPolymorphicBicycle::Print() const
{ {
std::cout << "I'm a bike!" << std::endl; std::cout << "I'm a bike!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
AlsoNotPolymorphicBicycle b; AlsoNotPolymorphicBicycle b;
AlsoNotPolymorphicCar c; AlsoNotPolymorphicCar c;
AlsoNotPolymorphicVehicle list[2] = { b, c }; AlsoNotPolymorphicVehicle list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i) for (auto i = 0ul; i < 2ul; ++i)
list[i].Print(); // No compilation error, but clearly not what we intended... list[i].Print(); // No compilation error, but clearly not what we intended...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So far, the perspectives aren't rosy: So far, the perspectives aren't rosy:
* The base class needs to know all the methods beforehand: `Print()` had to be defined in the base class to make it compile. * The base class needs to know all the methods beforehand: `Print()` had to be defined in the base class to make it compile.
* And even so, the result was clearly not what we hoped for: the dumb value provided in the base class was actually returned. * And even so, the result was clearly not what we hoped for: the dumb value provided in the base class was actually returned.
### `virtual ` keyword ### `virtual ` keyword
The second point is easy to solve: there is a dedicated keyword named **virtual** in the language that may qualify a method and tell it is likely to be adapted or superseded in a derived class. The second point is easy to solve: there is a dedicated keyword named **virtual** in the language that may qualify a method and tell it is likely to be adapted or superseded in a derived class.
Almost all methods might be virtual, with very few exceptions: Almost all methods might be virtual, with very few exceptions:
* Static methods * Static methods
* Constructors: no constructor can be virtual, and even more than that using a virtual method within a constructor won't work as expected (we'll come back to this [shortly](#Good-practice:-never-call-a-virtual-method-in-a-constructor)) * Constructors: no constructor can be virtual, and even more than that using a virtual method within a constructor won't work as expected (we'll come back to this [shortly](#Good-practice:-never-call-a-virtual-method-in-a-constructor))
* Template methods (we'll see that in part 4 of this lecture) * Template methods (we'll see that in part 4 of this lecture)
That means even the destructor may be virtual (and probably should - we'll go back to that as well...). That means even the destructor may be virtual (and probably should - we'll go back to that as well...).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class PolymorphicButClumsyVehicle class PolymorphicButClumsyVehicle
{ {
public: public:
PolymorphicButClumsyVehicle() = default; PolymorphicButClumsyVehicle() = default;
virtual void Print() const; virtual void Print() const;
}; };
class PolymorphicButClumsyCar : public PolymorphicButClumsyVehicle class PolymorphicButClumsyCar : public PolymorphicButClumsyVehicle
{ {
public: public:
PolymorphicButClumsyCar() = default; PolymorphicButClumsyCar() = default;
virtual void Print() const; virtual void Print() const;
}; };
class PolymorphicButClumsyBicycle : public PolymorphicButClumsyVehicle class PolymorphicButClumsyBicycle : public PolymorphicButClumsyVehicle
{ {
public: public:
PolymorphicButClumsyBicycle() = default; PolymorphicButClumsyBicycle() = default;
virtual void Print() const; virtual void Print() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void PolymorphicButClumsyVehicle::Print() const // Please notice: no `virtual` on definition! void PolymorphicButClumsyVehicle::Print() const // Please notice: no `virtual` on definition!
{ {
std::cout << "I am... hopefully a polymorphic vehicle?" << std::endl; std::cout << "I am... hopefully a polymorphic vehicle?" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void PolymorphicButClumsyCar::Print() const void PolymorphicButClumsyCar::Print() const
{ {
std::cout << "I am a car!" << std::endl; std::cout << "I am a car!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void PolymorphicButClumsyBicycle::Print() const void PolymorphicButClumsyBicycle::Print() const
{ {
std::cout << "I am a bike!" << std::endl; std::cout << "I am a bike!" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `virtual` work only with pointers or references ### `virtual` work only with pointers or references
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
PolymorphicButClumsyBicycle b; PolymorphicButClumsyBicycle b;
PolymorphicButClumsyCar c; PolymorphicButClumsyCar c;
PolymorphicButClumsyVehicle list[2] = { b, c }; PolymorphicButClumsyVehicle list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i) for (auto i = 0ul; i < 2ul; ++i)
list[i].Print(); // No compilation error, but clearly not what we intended... list[i].Print(); // No compilation error, but clearly not what we intended...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Still not what was intended... That's because when you use the objects directly as we do here, **static binding** is used: the definitions seen in the base class are used directly because the resolution occurs at compilation time. Still not what was intended... That's because when you use the objects directly as we do here, **static binding** is used: the definitions seen in the base class are used directly because the resolution occurs at compilation time.
To use the **dynamic binding**, we need to use either references or pointers. Let's do that: To use the **dynamic binding**, we need to use either references or pointers. Let's do that:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
PolymorphicButClumsyVehicle* b = new PolymorphicButClumsyBicycle; PolymorphicButClumsyVehicle* b = new PolymorphicButClumsyBicycle;
PolymorphicButClumsyVehicle* c = new PolymorphicButClumsyCar; PolymorphicButClumsyVehicle* c = new PolymorphicButClumsyCar;
PolymorphicButClumsyVehicle* list[2] = { b, c }; PolymorphicButClumsyVehicle* list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i) for (auto i = 0ul; i < 2ul; ++i)
list[i]->Print(); list[i]->Print();
delete b; delete b;
delete c; delete c;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The fact that a `PolymorphicButClumsyVehicle` pointer is able to properly call the derived classes version is what is called **polymorphism**; it's at runtime that the decision to call `PolymorphicButClumsyVehicle::Print()` or `PolymorphicButClumsyCar::Print()` is taken. The fact that a `PolymorphicButClumsyVehicle` pointer is able to properly call the derived classes version is what is called **polymorphism**; it's at runtime that the decision to call `PolymorphicButClumsyVehicle::Print()` or `PolymorphicButClumsyCar::Print()` is taken.
We're nonetheless not completely done yet: We're nonetheless not completely done yet:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
PolymorphicButClumsyVehicle* v = new PolymorphicButClumsyVehicle; PolymorphicButClumsyVehicle* v = new PolymorphicButClumsyVehicle;
PolymorphicButClumsyVehicle* b = new PolymorphicButClumsyBicycle; PolymorphicButClumsyVehicle* b = new PolymorphicButClumsyBicycle;
PolymorphicButClumsyVehicle* c = new PolymorphicButClumsyCar; PolymorphicButClumsyVehicle* c = new PolymorphicButClumsyCar;
PolymorphicButClumsyVehicle* list[3] = { v, b, c }; PolymorphicButClumsyVehicle* list[3] = { v, b, c };
for (auto i = 0ul; i < 3ul; ++i) for (auto i = 0ul; i < 3ul; ++i)
list[i]->Print(); list[i]->Print();
delete v; delete v;
delete b; delete b;
delete c; delete c;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Abstract class and pure virtual methods ### Abstract class and pure virtual methods
Our issue here is that `PolymorphicButClumsyVehicle` has no business being instantiated directly: it is merely an **abstract class** which should never be instantiated but is there to provide a skeleton to more substantiated derived classes. Our issue here is that `PolymorphicButClumsyVehicle` has no business being instantiated directly: it is merely an **abstract class** which should never be instantiated but is there to provide a skeleton to more substantiated derived classes.
The mechanism to indicate that is to provide at least one **pure virtual method**: a method which prototype is given in the base class but that **must** be overridden in derived classes (at least if you want them to become concrete). The syntax to do so is to add `= 0` after the prototype. The mechanism to indicate that is to provide at least one **pure virtual method**: a method which prototype is given in the base class but that **must** be overridden in derived classes (at least if you want them to become concrete). The syntax to do so is to add `= 0` after the prototype.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class PolymorphicVehicle class PolymorphicVehicle
{ {
public: public:
PolymorphicVehicle() = default; PolymorphicVehicle() = default;
virtual void Print() const = 0; // the only change from PolymorphicButClumsyVehicle! virtual void Print() const = 0; // the only change from PolymorphicButClumsyVehicle!
}; };
class PolymorphicCar : public PolymorphicVehicle class PolymorphicCar : public PolymorphicVehicle
{ {
public: public:
PolymorphicCar() = default; PolymorphicCar() = default;
virtual void Print() const; virtual void Print() const;
}; };
class PolymorphicBicycle : public PolymorphicVehicle class PolymorphicBicycle : public PolymorphicVehicle
{ {
public: public:
PolymorphicBicycle() = default; PolymorphicBicycle() = default;
virtual void Print() const; virtual void Print() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void PolymorphicCar::Print() const void PolymorphicCar::Print() const
{ {
std::cout << "I am a car!" << std::endl; std::cout << "I am a car!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void PolymorphicBicycle::Print() const void PolymorphicBicycle::Print() const
{ {
std::cout << "I am a bike!" << std::endl; std::cout << "I am a bike!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
PolymorphicVehicle v; // Compilation error: you can't instantiate an abstract class! PolymorphicVehicle v; // Compilation error: you can't instantiate an abstract class!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
But the following is fine: But the following is fine:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
PolymorphicVehicle* b = new PolymorphicBicycle; PolymorphicVehicle* b = new PolymorphicBicycle;
PolymorphicVehicle* c = new PolymorphicCar; PolymorphicVehicle* c = new PolymorphicCar;
PolymorphicVehicle* list[2] = { b, c }; PolymorphicVehicle* list[2] = { b, c };
for (auto i = 0ul; i < 2ul; ++i) for (auto i = 0ul; i < 2ul; ++i)
list[i]->Print(); list[i]->Print();
delete b; delete b;
delete c; delete c;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
_(don't worry if you get a warning - if so the compiler does its job well and we'll see shortly why)_ _(don't worry if you get a warning - if so the compiler does its job well and we'll see shortly why)_
**Beware:** You **must** provide a definition for all non pure-virtual methods in your class. Not doing so leads to a somewhat cryptic error at link-time. **Beware:** You **must** provide a definition for all non pure-virtual methods in your class. Not doing so leads to a somewhat cryptic error at link-time.
You are not required to provide a definition for a pure virtual method, and you won't most of the time... But you might provide one if you want to do so, for instance to provide an optional default instantiation for the method in derived classes: You are not required to provide a definition for a pure virtual method, and you won't most of the time... But you might provide one if you want to do so, for instance to provide an optional default instantiation for the method in derived classes:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct VirtualBase struct VirtualBase
{ {
virtual void Method() = 0; virtual void Method() = 0;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void VirtualBase::Method() void VirtualBase::Method()
{ {
std::cout << "Default implementation provided in abstract class." << std::endl; std::cout << "Default implementation provided in abstract class." << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct Concrete1 : public VirtualBase struct Concrete1 : public VirtualBase
{ {
virtual void Method() override; virtual void Method() override;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct Concrete2 : public VirtualBase struct Concrete2 : public VirtualBase
{ {
virtual void Method() override; virtual void Method() override;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Concrete1::Method() void Concrete1::Method()
{ {
VirtualBase::Method(); // call to the method defined in the base class VirtualBase::Method(); // call to the method defined in the base class
std::cout << "This enables providing a base behaviour that might be completed if needed " std::cout << "This enables providing a base behaviour that might be completed if needed "
"in derived classes, such as here by these lines you are reading!" << std::endl; "in derived classes, such as here by these lines you are reading!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Concrete2::Method() void Concrete2::Method()
{ {
std::cout << "Overridden implementation." << std::endl; std::cout << "Overridden implementation." << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
std::cout << "====== Concrete 1: uses up the definition provided in base class =====" << std::endl; std::cout << "====== Concrete 1: uses up the definition provided in base class =====" << std::endl;
Concrete1 concrete1; Concrete1 concrete1;
concrete1.Method(); concrete1.Method();
std::cout << "\n====== Concrete 2: doesn't use the definition provided in base class =====" << std::endl; std::cout << "\n====== Concrete 2: doesn't use the definition provided in base class =====" << std::endl;
Concrete2 concrete2; Concrete2 concrete2;
concrete2.Method(); concrete2.Method();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### override keyword ### override keyword
We saw in previous section that to make a method virtual we need to add a `virtual` qualifier in front of its prototype. We saw in previous section that to make a method virtual we need to add a `virtual` qualifier in front of its prototype.
I put it both in the base class and in the derived one, but in fact it is entirely up to the developer concerning the derived classes: I put it both in the base class and in the derived one, but in fact it is entirely up to the developer concerning the derived classes:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class European class European
{ {
public: public:
European() = default; European() = default;
virtual void Print() const; virtual void Print() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void European::Print() const void European::Print() const
{ {
std::cout << "I'm European!" << std::endl; std::cout << "I'm European!" << std::endl;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class French : public European class French : public European
{ {
public: public:
French() = default; French() = default;
void Print() const; // virtual keyword is skipped here - and it's fine by the standard void Print() const; // virtual keyword is skipped here - and it's fine by the standard
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void French::Print() const void French::Print() const
{ {
std::cout << "I'm French!" << std::endl; std::cout << "I'm French!" << std::endl;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
European* european = new European; European* european = new European;
european->Print(); european->Print();
European* french = new French; European* french = new French;
french->Print(); french->Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
But the drawback doing so is that we may forget the method is virtual... or we might do a typo when writing it! And in this case, the result is not what is expected: But the drawback doing so is that we may forget the method is virtual... or we might do a typo when writing it! And in this case, the result is not what is expected:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class ThirstyFrench : public European class ThirstyFrench : public European
{ {
public: public:
ThirstyFrench() = default; ThirstyFrench() = default;
virtual void Pint() const; // typo here! And the optional `virtual` doesn't help avoid it... virtual void Pint() const; // typo here! And the optional `virtual` doesn't help avoid it...
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void ThirstyFrench::Pint() const void ThirstyFrench::Pint() const
{ {
std::cout << "I'm French!" << std::endl; std::cout << "I'm French!" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
ThirstyFrench* french = new ThirstyFrench; ThirstyFrench* french = new ThirstyFrench;
french->Print(); french->Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
What would be nice is for the compiler to provide a way to secure against such errors... and that exactly what C++ 11 introduced with the `override` keyword. This keyword explicitly says we are declaring an override of a virtual method, and the code won't compile if the prototype doesn't match: What would be nice is for the compiler to provide a way to secure against such errors... and that exactly what C++ 11 introduced with the `override` keyword. This keyword explicitly says we are declaring an override of a virtual method, and the code won't compile if the prototype doesn't match:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class ThirstyButCarefulFrench : public European class ThirstyButCarefulFrench : public European
{ {
public: public:
ThirstyButCarefulFrench() = default; ThirstyButCarefulFrench() = default;
void Pint() const override; // COMPILATION ERROR! void Pint() const override; // COMPILATION ERROR!
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class ForgetfulFrench : public European class ForgetfulFrench : public European
{ {
public: public:
ForgetfulFrench() = default; ForgetfulFrench() = default;
virtual void Print() override; // COMPILATION ERROR! `const` is missing and therefore prototype doesn't match. virtual void Print() override; // COMPILATION ERROR! `const` is missing and therefore prototype doesn't match.
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Cost of virtuality ### Cost of virtuality
You have to keep in mind there is a small cost related to the virtual behaviour: at each method call the program has to figure out which dynamic type to use. To be honest the true effective cost is quite blurry for me: some says it's not that important (see for instance [isocpp FAQ](https://isocpp.org/wiki/faq/virtual-functions)) while others will say you can't be serious if you're using them. I tend personally to avoid them in the part of my code where I want to crunch numbers fast and I use them preferably in the initialization phase. You have to keep in mind there is a small cost related to the virtual behaviour: at each method call the program has to figure out which dynamic type to use. To be honest the true effective cost is quite blurry for me: some says it's not that important (see for instance [isocpp FAQ](https://isocpp.org/wiki/faq/virtual-functions)) while others will say you can't be serious if you're using them. I tend personally to avoid them in the part of my code where I want to crunch numbers fast and I use them preferably in the initialization phase.
## `dynamic_cast` ## `dynamic_cast`
There is yet a question to ask: what if we want to use a method only defined in the derived class? For instance, if we add an attribute `oil_type_` that is not meaningful for all types of vehicles, can we access it? There is yet a question to ask: what if we want to use a method only defined in the derived class? For instance, if we add an attribute `oil_type_` that is not meaningful for all types of vehicles, can we access it?
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
class PolymorphicVehicle class PolymorphicVehicle
{ {
public: public:
PolymorphicVehicle() = default; PolymorphicVehicle() = default;
virtual void Print() const = 0; virtual void Print() const = 0;
}; };
class PolymorphicThermicCar : public PolymorphicVehicle class PolymorphicThermicCar : public PolymorphicVehicle
{ {
public: public:
PolymorphicThermicCar(std::string&& oil_type); PolymorphicThermicCar(std::string&& oil_type);
virtual void Print() const; virtual void Print() const;
const std::string& GetOilType() const; const std::string& GetOilType() const;
private: private:
std::string oil_type_; std::string oil_type_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
PolymorphicThermicCar::PolymorphicThermicCar(std::string&& oil_type) PolymorphicThermicCar::PolymorphicThermicCar(std::string&& oil_type)
: PolymorphicVehicle(), : PolymorphicVehicle(),
oil_type_(oil_type) oil_type_(oil_type)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void PolymorphicThermicCar::Print() const void PolymorphicThermicCar::Print() const
{ {
std::cout << "I'm a car that uses up " << GetOilType() << std::endl; std::cout << "I'm a car that uses up " << GetOilType() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
const std::string& PolymorphicThermicCar::GetOilType() const const std::string& PolymorphicThermicCar::GetOilType() const
{ {
return oil_type_; return oil_type_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
PolymorphicVehicle* c = new PolymorphicThermicCar("gazole"); PolymorphicVehicle* c = new PolymorphicThermicCar("gazole");
std::cout << "Oil type = " << c->GetOilType() << std::endl; // COMPILATION ERROR: been there before... std::cout << "Oil type = " << c->GetOilType() << std::endl; // COMPILATION ERROR: been there before...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You may in fact explicitly tells the `c` pointer needs to be interpreted as a `PolymorphicThermicCar`: You may in fact explicitly tells the `c` pointer needs to be interpreted as a `PolymorphicThermicCar`:
**Xeus-cling issue:** Here cling may not be able to run it (depends on the version used - it didn't in 2021 but seems to work in 2022 and 2024) but it is accepted rightfully by a full-fledged compiler (see for instance [@Coliru](http://coliru.stacked-crooked.com/a/22fff15d28c93a17)):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Xeus-cling issue!
#include <iostream> #include <iostream>
{ {
PolymorphicThermicCar* c = new PolymorphicThermicCar("gazole"); PolymorphicThermicCar* c = new PolymorphicThermicCar("gazole");
PolymorphicVehicle* list[1] = { c }; // Ok! PolymorphicVehicle* list[1] = { c }; // Ok!
auto c_corrected = dynamic_cast<PolymorphicThermicCar*>(list[0]); auto c_corrected = dynamic_cast<PolymorphicThermicCar*>(list[0]);
std::cout << "Oil type = " << c_corrected->GetOilType() << std::endl; std::cout << "Oil type = " << c_corrected->GetOilType() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
So you could devise a way to identify which is the dynamic type of your `PolymorphicVehicle` pointer and cast it dynamically to the rightful type so that extended API offered by the derived class is accessible. So you could devise a way to identify which is the dynamic type of your `PolymorphicVehicle` pointer and cast it dynamically to the rightful type so that extended API offered by the derived class is accessible.
If you find this clunky, you are not alone: by experience if you really need to resort to **dynamic_cast** it's probably that your data architecture needs some revision. But maybe the mileage vary for other developers, and it's useful to know the possibility exists. If you find this clunky, you are not alone: by experience if you really need to resort to **dynamic_cast** it's probably that your data architecture needs some revision. But maybe the mileage vary for other developers, and it's useful to know the possibility exists.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## `final` keyword ## `final` keyword
If you need to specify a class that can't be derived, you may stick a `final` keyword in its declaration (from C++11 onward): If you need to specify a class that can't be derived, you may stick a `final` keyword in its declaration (from C++11 onward):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class UnderivableClass final class UnderivableClass final
{ }; { };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class ITryAnyway : public UnderivableClass // COMPILATION ERROR! class ITryAnyway : public UnderivableClass // COMPILATION ERROR!
{ }; { };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The keyword may also be used only for one or several virtual methods: The keyword may also be used only for one or several virtual methods:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct DerivableClass struct DerivableClass
{ {
virtual void Method1(); virtual void Method1();
virtual void Method2(); virtual void Method2();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct DerivedClassFirstLevel : public DerivableClass struct DerivedClassFirstLevel : public DerivableClass
{ {
virtual void Method1() override final; virtual void Method1() override final;
virtual void Method2() override; virtual void Method2() override;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct DerivedClassSecondLevel : public DerivedClassFirstLevel struct DerivedClassSecondLevel : public DerivedClassFirstLevel
{ {
virtual void Method2() override; // ok virtual void Method2() override; // ok
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct DerivedClassSecondLevelIgnoreFinal : public DerivedClassFirstLevel struct DerivedClassSecondLevelIgnoreFinal : public DerivedClassFirstLevel
{ {
virtual void Method1() override; // COMPILATION ERROR! virtual void Method1() override; // COMPILATION ERROR!
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Virtual destructor to avoid partial destruction ## Virtual destructor to avoid partial destruction
I indicated earlier that destructors might be virtual, but in fact I should have said that for most of the non final classes the destructor should be virtual: I indicated earlier that destructors might be virtual, but in fact I should have said that for most of the non final classes the destructor should be virtual:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct BaseClass3 struct BaseClass3
{ {
BaseClass3(); BaseClass3();
~BaseClass3(); ~BaseClass3();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
#include <iostream> #include <iostream>
BaseClass3::BaseClass3() BaseClass3::BaseClass3()
{ {
std::cout << "BaseClass3 constructor" << std::endl; std::cout << "BaseClass3 constructor" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
BaseClass3::~BaseClass3() BaseClass3::~BaseClass3()
{ {
std::cout << "BaseClass3 destructor" << std::endl; std::cout << "BaseClass3 destructor" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct DerivedClass3 : public BaseClass3 struct DerivedClass3 : public BaseClass3
{ {
DerivedClass3(); DerivedClass3();
~DerivedClass3(); ~DerivedClass3();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
DerivedClass3::DerivedClass3() DerivedClass3::DerivedClass3()
{ {
std::cout << "DerivedClass3 constructor" << std::endl; std::cout << "DerivedClass3 constructor" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
DerivedClass3::~DerivedClass3() DerivedClass3::~DerivedClass3()
{ {
std::cout << "DerivedClass3 destructor" << std::endl; std::cout << "DerivedClass3 destructor" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
std::cout << "Here all should be well: " << std::endl; std::cout << "Here all should be well: " << std::endl;
{ {
DerivedClass3 obj; DerivedClass3 obj;
} }
std::cout << std::endl << "But there not so much, see the missing destructor call! " << std::endl; std::cout << std::endl << "But there not so much, see the missing destructor call! " << std::endl;
{ {
BaseClass3* obj = new DerivedClass3; BaseClass3* obj = new DerivedClass3;
delete obj; delete obj;
} }
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You can see here the derived class destructor is not called! This means if you're for instance deallocating memory in this destructor, the memory will remain unduly allocated. You can see here the derived class destructor is not called! This means if you're for instance deallocating memory in this destructor, the memory will remain unduly allocated.
To circumvent this, you need to declare the destructor virtual in the base class. This way, the derived destructor will be properly called. To circumvent this, you need to declare the destructor virtual in the base class. This way, the derived destructor will be properly called.
### Good practice: should my destructor be virtual? ### Good practice: should my destructor be virtual?
So to put in a nutshell, 99 % of the time: So to put in a nutshell, 99 % of the time:
* If a class of yours is intended to be inherited from, make its destructor `virtual`. * If a class of yours is intended to be inherited from, make its destructor `virtual`.
* If not, mark the class as `final`. * If not, mark the class as `final`.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Good practice: never call a virtual method in a constructor ## Good practice: never call a virtual method in a constructor
A very important point: I lost time years ago with this because I didn't read carefully enough item 9 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)... A very important point: I lost time years ago with this because I didn't read carefully enough item 9 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)...
Due to the way construction occurs, never call a virtual method in a constructor: it won't perform the dynamic binding as you would like it to (and your compiler won't help you here). Due to the way construction occurs, never call a virtual method in a constructor: it won't perform the dynamic binding as you would like it to (and your compiler won't help you here).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
struct BaseClass4 struct BaseClass4
{ {
BaseClass4(); BaseClass4();
virtual std::string ClassName() const; virtual std::string ClassName() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct DerivedClass4 : public BaseClass4 struct DerivedClass4 : public BaseClass4
{ {
DerivedClass4() = default; DerivedClass4() = default;
virtual std::string ClassName() const; virtual std::string ClassName() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
BaseClass4::BaseClass4() BaseClass4::BaseClass4()
{ {
std::cout << "Hello! I'm " << ClassName() << std::endl; std::cout << "Hello! I'm " << ClassName() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
std::string BaseClass4::ClassName() const std::string BaseClass4::ClassName() const
{ {
return "BaseClass4"; return "BaseClass4";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
std::string DerivedClass4::ClassName() const std::string DerivedClass4::ClassName() const
{ {
return "DerivedClass4"; return "DerivedClass4";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
DerivedClass4 object; // not working by stack allocation... DerivedClass4 object; // not working by stack allocation...
DerivedClass4* object2 = new DerivedClass4; // neither by heap allocation! DerivedClass4* object2 = new DerivedClass4; // neither by heap allocation!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There is unfortunately no way to circumvent this; just some hack tactics (see for instance the _VirtualConstructor_ idiom on the [isocpp FAQ](https://isocpp.org/wiki/faq/virtual-functions)). There is unfortunately no way to circumvent this; just some hack tactics (see for instance the _VirtualConstructor_ idiom on the [isocpp FAQ](https://isocpp.org/wiki/faq/virtual-functions)).
When I need this functionality, I usually define an `Init()` method to call after the constructor, which includes the calls to virtual methods: When I need this functionality, I usually define an `Init()` method to call after the constructor, which includes the calls to virtual methods:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
struct BaseClass5 struct BaseClass5
{ {
BaseClass5() = default; BaseClass5() = default;
void Init(); void Init();
virtual std::string ClassName() const; virtual std::string ClassName() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct DerivedClass5 : public BaseClass5 struct DerivedClass5 : public BaseClass5
{ {
DerivedClass5() = default; DerivedClass5() = default;
virtual std::string ClassName() const; virtual std::string ClassName() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void BaseClass5::Init() void BaseClass5::Init()
{ {
std::cout << "Hello! I'm " << ClassName() << std::endl; std::cout << "Hello! I'm " << ClassName() << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
std::string BaseClass5::ClassName() const std::string BaseClass5::ClassName() const
{ {
return "BaseClass5"; return "BaseClass5";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
std::string DerivedClass5::ClassName() const std::string DerivedClass5::ClassName() const
{ {
return "DerivedClass5"; return "DerivedClass5";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
DerivedClass5 object; // ok by stack allocation DerivedClass5 object; // ok by stack allocation
object.Init(); object.Init();
DerivedClass5* object2 = new DerivedClass5; // same by heap allocation DerivedClass5* object2 = new DerivedClass5; // same by heap allocation
object2->Init(); object2->Init();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
But it's not perfect either: if the end-user forget to call the `Init()` method the class could be ill-constructed (to avoid this either I provide manually a check mechanism or I make sure the class is private stuff not intended to be used directly by the end-user). But it's not perfect either: if the end-user forget to call the `Init()` method the class could be ill-constructed (to avoid this either I provide manually a check mechanism or I make sure the class is private stuff not intended to be used directly by the end-user).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 8](./7b-hands-on.ipynb) # [Getting started in C++](./) - [Object programming](./0-main.ipynb) - [Hands-on 8](./7b-hands-on.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **EXERCISE 21: base class `TestDisplay`** ### **EXERCISE 21: base class `TestDisplay`**
Create a base class `TestDisplay` from which both `TestDisplayPowerOfTwoApprox` and `TestDisplaySum` will inherit publicly. Create a base class `TestDisplay` from which both `TestDisplayPowerOfTwoApprox` and `TestDisplaySum` will inherit publicly.
This class: This class:
* Should get a constructor which sets the resolution (respectively 100 or 1000 for our two derived classes) * Should get a constructor which sets the resolution (respectively 100 or 1000 for our two derived classes)
* Should define the `RoundToInteger` enum (as protected) * Should define the `RoundToInteger` enum (as protected)
* Includes a protected method named `PrintLine()` that will replace the `PrintLine()` we introduced in previous exercise. * Includes a protected method named `PrintLine()` that will replace the `PrintLine()` we introduced in previous exercise.
The constructors of derived classes will of course have to be modified accordingly: so far we relied on default ones. The constructors of derived classes will of course have to be modified accordingly: so far we relied on default ones.
**Note:** If you're using clang with warnings activated, you may get this warning: **Note:** If you're using clang with warnings activated, you may get this warning:
```shell ```shell
warning: 'TestDisplay' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables] warning: 'TestDisplay' has no out-of-line virtual method definitions; its vtable will be emitted in every translation unit [-Wweak-vtables]
``` ```
To fix it, you should define a virtual method that is not inlined - i.e. its definition is neither in the class declaration directly or defined with `inline` keyword. To fix it, you should define a virtual method that is not inlined - i.e. its definition is neither in the class declaration directly or defined with `inline` keyword.
In our case we define only one virtual method which doesn't really count as it is the virtual destructor. So we have to define one - even if it is not used. The proposed solution does so by adding a method not intended to be ever called. In our case we define only one virtual method which doesn't really count as it is the virtual destructor. So we have to define one - even if it is not used. The proposed solution does so by adding a method not intended to be ever called.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **EXERCISE 22: inherit from `TestDisplayPowerOfTwoApprox`** ### **EXERCISE 22: inherit from `TestDisplayPowerOfTwoApprox`**
We would like to get back former output in which we got first all outputs for 0.65, then all the ones for 0.35. We would like to get back former output in which we got first all outputs for 0.65, then all the ones for 0.35.
To do so, we will create two classes `TestDisplayPowerOfTwoApprox065` and `TestDisplayPowerOfTwoApprox035` that inherits from `TestDisplayPowerOfTwoApprox`. To do so, we will create two classes `TestDisplayPowerOfTwoApprox065` and `TestDisplayPowerOfTwoApprox035` that inherits from `TestDisplayPowerOfTwoApprox`.
Of course, we still abide by the DRY principle and we want to specialize only the code related to `Do()` method. Of course, we still abide by the DRY principle and we want to specialize only the code related to `Do()` method.
**Note:** The design proposed here is clearly not the best - it is just a pretense to practise virtual methods and encapsulation. **Note:** The design proposed here is clearly not the best - it is just a pretense to practise virtual methods and encapsulation.
The `main()` to use: The `main()` to use:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
TestDisplayPowerOfTwoApprox065 test_display_approx065(100); TestDisplayPowerOfTwoApprox065 test_display_approx065(100);
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
test_display_approx065.Do(nbits); test_display_approx065.Do(nbits);
std::cout << std::endl; std::cout << std::endl;
TestDisplayPowerOfTwoApprox035 test_display_approx035(100); TestDisplayPowerOfTwoApprox035 test_display_approx035(100);
for (int nbits = 2; nbits <= 8; nbits += 2) for (int nbits = 2; nbits <= 8; nbits += 2)
test_display_approx035.Do(nbits); test_display_approx035.Do(nbits);
std::cout << std::endl; std::cout << std::endl;
TestDisplaySumOfMultiply test_display_sum_of_multiply(1000); TestDisplaySumOfMultiply test_display_sum_of_multiply(1000);
for (int nbits = 1; nbits <= 8; ++nbits) for (int nbits = 1; nbits <= 8; ++nbits)
test_display_sum_of_multiply.Do(nbits); test_display_sum_of_multiply.Do(nbits);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
_Expected output:_ _Expected output:_
``` ```
[With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100] [With 2 bits]: 0.65 ~ 0.75 (3 / 2^2) [error = 15/100]
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100] [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 4/100]
[With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100] [With 6 bits]: 0.65 ~ 0.65625 (42 / 2^6) [error = 1/100]
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100] [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 0/100]
[With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100] [With 2 bits]: 0.35 ~ 0.375 (3 / 2^3) [error = 7/100]
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100] [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 2/100]
[With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100] [With 6 bits]: 0.35 ~ 0.351562 (45 / 2^7) [error = 0/100]
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100] [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 0/100]
[With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000] [With 1 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 2965 [error = 254/1000]
[With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000] [With 2 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4448 [error = 119/1000]
[With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000] [With 3 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4008 [error = 8/1000]
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000] [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 30/1000]
[With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000] [With 5 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3967 [error = 2/1000]
[With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000] [With 6 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 4004 [error = 7/1000]
[With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000] [With 7 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3977 [error = 0/1000]
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000] [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2/1000]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **EXERCISE 23: Toward a `TestDisplayContainer` class** ### **EXERCISE 23: Toward a `TestDisplayContainer` class**
We would like to introduce an object whose purpose is to store the various `TestDisplay` class and call for each of them the `Do` method. We would like to introduce an object whose purpose is to store the various `TestDisplay` class and call for each of them the `Do` method.
The declaration of the class should look like: The declaration of the class should look like:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class TestDisplayContainer class TestDisplayContainer
{ {
public: public:
//! Maximum number of objects that might be stored (to avoid magic number) //! Maximum number of objects that might be stored (to avoid magic number)
static constexpr std::size_t MAX_ELTS { 3ul }; static constexpr std::size_t MAX_ELTS { 3ul };
//! Add a new test_display_register. //! Add a new test_display_register.
//! At each call, the item to be registered is put at the first available position and internal current_position_ //! At each call, the item to be registered is put at the first available position and internal current_position_
//! is incremented. If the end-user attempts to register more than three items, the Error() function is called. //! is incremented. If the end-user attempts to register more than three items, the Error() function is called.
void Register(TestDisplay* test_display); void Register(TestDisplay* test_display);
//! For each `TestDisplay` stored within the container, loop over all those bits and print the result on screen. //! For each `TestDisplay` stored within the container, loop over all those bits and print the result on screen.
void Do(int initial_Nbit, int final_Nbit, int increment_Nbit) const; void Do(int initial_Nbit, int final_Nbit, int increment_Nbit) const;
private: private:
//! List of all known `TestDisplay` objects. //! List of all known `TestDisplay` objects.
TestDisplay* list_[MAX_ELTS]; TestDisplay* list_[MAX_ELTS];
//! Index to place the next register object. If '3', no more object may be registered. //! Index to place the next register object. If '3', no more object may be registered.
std::size_t current_position_ {}; std::size_t current_position_ {};
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You will need to add a sanity check in constructor; in case of failure use at the moment the following function (that should already be in the file): You will need to add a sanity check in constructor; in case of failure use at the moment the following function (that should already be in the file):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
//! Function for error handling. We will see later how to fulfill the same functionality more properly. //! Function for error handling. We will see later how to fulfill the same functionality more properly.
[[noreturn]] void Error(std::string explanation) [[noreturn]] void Error(std::string explanation)
{ {
std::cout << "ERROR: " << explanation << std::endl; std::cout << "ERROR: " << explanation << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The `main()` to use is: The `main()` to use is:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
TestDisplayContainer container; TestDisplayContainer container;
container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); // we change the resolution container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); // we change the resolution
container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); // we change the resolution container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); // we change the resolution
container.Register(new TestDisplaySumOfMultiply(1000000)); // we change the resolution container.Register(new TestDisplaySumOfMultiply(1000000)); // we change the resolution
container.Do(4, 16, 4); container.Do(4, 16, 4);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
_Expected result:_ _Expected result:_
``` ```
[With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 3846154/100000000] [With 4 bits]: 0.65 ~ 0.625 (10 / 2^4) [error = 3846154/100000000]
[With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 240385/100000000] [With 8 bits]: 0.65 ~ 0.648438 (166 / 2^8) [error = 240385/100000000]
[With 12 bits]: 0.65 ~ 0.649902 (2662 / 2^12) [error = 15024/100000000] [With 12 bits]: 0.65 ~ 0.649902 (2662 / 2^12) [error = 15024/100000000]
[With 16 bits]: 0.65 ~ 0.649994 (42598 / 2^16) [error = 939/100000000] [With 16 bits]: 0.65 ~ 0.649994 (42598 / 2^16) [error = 939/100000000]
[With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 1785714/100000000] [With 4 bits]: 0.35 ~ 0.34375 (11 / 2^5) [error = 1785714/100000000]
[With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 111607/100000000] [With 8 bits]: 0.35 ~ 0.349609 (179 / 2^9) [error = 111607/100000000]
[With 12 bits]: 0.35 ~ 0.349976 (2867 / 2^13) [error = 6975/100000000] [With 12 bits]: 0.35 ~ 0.349976 (2867 / 2^13) [error = 6975/100000000]
[With 16 bits]: 0.35 ~ 0.349998 (45875 / 2^17) [error = 436/100000000] [With 16 bits]: 0.35 ~ 0.349998 (45875 / 2^17) [error = 436/100000000]
[With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 29917/1000000] [With 4 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3857 [error = 29917/1000000]
[With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2000/1000000] [With 8 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3968 [error = 2000/1000000]
[With 12 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000] [With 12 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000]
[With 16 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000] [With 16 bits]: 0.65 * 3515 + 0.35 * 4832 = 3976 ~ 3975 [error = 239/1000000]
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**Note:** If you're using clang and activate its warnings (should be the case if you're using our Fedora Docker image), you may get these warnings: **Note:** If you're using clang and activate its warnings (should be the case if you're using our Fedora Docker image), you may get these warnings:
```shell ```shell
/home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:458:5: warning: unsafe buffer access [-Wunsafe-buffer-usage] /home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:458:5: warning: unsafe buffer access [-Wunsafe-buffer-usage]
458 | list_[current_position_] = test_display; 458 | list_[current_position_] = test_display;
| ^~~~~ | ^~~~~
/home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:468:13: warning: unsafe buffer access [-Wunsafe-buffer-usage] /home/dev_cpp/training_cpp/2-ObjectProgramming/exercise23.cpp:468:13: warning: unsafe buffer access [-Wunsafe-buffer-usage]
468 | list_[i]->Do(nbits); 468 | list_[i]->Do(nbits);
``` ```
The compiler is really right here; the code we proposed to limit the number objects is very clunky. We will for the sake of the tutorial keep them quite a while; however in a true codebase you should really strive to fix such warnings as soon as they pop up (we will eventually get there but once again for the purpose of the lecture it's not our top priority). The compiler is really right here; the code we proposed to limit the number objects is very clunky. We will for the sake of the tutorial keep them quite a while; however in a true codebase you should really strive to fix such warnings as soon as they pop up (we will eventually get there but once again for the purpose of the lecture it's not our top priority).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **EXERCISE 24: dynamic allocation of array** ### **EXERCISE 24: dynamic allocation of array**
Instead of setting an arbitrary size of 3, we will now add a size dynamically in `TestDisplayContainer` constructor; the internal storage will now be: Instead of setting an arbitrary size of 3, we will now add a size dynamically in `TestDisplayContainer` constructor; the internal storage will now be:
``` ```
TestDisplay** list_; TestDisplay** list_;
``` ```
meaning we will store an array of pointers (don't worry, we will see later how to avoid such monstruosities... but it is useful nonetheless to try them a bit). meaning we will store an array of pointers (don't worry, we will see later how to avoid such monstruosities... but it is useful nonetheless to try them a bit).
Constructor must now: Constructor must now:
* Allocate the array of `TestDisplay*` with a **capacity** given as its argument (the capacity being the number of elements that *might* be stored inside - we'll see the chosen name is not a whim). * Allocate the array of `TestDisplay*` with a **capacity** given as its argument (the capacity being the number of elements that *might* be stored inside - we'll see the chosen name is not a whim).
* Keep track of the capacity (the related data attribute should be constant: we don't intend to modify the capacity of the array after construction). * Keep track of the capacity (the related data attribute should be constant: we don't intend to modify the capacity of the array after construction).
* Set each element to `nullptr`. * Set each element to `nullptr`.
Destructor must of course take care of deallocating properly the memory. Destructor must of course take care of deallocating properly the memory.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### **EXERCISE 25: transform `TestDisplayContainer::Do()` into a free function** ### **EXERCISE 25: transform `TestDisplayContainer::Do()` into a free function**
We probably went a bridge too far: it is useful to provide an object which contains several `TestDisplay` together, but making it take in charge the loop might not be that good an idea (in a real program you might for instance interact with this container by another mean than the pre-defined loop). We probably went a bridge too far: it is useful to provide an object which contains several `TestDisplay` together, but making it take in charge the loop might not be that good an idea (in a real program you might for instance interact with this container by another mean than the pre-defined loop).
Replace the `TestDisplayContainer::Do()` method by a free function with signature: Replace the `TestDisplayContainer::Do()` method by a free function with signature:
```c++ ```c++
void Loop(int initial_Nbit, int final_Nbit, int increment_Nbit, const TestDisplayContainer& container) void Loop(int initial_Nbit, int final_Nbit, int increment_Nbit, const TestDisplayContainer& container)
``` ```
To do so, you will need to add several methods to `TestDisplayContainer`: To do so, you will need to add several methods to `TestDisplayContainer`:
- A method that returns the **size** (i.e. the number of non nullptr `TestDisplay*` stored), which will be required to loop over the relevant elements. - A method that returns the **size** (i.e. the number of non nullptr `TestDisplay*` stored), which will be required to loop over the relevant elements.
- A method to access the `i`-th element stored in the table. Signature might be: - A method to access the `i`-th element stored in the table. Signature might be:
```c++ ```c++
const TestDisplay& GetElement(std::size_t i) const const TestDisplay& GetElement(std::size_t i) const
``` ```
(but others are also possible - you may prefer to return a pointer rather than a reference). (but others are also possible - you may prefer to return a pointer rather than a reference).
New `main()` is: New `main()` is:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
TestDisplayContainer container(3ul); TestDisplayContainer container(3ul);
container.Register(new TestDisplayPowerOfTwoApprox065(100000000)); container.Register(new TestDisplayPowerOfTwoApprox065(100000000));
container.Register(new TestDisplayPowerOfTwoApprox035(100000000)); container.Register(new TestDisplayPowerOfTwoApprox035(100000000));
container.Register(new TestDisplaySumOfMultiply(1000000)); container.Register(new TestDisplaySumOfMultiply(1000000));
Loop(4, 16, 4, container); Loop(4, 16, 4, container);
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
...@@ -33,16 +33,15 @@ ...@@ -33,16 +33,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
%% 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:
## 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++
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.
friend Vector Add(const Vector& v1, const Vector& v2); friend Vector Add(const Vector& v1, const Vector& v2);
public : public :
Vector(double x, double y, double z); Vector(double x, double y, double z);
Vector() = default; Vector() = default;
void Print() const; void Print() const;
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++
Vector::Vector(double x, double y, double z) Vector::Vector(double x, double y, double z)
: x_(x), : x_(x),
y_(y), y_(y),
z_(z) z_(z)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vector Add(const Vector& v1, const Vector& v2) Vector Add(const Vector& v1, const Vector& v2)
{ {
Vector ret; Vector ret;
ret.x_ = v1.x_ + v2.x_; ret.x_ = v1.x_ + v2.x_;
ret.y_ = v1.y_ + v2.y_; ret.y_ = v1.y_ + v2.y_;
ret.z_ = v1.z_ + v2.z_; ret.z_ = v1.z_ + v2.z_;
return ret; return ret;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void Vector::Print() const void Vector::Print() const
{ {
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Vector v1(3., 5., 7.); Vector v1(3., 5., 7.);
Vector v2(7., 5., 3.); Vector v2(7., 5., 3.);
Vector v3 = Add(v1, v2); Vector v3 = Add(v1, v2);
v3.Print(); v3.Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
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++
{ {
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 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. 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. In the following we will just replace the `Add` method by `operator+`. To overload an operator, the syntax is just the keyword **operator** followed by the operator to overload. In the following we will just replace the `Add` method by `operator+`.
The following code illustrate how to do so... but unfortunately doesn't run with Xeus Cling (you may play with it [@Coliru](https://coliru.stacked-crooked.com/a/765b9dc1e2b73c71)). The following code illustrate how to do so:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// DOESN'T RUN WITH XEUS-CLING %%cppmagics cppyy/cppdef
// < We need to help the kernel interpret properly the code below, which is perfectly valid C++. Don't bother about this magics!
#include <iostream> #include <iostream>
class VectorPlus class VectorPlus
{ {
public : public :
VectorPlus(double x, double y, double z); VectorPlus(double x, double y, double z);
VectorPlus() = default; VectorPlus() = default;
void Print() const; void Print() const;
// Friendship as free function operator+ wouldn't otherwise be allowed to access data attributes. // Friendship as free function operator+ wouldn't otherwise be allowed to access data attributes.
friend VectorPlus operator+(const VectorPlus& v1, const VectorPlus& v2); friend VectorPlus operator+(const VectorPlus& v1, const VectorPlus& v2);
private : private :
double x_ = 0.; double x_ = 0.;
double y_ = 0.; double y_ = 0.;
double z_ = 0.; double z_ = 0.;
}; };
VectorPlus::VectorPlus(double x, double y, double z) VectorPlus::VectorPlus(double x, double y, double z)
: x_(x), : x_(x),
y_(y), y_(y),
z_(z) z_(z)
{ } { }
void VectorPlus::Print() const void VectorPlus::Print() const
{ {
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
} }
VectorPlus operator+(const VectorPlus& v1, const VectorPlus& v2) VectorPlus operator+(const VectorPlus& v1, const VectorPlus& v2)
{ {
// Provides a symmetric implementation of operator +: both vectors are at the same level! // Provides a symmetric implementation of operator +: both vectors are at the same level!
VectorPlus ret; VectorPlus ret;
ret.x_ = v1.x_ + v2.x_; ret.x_ = v1.x_ + v2.x_;
ret.y_ = v1.y_ + v2.y_; ret.y_ = v1.y_ + v2.y_;
ret.z_ = v1.z_ + v2.z_; ret.z_ = v1.z_ + v2.z_;
return ret; return ret;
} }
```
%% Cell type:code id: tags:
int main(int argc, char** argv) ``` c++
{ VectorPlus v1(3., 5., 7.);
VectorPlus v1(3., 5., 7.); VectorPlus v2(7., 5., 3.);
VectorPlus v2(7., 5., 3.);
VectorPlus v3 = v1 + v2; // Nicer syntax!
v3.Print();
VectorPlus v4 = operator+(v1, v2); // but "usual" method syntax is possible as well VectorPlus v3 = v1 + v2; // Nicer syntax!
v4.Print(); v3.Print();
return EXIT_SUCCESS; VectorPlus v4 = operator+(v1, v2); // but "usual" method syntax is possible as well
} v4.Print();
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It should be noted that for most operators it is also possible to define them as a class method instead: It should be noted that for most operators it is also possible to define them as a class method instead:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics cppyy/cppdef
class VectorPlusAsMethod class VectorPlusAsMethod
{ {
public : public :
VectorPlusAsMethod(double x, double y, double z); VectorPlusAsMethod(double x, double y, double z);
VectorPlusAsMethod() = default; VectorPlusAsMethod() = default;
void Print() const; void Print() const;
// I would rather put the definition outside but Xeus-cling doesn't seem to accept this. VectorPlusAsMethod operator+(const VectorPlusAsMethod& v) const;
VectorPlusAsMethod operator+(const VectorPlusAsMethod& v) const
{
VectorPlusAsMethod ret;
ret.x_ = x_ + v.x_;
ret.y_ = y_ + v.y_;
ret.z_ = z_ + v.z_;
return ret;
}
private : private :
double x_ = 0.; double x_ {};
double y_ = 0.; double y_ {};
double z_ = 0.; double z_ {};
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics cppyy/cppdef
VectorPlusAsMethod::VectorPlusAsMethod(double x, double y, double z) VectorPlusAsMethod::VectorPlusAsMethod(double x, double y, double z)
: x_(x), : x_{x},
y_(y), y_{y},
z_(z) z_{z}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void VectorPlusAsMethod::Print() const void VectorPlusAsMethod::Print() const
{ {
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
VectorPlusAsMethod VectorPlusAsMethod::operator+(const VectorPlusAsMethod& v) const
{ {
VectorPlusAsMethod ret;
ret.x_ = x_ + v.x_;
ret.y_ = y_ + v.y_;
ret.z_ = z_ + v.z_;
return ret;
}
```
%% Cell type:code id: tags:
``` c++
VectorPlusAsMethod v1(3., 5., 7.); VectorPlusAsMethod v1(3., 5., 7.);
VectorPlusAsMethod v2(7., 5., 3.); VectorPlusAsMethod v2(7., 5., 3.);
VectorPlusAsMethod v3 = v1 + v2; VectorPlusAsMethod v3 = v1 + v2;
v3.Print(); v3.Print();
VectorPlusAsMethod v4 = v1.operator+(v2); // but "usual" method syntax is possible as well VectorPlusAsMethod v4 = v1.operator+(v2); // but "usual" method syntax is possible as well
v4.Print(); v4.Print();
}
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
We see here in the definition of the `operator+` that both `VectorPlusAsMethod` objects added aren't symmetric: one is the data attribute while the other is the data attribute of another object given as an argument. If is often advised to rather use the free function version to avoid this asymmetry, but it is mostly a matter of taste as both are working. We see here in the definition of the `operator+` that both `VectorPlusAsMethod` objects added aren't symmetric: one is the data attribute while the other is the data attribute of another object given as an argument. If is often advised to rather use the free function version to avoid this asymmetry, but it is mostly a matter of taste as both are working.
As a side note, please remark the `VectorPlusAsMethod::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 `VectorPlusAsMethod::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.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Operator between different types ## Operator between different types
It is also possible to define an operator which acts upon two objects of different nature: It is also possible to define an operator which acts upon two objects of different nature:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics cppyy/cppdef
class VectorPlusDouble class VectorPlusDouble
{ {
public : public :
VectorPlusDouble(double x, double y, double z); VectorPlusDouble(double x, double y, double z);
VectorPlusDouble() = default; VectorPlusDouble() = default;
void Print() const; void Print() const;
// Defined in the class declaration due to Xeus-cling limitation. VectorPlusDouble operator+(double value) const;
VectorPlusDouble operator+(double value) const
{
VectorPlusDouble ret;
ret.x_ = x_ + value;
ret.y_ = y_ + value;
ret.z_ = z_ + value;
return ret;
}
private : private :
double x_ = 0.; double x_ {};
double y_ = 0.; double y_ {};
double z_ = 0.; double z_ {};
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
void VectorPlusDouble::Print() const void VectorPlusDouble::Print() const
{ {
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics cppyy/cppdef
VectorPlusDouble::VectorPlusDouble(double x, double y, double z) VectorPlusDouble::VectorPlusDouble(double x, double y, double z)
: x_(x), : x_{x},
y_(y), y_{y},
z_(z) z_{z}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
VectorPlusDouble VectorPlusDouble::operator+(double value) const
{
VectorPlusDouble ret;
ret.x_ = x_ + value;
ret.y_ = y_ + value;
ret.z_ = z_ + value;
return ret;
}
```
%% Cell type:code id: tags:
``` c++
{ {
VectorPlusDouble vector(5., 3.2, -1.); VectorPlusDouble vector(5., 3.2, -1.);
VectorPlusDouble vector_plus_5 = vector + 5.; VectorPlusDouble vector_plus_5 = vector + 5.;
vector_plus_5.Print(); vector_plus_5.Print();
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, pay attention to the fact this operator is not commutative. It is indeed a shortcut to However, pay attention to the fact this operator is not commutative. It is indeed a shortcut to
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
VectorPlusDouble vector(5., 3.2, -1.); VectorPlusDouble vector(5., 3.2, -1.);
VectorPlusDouble vector_plus_5 = vector.operator+(5.); VectorPlusDouble vector_plus_5 = vector.operator+(5.);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
and the following won't compile: and the following won't compile:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
VectorPlusDouble vector(5., 3.2, -1.); VectorPlusDouble vector(5., 3.2, -1.);
VectorPlusDouble vector_plus_5 = 5. + vector; // COMPILATION ERROR! VectorPlusDouble vector_plus_5 = 5. + vector; // COMPILATION ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% 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, available [@Coliru](https://coliru.stacked-crooked.com/a/c03fc40e0f0a8ea0)). 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.
Of course, it is a **good practice** to define one in way of the other: Of course, it is a **good practice** in this case to define one in way of the other:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Won't compile in Xeus-cling %%cppmagics cppyy/cppdef
#include <cstdlib>
#include <iostream>
class VectorPlusDoubleCommutative class VectorPlusDoubleCommutative
{ {
public : public :
VectorPlusDoubleCommutative(double x, double y, double z); VectorPlusDoubleCommutative(double x, double y, double z);
VectorPlusDoubleCommutative() = default; VectorPlusDoubleCommutative() = default;
void Print() const; void Print() const;
friend VectorPlusDoubleCommutative operator+(const VectorPlusDoubleCommutative& v, double value); friend VectorPlusDoubleCommutative operator+(const VectorPlusDoubleCommutative& v, double value);
friend VectorPlusDoubleCommutative operator+(double value, const VectorPlusDoubleCommutative& v); friend VectorPlusDoubleCommutative operator+(double value, const VectorPlusDoubleCommutative& v);
private : private :
double x_ = 0.; double x_ {};
double y_ = 0.; double y_ {};
double z_ = 0.; double z_ {};
}; };
```
%% Cell type:code id: tags:
``` c++
#include <iostream>
void VectorPlusDoubleCommutative::Print() const void VectorPlusDoubleCommutative::Print() const
{ {
std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; std::cout << "(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl;
} }
```
%% Cell type:code id: tags:
``` c++
%%cppmagics cppyy/cppdef
VectorPlusDoubleCommutative::VectorPlusDoubleCommutative(double x, double y, double z) VectorPlusDoubleCommutative::VectorPlusDoubleCommutative(double x, double y, double z)
: x_(x), : x_{x},
y_(y), y_{y},
z_(z) z_{z}
{ } { }
```
%% Cell type:code id: tags:
``` c++
VectorPlusDoubleCommutative operator+(const VectorPlusDoubleCommutative& v, double value) VectorPlusDoubleCommutative operator+(const VectorPlusDoubleCommutative& v, double value)
{ {
VectorPlusDoubleCommutative ret; VectorPlusDoubleCommutative ret;
ret.x_ = v.x_ + value; ret.x_ = v.x_ + value;
ret.y_ = v.y_ + value; ret.y_ = v.y_ + value;
ret.z_ = v.z_ + value; ret.z_ = v.z_ + value;
return ret; return ret;
} }
```
%% Cell type:code id: tags:
``` c++
VectorPlusDoubleCommutative operator+(double value, const VectorPlusDoubleCommutative& v) VectorPlusDoubleCommutative operator+(double value, const VectorPlusDoubleCommutative& v)
{ {
return v + value; // good practice: make it rely upon the other `operator+` defined! return v + value; // good practice: make it rely upon the other `operator+` defined!
} }
```
%% Cell type:code id: tags:
int main(int argc, char** argv) ``` c++
{ {
VectorPlusDoubleCommutative vector(5., 3.2, -1.); VectorPlusDoubleCommutative vector(5., 3.2, -1.);
VectorPlusDoubleCommutative vector_plus_5 = vector + 5.; VectorPlusDoubleCommutative vector_plus_5 = vector + 5.;
VectorPlusDoubleCommutative vector_plus_5_commutated = 5. + vector; VectorPlusDoubleCommutative vector_plus_5_commutated = 5. + vector;
vector_plus_5.Print(); vector_plus_5.Print();
vector_plus_5_commutated.Print(); vector_plus_5_commutated.Print();
return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Limitations ## Limitations
You cannot change: You cannot change:
* The number of operators arguments * The number of operators arguments
* The [precedence rules](https://en.cppreference.com/w/cpp/language/operator_precedence) (between `+` and `*` for instance) * The [precedence rules](https://en.cppreference.com/w/cpp/language/operator_precedence) (between `+` and `*` for instance)
You can't _invent_ new operators, but only redefine operators in the following list (taken from [Operators in C and C++](https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B), please consult for more details): You can't _invent_ new operators, but only redefine operators in the following list (taken from [Operators in C and C++](https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B), please consult for more details):
* Arithmetic operators: `+ - * / % ++ --` * Arithmetic operators: `+ - * / % ++ --`
* Comparison operators: `== != <= >= < > <=>` * Comparison operators: `== != <= >= < > <=>`
* Logical operators: `! && ||` * Logical operators: `! && ||`
* Bitwise operators: `~ & | ^ << >>` * Bitwise operators: `~ & | ^ << >>`
* Assignment operators: `= += -= *= /= %= &= |= ^= <<= >>=` * Assignment operators: `= += -= *= /= %= &= |= ^= <<= >>=`
* Member and pointer operators: `[] * & -> ->*` * Member and pointer operators: `[] * & -> ->*`
* Other operators: `() , "" new new[] delete delete[]` * Other operators: `() , "" new new[] delete delete[]`
* Conversion operators - see next section * Conversion operators - see next section
That list might be incomplete: `""` operator was introduced in C++ 11 (cf. [this blog post](https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/)), and `<=>` operator was introduced in C++ 20, for instance. That list might be incomplete: `""` operator was introduced in C++ 11 (cf. [this blog post](https://www.fluentcpp.com/2016/12/08/strong-types-for-strong-interfaces/)), and `<=>` operator was introduced in C++ 20, for instance.
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++
#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_ { }; int numerator_ { };
int denominator_ { }; int denominator_ { };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Rational::Rational(int numerator, int denominator) Rational::Rational(int numerator, int denominator)
: numerator_(numerator), : numerator_(numerator),
denominator_(denominator) denominator_(denominator)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Rational::operator int() const Rational::operator int() const
{ {
return numerator_ / denominator_; return numerator_ / denominator_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Rational::operator double() const Rational::operator double() const
{ {
return static_cast<double>(numerator_) / denominator_; return static_cast<double>(numerator_) / denominator_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
Rational val_r(15, 7); Rational val_r(15, 7);
std::cout << "val as double: " << static_cast<double>(val_r) << std::endl ; std::cout << "val as double: " << static_cast<double>(val_r) << std::endl ;
std::cout << "val as integer: " << static_cast<int>(val_r) << std::endl ; std::cout << "val as integer: " << static_cast<int>(val_r) << std::endl ;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As for constructors, you should add the keyword `explicit` in the class declaration to ensure no implicit conversion occurs: As for constructors, you should add the keyword `explicit` in the class declaration to ensure no implicit conversion occurs:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Won't run in Xeus-cling, as it's not within the class declaration // Won't run in the notebook, as it's not declared within the class declaration
explicit operator int() const; explicit operator int() const;
explicit operator double() const; explicit operator double() const;
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
...@@ -78,16 +78,15 @@ ...@@ -78,16 +78,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
%% 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:
## Operator <, >, <=, >= ## 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?) 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):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Xeus-cling issue: doesn't compile! %%cppmagics cppyy/cppdef
#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_ {};
int denominator_ = 0; int denominator_ {};
}; };
```
%% Cell type:code id: tags:
``` c++
%%cppmagics cppyy/cppdef
Rational::Rational(int numerator, int denominator) Rational::Rational(int numerator, int denominator)
: numerator_(numerator), : numerator_{numerator},
denominator_(denominator) denominator_{denominator}
{ } { }
```
%% Cell type:code id: tags:
``` c++
Rational::operator double() const Rational::operator double() const
{ {
return static_cast<double>(numerator_) / denominator_; return static_cast<double>(numerator_) / denominator_;
} }
```
%% Cell type:code id: tags:
``` c++
bool operator<(const Rational& lhs, const Rational& rhs) bool operator<(const Rational& lhs, const Rational& rhs)
{ {
return static_cast<double>(lhs) < static_cast<double>(rhs); return static_cast<double>(lhs) < static_cast<double>(rhs);
} }
```
%% Cell type:code id: tags:
int main(int argc, char** argv) ``` c++
{ {
Rational r1(15, 7); Rational r1(15, 7);
Rational r2(27, 4); Rational r2(27, 4);
std::cout << (r1 < r2) << std::endl; std::cout << "Is r1 lower than r2? -> " << std::boolalpha << (r1 < r2) << std::endl;
return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Defining `operator<` does not automatically define 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: Defining `operator<` does not automatically define 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 are allowed by the language to 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 are allowed by the language to 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 independent from each other: defining one **doesn't** define the other one... * They are independent 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++
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... friend bool operator==(const Vector& lhs, const Vector& rhs);
// 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: private:
int* array_ = nullptr; int* array_ { nullptr };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vector::Vector(int x, int y, int z) Vector::Vector(int x, int y, int z)
{ {
array_ = new int[3]; array_ = new int[3];
array_[0] = x; array_[0] = x;
array_[1] = y; array_[1] = y;
array_[2] = z; array_[2] = z;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vector::~Vector() Vector::~Vector()
{ {
delete[] array_; delete[] array_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
bool operator==(const Vector& lhs, const Vector& rhs)
{
return lhs.array_ == rhs.array_; // BUG!
}
```
%% Cell type:code id: tags:
``` c++
#include <iostream> #include <iostream>
{ {
Vector v1(3, 4, 5); Vector v1(3, 4, 5);
Vector v2(3, 4, 5); Vector v2(3, 4, 5);
std::cout << "Are equal? : " << (v1 == v2) << std::endl; std::cout << "Are equal? ('true' expected): " << (v1 == v2) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% 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!). 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)): In C++ 17 and below, you have to define all the comparison operators, which can quickly become rather tedious (and error-prone):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Xeus-cling issue: doesn't compile! %%cppmagics cppyy/cppdef
#include <iostream> #include <iostream>
#include <cmath> #include <cmath>
class Rational class Rational20
{ {
public : public :
explicit Rational(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_ {};
int denominator_ = 0; int denominator_ {};
}; };
```
Rational::Rational(int numerator, int denominator) %% Cell type:code id: tags:
: numerator_(numerator),
denominator_(denominator) ``` c++
%%cppmagics cppyy/cppdef
Rational20::Rational20(int numerator, int denominator)
: numerator_{numerator},
denominator_{denominator}
{ } { }
```
Rational::operator double() const %% Cell type:code id: tags:
``` c++
Rational20::operator double() const
{ {
return static_cast<double>(numerator_) / static_cast<double>(denominator_); return static_cast<double>(numerator_) / static_cast<double>(denominator_);
} }
```
bool operator<(const Rational& lhs, const Rational& rhs) %% Cell type:code id: tags:
``` c++
bool operator<(const Rational20& lhs, const Rational20& rhs)
{ {
return static_cast<double>(lhs) < static_cast<double>(rhs); return static_cast<double>(lhs) < static_cast<double>(rhs);
} }
```
bool operator==(const Rational& lhs, const Rational& rhs) %% Cell type:code id: tags:
``` c++
bool operator==(const Rational20& lhs, const Rational20& rhs)
{ {
return std::fabs(static_cast<double>(lhs) - static_cast<double>(rhs) ) < 1.e-16; // crude... return std::fabs(static_cast<double>(lhs) - static_cast<double>(rhs) ) < 1.e-16; // crude...
} }
```
%% Cell type:code id: tags:
bool operator!=(const Rational& lhs, const Rational& rhs) ``` c++
bool operator!=(const Rational20& lhs, const Rational20& rhs)
{ {
return !operator==(lhs, rhs); return !operator==(lhs, rhs);
} }
```
%% Cell type:code id: tags:
bool operator>(const Rational& lhs, const Rational& rhs) ``` c++
bool operator>(const Rational20& lhs, const Rational20& rhs)
{ {
return operator<(rhs, lhs); return operator<(rhs, lhs);
} }
```
bool operator>=(const Rational& lhs, const Rational& rhs) %% Cell type:code id: tags:
``` c++
bool operator>=(const Rational20& lhs, const Rational20& rhs)
{ {
return !operator<(lhs, rhs); return !operator<(lhs, rhs);
} }
```
bool operator<=(const Rational& lhs, const Rational& rhs) %% Cell type:code id: tags:
``` c++
bool operator<=(const Rational20& lhs, const Rational20& rhs)
{ {
return !operator>(lhs, rhs); return !operator>(lhs, rhs);
} }
```
%% Cell type:code id: tags:
int main() ``` c++
{ {
Rational a(5, 2); Rational20 a(5, 2);
Rational b(17, 5); 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;
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: %% 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)): C++ 20 introduces the so-called spaceship operator, which enables defining more concisely all those operators:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Xeus-cling issue: doesn't compile! (and C++ 20 not yet supported there anyway) %%cppmagics clang
// We're using native compiler here as the standard library embedded with `cling` - the interpreter used by our kernel -
// doesn't support yet `std::partial_ordering`.
#include <iostream>
#include <cmath> #include <cmath>
#include <compare>
#include <iostream>
// ===========================
// Declarations
// ===========================
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_ {};
int denominator_ = 0; int denominator_ {};
}; };
bool operator==(const Rational20& lhs, const Rational20& rhs);
std::partial_ordering operator<=>(const Rational20& lhs, const Rational20& rhs);
// ===========================
// Definitions
// ===========================
Rational20::Rational20(int numerator, int denominator) Rational20::Rational20(int numerator, int denominator)
: numerator_(numerator), : numerator_{numerator},
denominator_(denominator) denominator_{denominator}
{ } { }
Rational20::operator double() const Rational20::operator double() const
{ {
return static_cast<double>(numerator_) / static_cast<double>(denominator_); return static_cast<double>(numerator_) / static_cast<double>(denominator_);
} }
bool operator==(const Rational20& lhs, const Rational20& rhs) bool operator==(const Rational20& lhs, const Rational20& rhs)
{ {
return std::fabs(static_cast<double>(lhs) - static_cast<double>(rhs)) < 1.e-16; // crude... 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) std::partial_ordering operator<=>(const Rational20& lhs, const Rational20& rhs)
{ {
return static_cast<double>(lhs) <=> static_cast<double>(rhs); return static_cast<double>(lhs) <=> static_cast<double>(rhs);
} }
// ===========================
// Main
// ===========================
int main() int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
Rational20 a(5, 2); Rational20 a(5, 2);
Rational20 b(17, 5); 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;
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: %% 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:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct Toolbox struct Toolbox
{ {
unsigned int Nscrewdriver_; unsigned int Nscrewdriver_;
unsigned int Nhammer_; unsigned int Nhammer_;
unsigned int Nnails_; unsigned int Nnails_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Xeus-cling issue: doesn't compile!
bool operator==(const Toolbox& lhs, const Toolbox& rhs) bool operator==(const Toolbox& lhs, const Toolbox& rhs)
{ {
return lhs.Nscrewdriver_ == rhs.Nscrewdriver_ return lhs.Nscrewdriver_ == rhs.Nscrewdriver_
&& lhs.Nhammer_ == rhs.Nhammer_ && lhs.Nhammer_ == rhs.Nhammer_
&& lhs.Nails_ == rhs.Nnails_; && lhs.Nnails_ == rhs.Nnails_;
} }
``` ```
%% Cell type:markdown id: tags: %% 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! 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)): C++ 20 will enable default behaviour for the comparison operators, so in this case you would be able to write instead:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics clang
// We're using native compiler here as the standard library embedded with `cling` - the interpreter used by our kernel -
// doesn't support yet properly spaceship operator.
#include <iostream> #include <iostream>
struct Toolbox struct AutomatedToolbox
{ {
bool operator==(const Toolbox&) const = default; bool operator==(const AutomatedToolbox&) const = default;
auto operator<=>(const Toolbox&) const = default; auto operator<=>(const AutomatedToolbox&) const = default;
unsigned int Nscrewdriver_; unsigned int Nscrewdriver_;
unsigned int Nhammer_; unsigned int Nhammer_;
unsigned int Nnails_; unsigned int Nnails_;
}; };
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
int main(int argc, char** argv)
{ {
Toolbox toolbox1; AutomatedToolbox toolbox1;
toolbox1.Nscrewdriver_ = 5; toolbox1.Nscrewdriver_ = 5;
toolbox1.Nhammer_ = 4; toolbox1.Nhammer_ = 4;
toolbox1.Nnails_ = 200; toolbox1.Nnails_ = 200;
Toolbox toolbox2; AutomatedToolbox toolbox2;
toolbox2.Nscrewdriver_ = 5; toolbox2.Nscrewdriver_ = 5;
toolbox2.Nhammer_ = 4; toolbox2.Nhammer_ = 4;
toolbox2.Nnails_ = 200; toolbox2.Nnails_ = 200;
std::cout << std::boolalpha << (toolbox1 == toolbox2) << std::endl; std::cout << std::boolalpha << (toolbox1 == toolbox2) << std::endl;
std::cout << std::boolalpha << (toolbox1 < toolbox2) << std::endl; std::cout << std::boolalpha << (toolbox1 < toolbox2) << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% 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: 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: %% Cell type:code id: tags:
``` C++17 ``` c++
// What the operator< would look like if we wrote it ourselves: // What the operator< would look like if we wrote it ourselves:
bool operator<(const Rational& lhs, const Rational& rhs) bool operator<(const Toolbox& lhs, const Toolbox& rhs)
{ {
if !(toolbox1.Nscrewdriver_ == toolbox2.Nscrewdriver_) if (lhs.Nscrewdriver_ != rhs.Nscrewdriver_)
return toolbox1.Nscrewdriver_ < toolbox2.Nscrewdriver_; return lhs.Nscrewdriver_ < rhs.Nscrewdriver_;
if !(toolbox1.Nhammer_ == toolbox2.Nhammer_) if (lhs.Nhammer_ != rhs.Nhammer_)
return toolbox1.Nhammer_ < toolbox2.Nhammer_; return lhs.Nhammer_ < rhs.Nhammer_;
if !(toolbox1.Nnails_ == toolbox2.Nnails_) if (lhs.Nnails_ != rhs.Nnails_)
return toolbox1.Nnails_ < toolbox2.Nnails_; return lhs.Nnails_ < rhs.Nnails_;
return false; return false;
} }
``` ```
%% Cell type:markdown id: tags: %% 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. [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). **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 you want).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Stream operators](./3-Stream.ipynb) # [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Stream operators](./3-Stream.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Operator << ## Operator <<
So far, we have very often defined methods and/or functions named `Print()` to provide a visual printing of the content of a class. So far, we have very often defined methods and/or functions named `Print()` to provide a visual printing of the content of a class.
However, it's even better if we could use the `<<` syntax directly, and the way to do so is to provide an overload of `operator<<`. However, it's even better if we could use the `<<` syntax directly, and the way to do so is to provide an overload of `operator<<`.
The recommended way to implement it is to use under the hood a `Print(std::ostream&)` (or whatever you want to call it): The recommended way to implement it is to use under the hood a `Print(std::ostream&)` (or whatever you want to call it):
**Xeus-cling** issue: cling doesn't accept operator definition outside of class; please use [@Coliru](https://coliru.stacked-crooked.com/a/04f59249cce6c131)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Doesn't run on Xeus-cling due to definition of an operator outside of a class %%cppmagics cppyy/cppdef
// A ticket has been opened for this: https://github.com/QuantStack/xeus-cling/issues/214
#include <iostream> #include <iostream>
class Rational class Rational
{ {
public : public :
explicit Rational(int numerator, int denominator); explicit Rational(int numerator, int denominator);
// Might even be private; in which case friendship needs to be declared // Might even be private; in which case friendship needs to be declared
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
private : private :
int numerator_ = 0; int numerator_ {};
int denominator_ = 0; int denominator_ {};
}; };
```
%% Cell type:code id: tags:
``` c++
%%cppmagics cppyy/cppdef
Rational::Rational(int numerator, int denominator) Rational::Rational(int numerator, int denominator)
: numerator_(numerator), : numerator_{numerator},
denominator_(denominator) denominator_{denominator}
{ } { }
```
%% Cell type:code id: tags:
``` c++
void Rational::Print(std::ostream& out) const void Rational::Print(std::ostream& out) const
{ {
out << numerator_ << " / " << denominator_; out << numerator_ << " / " << denominator_;
} }
```
%% Cell type:code id: tags:
``` c++
std::ostream& operator<<(std::ostream& out, const Rational& r) std::ostream& operator<<(std::ostream& out, const Rational& r)
{ {
r.Print(out); // see how the bulk of the work is done by the 'Print()' method! r.Print(out); // see how the bulk of the work is done by the 'Print()' method!
return out; return out;
} }
```
%% Cell type:code id: tags:
int main() ``` c++
{ {
Rational r1(22, 7); Rational r1(22, 7);
Rational r2(77, 17); Rational r2(77, 17);
std::cout << "Rational = " << r1 << std::endl; std::cout << "Rational = " << r1 << std::endl;
std::cout << "Call may also be chained due to the signature of the function: " << r1 std::cout << "Call may also be chained due to the signature of the function: " << r1
<< " and " << r2 << std::endl; << " and " << r2 << std::endl;
return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice the slightly weird expected syntax for the `operator<<`: the `std::ostream` appears actually twice; the reason for that is to enable chained calls: Please notice the slightly weird expected syntax for the `operator<<`: the `std::ostream` appears actually twice; the reason for that is to enable chained calls:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Doesn't run on Xeus-cling!
int main()
{ {
Rational r1(22, 7); Rational r1(22, 7);
Rational r2(84, 9); Rational r2(84, 9);
std::cout << "Rationals = " << r1 << " and " << r2 << std::endl; std::cout << "Rationals = " << r1 << " and " << r2 << std::endl;
return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Operator >> ## Operator >>
`operator>>` might be overloaded in a similar way; it might be used for instance to create an object from data read in a file. As usual with `operator>>`, you should be cautious and handle well the case in which the flux becomes invalid: `operator>>` might be overloaded in a similar way; it might be used for instance to create an object from data read in a file. As usual with `operator>>`, you should be cautious and handle well the case in which the flux becomes invalid:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Doesn't run on Xeus-cling due to definition of an operator outside of a class %%cppmagics std::cin/clang
// A ticket has been opened for this: https://github.com/QuantStack/xeus-cling/issues/214
#include <iostream> #include <iostream>
class Rational class Rational
{ {
public : public :
explicit Rational(int numerator, int denominator); explicit Rational(int numerator, int denominator);
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
friend std::istream& operator>>(std::istream& in, Rational& r); friend std::istream& operator>>(std::istream& in, Rational& r);
private: private:
//! Set method to modify numerator and denominator. I made the arbitrary choice to make it private //! Set method to modify numerator and denominator. I made the arbitrary choice to make it private
//! to illustrate friendship. //! to illustrate friendship.
void Set(int numerator, int denominator); void Set(int numerator, int denominator);
private : private :
int numerator_ = 0; int numerator_ {};
int denominator_ = 0; int denominator_ {};
}; };
Rational::Rational(int numerator, int denominator) Rational::Rational(int numerator, int denominator)
: numerator_(numerator), : numerator_{numerator},
denominator_(denominator) denominator_{denominator}
{ } { }
void Rational::Print(std::ostream& out) const void Rational::Print(std::ostream& out) const
{ {
out << numerator_ << " / " << denominator_; out << numerator_ << " / " << denominator_;
} }
std::ostream& operator<<(std::ostream& out, const Rational& r) std::ostream& operator<<(std::ostream& out, const Rational& r)
{ {
r.Print(out); r.Print(out);
return out; return out;
} }
void Rational::Set(int numerator, int denominator) void Rational::Set(int numerator, int denominator)
{ {
numerator_ = numerator; numerator_ = numerator;
denominator_ = denominator; denominator_ = denominator;
} }
std::istream& operator>>(std::istream& in, Rational& r) std::istream& operator>>(std::istream& in, Rational& r)
{ {
int numerator {}, denominator {}; int numerator {}, denominator {};
in >> numerator >> denominator; // Notice the operator>> chained call in >> numerator >> denominator; // Notice the operator>> chained call
if (!in) if (!in)
{ {
// If istream is bad; do not modify 'r' // If istream is bad; do not modify 'r'
return in; return in;
} }
r.Set(numerator, denominator); // ok due to friendship! r.Set(numerator, denominator); // ok due to friendship!
return in; return in;
} }
int main() int main()
{ {
Rational r1(0,0); Rational r1(0,0);
std::cout << "Enter a rational by separating calls by a space" << std::endl; std::cout << "Enter a rational by separating calls by a space" << std::endl;
std::cin >> r1; std::cin >> r1;
if (std::cin) if (std::cin)
std::cout << "Value read is " << r1 << std::endl; std::cout << "Value read is " << r1 << std::endl;
else else
std::cout << "Invalid input!" << std::endl; std::cerr << "Invalid input!" << std::endl;
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This code doesn't run neither in Xeus-cling (due to the operator bug) nor in [Coliru](https://coliru.stacked-crooked.com/) or [Wandbox](https://wandbox.org/) (due to limited `std::cin` support) but you may check it on a local installation. This code is far from perfect: it handles correctly the case gibberish is given through `std::cin`, but if you put more than two blocks separated by spaces the first two ones are used and the others aren't dealt with... As we have already said, proper and complete handling of input stream is though!
Even then, it is not perfect: it handles correctly the case gibberish is given through `std::cin`, but if you put more than two blocks separated by spaces the first two ones are used and the others aren't dealt with... As I said, proper and complete handling of input stream is though!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
...@@ -47,16 +47,15 @@ ...@@ -47,16 +47,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](/notebooks/3-Operators/0-main.ipynb) - [Assignment operator and the canonical form of a class](/notebooks/3-Operators/4-CanonicalForm.ipynb) # [Getting started in C++](./) - [Operators](/notebooks/3-Operators/0-main.ipynb) - [Assignment operator and the canonical form of a class](/notebooks/3-Operators/4-CanonicalForm.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Assignment operator ## Assignment operator
We have not yet addressed one of the most natural operator: the one that might be used to allocate a value to an object. We have not yet addressed one of the most natural operator: the one that might be used to allocate a value to an object.
### Default behaviour (for a simple case) ### Default behaviour (for a simple case)
This one is provided by default: This one is provided by default:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics cppyy/cppdef
#include <iostream> #include <iostream>
class Vector class Vector
{ {
public: public:
Vector(double x, double y, double z); Vector(double x, double y, double z);
Vector& operator=(const Vector&) = default; Vector& operator=(const Vector&) = default;
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
private: private:
double x_ = 0.; double x_ {};
double y_ = 0.; double y_ {};
double z_ = 0.; double z_ {};
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics cppyy/cppdef
Vector::Vector(double x, double y, double z) Vector::Vector(double x, double y, double z)
: x_(x), : x_{x},
y_(y), y_{y},
z_(z) z_{z}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Vector::Print(std::ostream& out) const void Vector::Print(std::ostream& out) const
{ {
out << "(" << x_ << ", " << y_ << ", " << z_ << ")"; out << "(" << x_ << ", " << y_ << ", " << z_ << ")";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Vector v1(3., 5., 7.); Vector v1(3., 5., 7.);
Vector v2(-4., -16., 0.); Vector v2(-4., -16., 0.);
v2 = v1; v2 = v1;
v2.Print(std::cout); v2.Print(std::cout);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### The pointer case ### The pointer case
So end of the story? Not exactly... Let's write the same and store the values in a dynamic array instead (of course you shouldn't do that in a real case: `std::vector` or `std::array` would avoid the hassle below!): So end of the story? Not exactly... Let's write the same and store the values in a dynamic array instead (of course you shouldn't do that in a real case: `std::vector` or `std::array` would avoid the hassle below!):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
class Vector2 class Vector2
{ {
public: public:
Vector2(double x, double y, double z); Vector2(double x, double y, double z);
~Vector2(); ~Vector2();
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
private: private:
double* array_ = nullptr; double* array_ { nullptr };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vector2::Vector2(double x, double y, double z) Vector2::Vector2(double x, double y, double z)
{ {
array_ = new double[3]; array_ = new double[3];
array_[0] = x; array_[0] = x;
array_[1] = y; array_[1] = y;
array_[2] = z; array_[2] = z;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vector2::~Vector2() Vector2::~Vector2()
{ {
delete[] array_; delete[] array_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Vector2::Print(std::ostream& out) const void Vector2::Print(std::ostream& out) const
{ {
out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")"; out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
// Dynamic allocation here just to be able to make our point due to the explicit call of destructor with `delete` // Dynamic allocation here just to be able to make our point due to the explicit call of destructor with `delete`
Vector2* v1 = new Vector2(3., 5., 7.); Vector2* v1 = new Vector2(3., 5., 7.);
Vector2* v2 = new Vector2(-4., -16., 0.); Vector2* v2 = new Vector2(-4., -16., 0.);
v2 = v1; v2 = v1;
std::cout << "Delete v1 @ " << std::hex << v1 << std::endl; std::cout << "Delete v1 @ " << std::hex << v1 << std::endl;
delete v1; delete v1;
std::cout << "Delete v2 @ " << std::hex << v2 << std::endl; std::cout << "Delete v2 @ " << std::hex << v2 << std::endl;
delete v2; // WARNING: Comment this line for not kernel to crash delete v2; // WARNING: Comment this line for not kernel to crash
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
At the time of this writing, this makes the kernel crash... In a more advanced environment ([Wandbox](https://wandbox.org) for instance) the reason appears more clearly: At the time of this writing, this makes the kernel crash... In a more realistic environment (below when the code is compiled with clang and executed, but you can play with [Wandbox](https://wandbox.org) if you want to see how the different compilers handle it) the reason appears more clearly:
%% Cell type:markdown id: tags: %% Cell type:code id: tags:
``` c++
%%cppmagics clang
// Exact same code as abovce, but given to your clang++ compiler to be compiled and executed
#include <iostream>
class Vector2
{
public:
Vector2(double x, double y, double z);
~Vector2();
void Print(std::ostream& out) const;
```txt private:
** Error in `./prog.exe': double free or corruption (fasttop): 0x0000000001532da0 **
double* array_ { nullptr };
};
Vector2::Vector2(double x, double y, double z)
{
array_ = new double[3];
array_[0] = x;
array_[1] = y;
array_[2] = z;
}
Vector2::~Vector2()
{
delete[] array_;
}
void Vector2::Print(std::ostream& out) const
{
out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")";
}
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{
// Dynamic allocation here just to be able to make our point due to the explicit call of destructor with `delete`
Vector2* v1 = new Vector2(3., 5., 7.);
Vector2* v2 = new Vector2(-4., -16., 0.);
v2 = v1;
std::cout << "Delete v1 @ " << std::hex << v1 << std::endl;
delete v1;
std::cout << "Delete v2 @ " << std::hex << v2 << std::endl;
delete v2;
return EXIT_SUCCESS;
}
``` ```
%% Cell type:markdown id: tags:
So what's the deal? The default operator copies all the data attributes from `v1` to `v2`... which here amounts only to the `array_` pointer. But it really copies that: the pointer itself, _not_ the data pointed by this pointer. So in fine v1 and v2 points to the same area of memory, and the issue is that we attempt to free the same memory twice. So what's the deal? The default operator copies all the data attributes from `v1` to `v2`... which here amounts only to the `array_` pointer. But it really copies that: the pointer itself, _not_ the data pointed by this pointer. So in fine v1 and v2 points to the same area of memory, and the issue is that we attempt to free the same memory twice.
One way to solve this is not to use a dynamic array - for instance you would be cool with a `std::vector`. But we can do so properly by hand as well (in practice don't - stick with `std::vector`!): One way to solve this is not to use a dynamic array - for instance you would be cool with a `std::vector`. But we can do so properly by hand as well (in practice don't - stick with `std::vector`!):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics cppyy/cppdef
#include <iostream> #include <iostream>
class Vector3 class Vector3
{ {
public: public:
Vector3(double x, double y, double z); Vector3(double x, double y, double z);
~Vector3(); ~Vector3();
// Then again the Xeus-cling issue with out of class operator definition. Vector3& operator=(const Vector3& rhs);
Vector3& operator=(const Vector3& rhs)
{
// Array already initialized in constructor; just change its content.
for (auto i = 0ul; i < 3ul; ++i)
array_[i] = rhs.array_[i];
return *this; // The (logical) return value for such a method.
}
void Print(std::ostream& out) const; void Print(std::ostream& out) const;
private: private:
double* array_ = nullptr; double* array_ { nullptr };
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vector3::Vector3(double x, double y, double z) Vector3::Vector3(double x, double y, double z)
{ {
array_ = new double[3]; array_ = new double[3];
array_[0] = x; array_[0] = x;
array_[1] = y; array_[1] = y;
array_[2] = z; array_[2] = z;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vector3::~Vector3() Vector3::~Vector3()
{ {
delete[] array_; delete[] array_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
void Vector3::Print(std::ostream& out) const void Vector3::Print(std::ostream& out) const
{ {
out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")"; out << "(" << array_[0] << ", " << array_[1] << ", " << array_[2] << ")";
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
Vector3& Vector3::operator=(const Vector3& rhs)
{
// Array already initialized in constructor; just change its content.
for (auto i = 0ul; i < 3ul; ++i)
array_[i] = rhs.array_[i];
return *this; // The (logical) return value for such a method.
}
```
%% Cell type:code id: tags:
``` c++
{ {
Vector3 v1(3., 5., 7.); Vector3 v1(3., 5., 7.);
Vector3 v2(-4., -16., 0.); Vector3 v2(-4., -16., 0.);
v2 = v1; v2 = v1;
v1.Print(std::cout); v1.Print(std::cout);
std::cout << std::endl; std::cout << std::endl;
v2.Print(std::cout); v2.Print(std::cout);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Uncopyable class ### Uncopyable class
In fact when I said by default an assignment operator is made available for the class, I was overly simplifying the issue. Let's consider for instance a class with a reference data attribute: In fact when we said by default an assignment operator is made available for the class, we were overly simplifying the issue. Let's consider for instance a class with a reference data attribute:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class ClassWithRef class ClassWithRef
{ {
public: public:
ClassWithRef(int& index); ClassWithRef(int& index);
private: private:
int& index_; int& index_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
ClassWithRef::ClassWithRef(int& index) ClassWithRef::ClassWithRef(int& index)
: index_(index) : index_(index)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
int a = 5; int a = 5;
ClassWithRef obj(a); ClassWithRef obj(a);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
int a = 5; int a = 5;
int b = 7; int b = 7;
ClassWithRef obj1(a); ClassWithRef obj1(a);
ClassWithRef obj2(b); ClassWithRef obj2(b);
obj2 = obj1; // COMPILATION ERROR obj2 = obj1; // COMPILATION ERROR
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
A class with a reference can't be copied: the reference is to be set at construction, and be left unchanged later. So a copy is out of the question, hence the compilation error. A class with a reference can't be copied: the reference is to be set at construction, and be left unchanged later. So a copy is out of the question, hence the compilation error.
The same is true as well for a class with an uncopyable data attribute: the class is then automatically uncopyable as well. The same is true as well for a class with an uncopyable data attribute: the class is then automatically uncopyable as well.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Copy constructor ### Copy constructor
Please notice however that it is still possible to allocate a new object which would be a copy of another one with a **copy constructor**. The syntax is given below: Please notice however that it is still possible to allocate a new object which would be a copy of another one with a **copy constructor**. The syntax is given below:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class ClassWithRef2 class ClassWithRef2
{ {
public: public:
ClassWithRef2(int& index); ClassWithRef2(int& index);
ClassWithRef2(const ClassWithRef2& ) = default; ClassWithRef2(const ClassWithRef2& ) = default;
private: private:
int& index_; int& index_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
ClassWithRef2::ClassWithRef2(int& index) ClassWithRef2::ClassWithRef2(int& index)
: index_(index) : index_(index)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
int a = 5; int a = 5;
ClassWithRef2 obj1(a); ClassWithRef2 obj1(a);
ClassWithRef2 obj2(obj1); // ok ClassWithRef2 obj2(obj1); // ok
ClassWithRef2 obj3 {obj1}; // ok ClassWithRef2 obj3 {obj1}; // ok
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
There are effectively two ways to copy an object: There are effectively two ways to copy an object:
* With an assignment operator. * With an assignment operator.
* With a copy constructor. * With a copy constructor.
It is NOT the same underlying method which is called under the hood, you might have a different behaviour depending on which one is called or not! It is NOT the same underlying method which is called under the hood, you might have a different behaviour depending on which one is called or not!
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### The dangers of copy constructions... and how I avoid them ### The dangers of copy constructions... and how I (Sébastien) avoid them
Copy construction may in fact be quite dangerous: Copy construction may in fact be quite dangerous:
* As we've just seen, assignment and construction may differ in implementation, which is not a good thing. There are ways to define one in function of the other (see for instance item 11 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)) but they aren't that trivial. * As we've just seen, assignment and construction may differ in implementation, which is not a good thing. There are ways to define one in function of the other (see for instance item 11 of [Effective C++](../bibliography.ipynb#Effective-C++-/-More-Effective-C++)) but they aren't that trivial.
* Depending on somewhat complicated rules that have evolved with standard versions, some might or might not be defined implicitly. * Depending on somewhat complicated rules that have evolved with standard versions, some might or might not be defined implicitly.
* More importantly, assignment operators may be a nightmare to maintain. Imagine you have a class for which you overload manually the assignment operator and/or the copy constructor. If later you add a new data attribute, you have to make sure not to forget to add it in both implementations; if you forget once you will enter the realm of undefined behaviour... and good luck for you to find the origin of the bug! * More importantly, assignment operators may be a nightmare to maintain. Imagine you have a class for which you overload manually the assignment operator and/or the copy constructor. If later you add a new data attribute, you have to make sure not to forget to add it in both implementations; if you forget once you will enter the realm of undefined behaviour... and good luck for you to find the origin of the bug!
To avoid that I took the extreme rule to (almost) never overload those myself: To avoid that I took the extreme rule to (almost) never overload those myself:
* As explained briefly above, using appropriate container may remove the need. `Vector3` could be written with a `std::array` instead of the dynamic array, and the STL object will be properly copied with default behaviour! * As explained briefly above, using appropriate container may remove the need. `Vector3` could be written with a `std::array` instead of the dynamic array, and the STL object will be properly copied with default behaviour!
* I define explicitly in my classes the behaviour of these operators with `= default` or `= delete` syntaxes. More often than not, my objects have no business being copied and `= delete` is really my default choice (this keyword indicates to the compiler the operator should not be provided for the class). * I define explicitly in my classes the behaviour of these operators with `= default` or `= delete` syntaxes. More often than not, my objects have no business being copied and `= delete` is really my default choice (this keyword indicates to the compiler the operator should not be provided for the class).
I don't pretend it is the universal choice, just my way to avoid the potential issues with manually overloaded copy operators (some would say the [Open-closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) would avoid the most problematic one, but in the real world I find it difficult to stick with this principle...) I don't pretend it is the universal choice, just my way to avoid the potential issues with manually overloaded copy operators (some would say the [Open-closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) would avoid the most problematic one, but in the real world I find it difficult to stick with this principle...)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Canonical form of a class ## Canonical form of a class
So a typical class of mine looks like: So a typical class of mine looks like:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class AlmostCanonicalClass class AlmostCanonicalClass
{ {
public: // or even protected or private for some of them! public: // or even protected or private for some of them!
//! Constructor. //! Constructor.
AlmostCanonicalClass(...); AlmostCanonicalClass(...);
//! Destructor. //! Destructor.
~AlmostCanonicalClass() = default; ~AlmostCanonicalClass() = default;
//! Disable copy constructor. //! Disable copy constructor.
AlmostCanonicalClass(const AlmostCanonicalClass& ) = delete; AlmostCanonicalClass(const AlmostCanonicalClass& ) = delete;
//! Disable copy assignment. //! Disable copy assignment.
AlmostCanonicalClass& operator=(const AlmostCanonicalClass& ) = delete; AlmostCanonicalClass& operator=(const AlmostCanonicalClass& ) = delete;
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Your IDE or a script might even be handy to generate this by default (with additional Doxygen comments for good measure). Your IDE or a script might even be handy to generate this by default (with additional Doxygen comments for good measure).
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### [Advanced] The true canonical class ### [Advanced] The true canonical class
Why _almost_ canonical class? Because C++ 11 introduced a very powerful **move semantics** (see the [notebook](/notebooks/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb) about it) and so the true canonical class is: Why _almost_ canonical class? Because C++ 11 introduced a very powerful **move semantics** (see the [notebook](/notebooks/5-UsefulConceptsAndSTL/5-MoveSemantics.ipynb) about it) and so the true canonical class is:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class TrueCanonicalClass class TrueCanonicalClass
{ {
public: // or even protected or private for some of them! public: // or even protected or private for some of them!
//! Constructor. //! Constructor.
TrueCanonicalClass(...); TrueCanonicalClass(...);
//! Destructor. //! Destructor.
~TrueCanonicalClass() = default; ~TrueCanonicalClass() = default;
//! Disable copy constructor. //! Disable copy constructor.
TrueCanonicalClass(const TrueCanonicalClass& ) = delete; TrueCanonicalClass(const TrueCanonicalClass& ) = delete;
//! Disable copy assignment. //! Disable copy assignment.
TrueCanonicalClass& operator=(const TrueCanonicalClass& ) = delete; TrueCanonicalClass& operator=(const TrueCanonicalClass& ) = delete;
//! Disable move constructor. //! Disable move constructor.
TrueCanonicalClass(TrueCanonicalClass&& ) = delete; TrueCanonicalClass(TrueCanonicalClass&& ) = delete;
//! Disable move assignment. //! Disable move assignment.
TrueCanonicalClass& operator=(TrueCanonicalClass&& ) = delete; TrueCanonicalClass& operator=(TrueCanonicalClass&& ) = delete;
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In my programs, I like to declare explicitly all of them, using `default` and `delete` to provide automatic implementation for most of them. In my programs, I like to declare explicitly all of them, using `default` and `delete` to provide automatic implementation for most of them.
I admit it's a bit of boilerplate (and to be honest a script does the job for me in my project...), if you don't want to there are in fact rules that specify which of them you need to define: for instance if a class requires a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly requires all three. See [this cppreference link](https://en.cppreference.com/w/cpp/language/rule_of_three) for more about these rules and [this blog post](https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie/) for an opposite point of view. I admit it's a bit of boilerplate (and to be honest a script does the job for me in my project...), if you don't want to there are in fact rules that specify which of them you need to define: for instance if a class requires a user-defined destructor, a user-defined copy constructor, or a user-defined copy assignment operator, it almost certainly requires all three. See [this cppreference link](https://en.cppreference.com/w/cpp/language/rule_of_three) for more about these rules and [this blog post](https://www.fluentcpp.com/2019/04/23/the-rule-of-zero-zero-constructor-zero-calorie/) for an opposite point of view.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Functors](./5-Functors.ipynb) # [Getting started in C++](./) - [Operators](./0-main.ipynb) - [Functors](./5-Functors.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## What is a functor? ## What is a functor?
### Disclaimer: not a functional programming functor! ### Disclaimer: not a functional programming functor!
First of call, for those of you with a background in functional programming, what C++ calls a `functor` is [not what you are used to](https://en.wikipedia.org/wiki/Functor_(functional_programming)). First of call, for those of you with a background in functional programming, what C++ calls a `functor` is [not what you are used to](https://en.wikipedia.org/wiki/Functor_(functional_programming)).
### Functor in C++ ### Functor in C++
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 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. 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): 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: %% Cell type:code id: tags:
``` C++17 ``` c++
class LinearFunction class LinearFunction
{ {
public : public :
LinearFunction(int constant); LinearFunction(int constant);
int operator()(int value) int operator()(int value) const;
{
return constant_ * value; // here due to usual Xeus-cling issue when out of class
// definition is used for operators
}
private : private :
int constant_ = 0.; int constant_ {};
} ; } ;
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int LinearFunction::operator()(int value) const
{
return constant_ * value;
}
```
%% Cell type:code id: tags:
``` c++
%%cppmagics cppyy/cppdef
LinearFunction::LinearFunction(int constant) LinearFunction::LinearFunction(int constant)
: constant_(constant) : constant_{constant}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
int values[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; int values[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
LinearFunction Double(2); LinearFunction Double(2);
LinearFunction Triple(3); LinearFunction Triple(3);
for (int value : values) for (int value : values)
std::cout << value << "\t" << Double(value) << "\t" << Triple(value) << std::endl; std::cout << value << "\t" << Double(value) << "\t" << Triple(value) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This might seem inconsequential, but they are sometimes extremely useful. This might seem inconsequential, but they are sometimes extremely useful.
## Functors in STL ## 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`: 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: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <vector> #include <vector>
{ {
std::vector<int> values { -980, 12, 2987, -8323, 4, 275, -8936, 27, -988, 92784 }; std::vector<int> values { -980, 12, 2987, -8323, 4, 275, -8936, 27, -988, 92784 };
std::sort(values.begin(), values.end(), std::greater<int>()); std::sort(values.begin(), values.end(), std::greater<int>());
for (auto value : values) for (auto value : values)
std::cout << value << " "; std::cout << value << " ";
} }
``` ```
%% Cell type:markdown id: tags: %% 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. 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: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
...@@ -45,16 +45,15 @@ ...@@ -45,16 +45,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
...@@ -31,16 +31,15 @@ ...@@ -31,16 +31,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Introduction](./1-Intro.ipynb) # [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Introduction](./1-Intro.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Motivation ## Motivation
The typical introduction to the need of generic programming is implementing a function that determines the minimum between two quantities (by the way don't do that: STL [provides it already](https://en.cppreference.com/w/cpp/algorithm/min)...) The typical introduction to the need of generic programming is implementing a function that determines the minimum between two quantities (by the way don't do that: STL [provides it already](https://en.cppreference.com/w/cpp/algorithm/min)...)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
int Min(int lhs, int rhs) int Min(int lhs, int rhs)
{ {
return lhs < rhs ? lhs : rhs; return lhs < rhs ? lhs : rhs;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
double Min(double lhs, double rhs) double Min(double lhs, double rhs)
{ {
return lhs < rhs ? lhs : rhs; return lhs < rhs ? lhs : rhs;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
float Min(float lhs, float rhs) float Min(float lhs, float rhs)
{ {
return lhs < rhs ? lhs : rhs; return lhs < rhs ? lhs : rhs;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
std::cout << Min(5, -8) << std::endl; std::cout << Min(5, -8) << std::endl;
std::cout << Min(5., -8.) << std::endl; std::cout << Min(5., -8.) << std::endl;
std::cout << Min(5.f, -8.f) << std::endl; std::cout << Min(5.f, -8.f) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Already tired? Yet we have still to define the same for unsigned versions, other types such as `short` ou `long`... And it's not very [**DRY** (Don't Repeat Yourself)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself): you may have noticed the implementation is exactly the same! Already tired? Yet we have still to define the same for unsigned versions, other types such as `short` ou `long`... And it's not very [**DRY** (Don't Repeat Yourself)](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself): you may have noticed the implementation is exactly the same!
## Function templates (or methods) ## Function templates (or methods)
A mechanism was therefore introduced in C++ to provide only one implementation: the **templates**: A mechanism was therefore introduced in C++ to provide only one implementation: the **templates**:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
T MinWithTemplate(T lhs, T rhs) T MinWithTemplate(T lhs, T rhs)
{ {
return lhs < rhs ? lhs : rhs; return lhs < rhs ? lhs : rhs;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
std::cout << MinWithTemplate<int>(5, -8) << std::endl; std::cout << MinWithTemplate<int>(5, -8) << std::endl;
std::cout << MinWithTemplate(5, -8) << std::endl; std::cout << MinWithTemplate(5, -8) << std::endl;
std::cout << MinWithTemplate(5., -8.) << std::endl; std::cout << MinWithTemplate(5., -8.) << std::endl;
std::cout << MinWithTemplate(5.f, -8.f) << std::endl; std::cout << MinWithTemplate(5.f, -8.f) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
As you can see: As you can see:
* The type is replaced by a parameter (here called `T`) * The type is replaced by a parameter (here called `T`)
* In the function call, you might specify the type explicitly between brackets (`<>`). If not specified, the compiler may figure it out if said type is used for one or several of the parameters. In other words, for the following case it won't work: * In the function call, you might specify the type explicitly between brackets (`<>`). If not specified, the compiler may figure it out if said type is used for one or several of the parameters. In other words, for the following case it won't work:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
T Convert(int value) T Convert(int value)
{ {
return static_cast<T>(value); return static_cast<T>(value);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
double x = Convert(5); // Error: can't figure out which type `T` to use! double x = Convert(5); // Error: can't figure out which type `T` to use!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
float x = Convert<double>(5); // Ok float x = Convert<double>(5); // Ok
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You may have noticed there are no constraints on `T` whatsoever. If you invoke a template and the implementation doesn't make sense for the chosen type the compiler will yell (no doubt you have already seen compilers are extremely talented to do so and it is even truer regarding templates...) You may have noticed there are no constraints on `T` whatsoever. If you invoke a template and the implementation doesn't make sense for the chosen type the compiler will yell (no doubt you have already seen compilers are extremely talented to do so and it is even truer regarding templates...)
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
{ {
Convert<std::string>(5); // Doesn't make sense so compiler will yell! Convert<std::string>(5); // Doesn't make sense so compiler will yell!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### `static_assert` ### `static_assert`
Of course, it would be nicer to get a clearer error message when an impossible type is provided... C++ 20 will introduce the [concept](https://en.cppreference.com/w/cpp/language/constraints) to constraint properly which type is acceptable, but C++ 11 already introduced a way slightly better than C++ 03 with `static_assert`: Of course, it would be nicer to get a clearer error message when an impossible type is provided... C++ 20 will introduce the [concept](https://en.cppreference.com/w/cpp/language/constraints) to constraint properly which type is acceptable, but C++ 11 already introduced a way slightly better than C++ 03 with `static_assert`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <type_traits> // for std::is_arithmetic #include <type_traits> // for std::is_arithmetic
template<class T> template<class T>
T Convert2(int value) T Convert2(int value)
{ {
static_assert(std::is_arithmetic<T>(), "T must be an integer or a floating point!"); static_assert(std::is_arithmetic<T>(), "T must be an integer or a floating point!");
return static_cast<T>(value); return static_cast<T>(value);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
{ {
Convert2<std::string>(5); // Doesn't make sense so compiler will yell! Convert2<std::string>(5); // Doesn't make sense so compiler will yell!
// But first line is much clearer than previously... // But first line is much clearer than previously...
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
`static_assert` evolved in C++ 17: `static_assert` evolved in C++ 17:
* In C++ 11, it takes two arguments: the test and a string which features an explanation on the topic at hand. * In C++ 11, it takes two arguments: the test and a string which features an explanation on the topic at hand.
* In C++ 17 and above, you might just give the test; it is actually handy when the test is already crystal clear: * In C++ 17 and above, you might just give the test; it is actually handy when the test is already crystal clear:
```c++ ```c++
static_assert(std::is_same<T, int>(), "Check T is an integer"); static_assert(std::is_same<T, int>(), "Check T is an integer");
``` ```
is a tad overkill! is a tad overkill!
That being said, if the test is not that trivial you should really use the possibility to add an explanation. That being said, if the test is not that trivial you should really use the possibility to add an explanation.
## Class templates ## Class templates
We have seen templates in the case of functions, but classes can be templated as well: We have seen templates in the case of functions, but classes can be templated as well:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
class HoldAValue class HoldAValue
{ {
public: public:
HoldAValue(T value); HoldAValue(T value);
T GetValue() const; T GetValue() const;
private: private:
T value_; T value_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
HoldAValue<T>::HoldAValue(T value) // the <T> is mandatory to identify properly the class HoldAValue<T>::HoldAValue(T value) // the <T> is mandatory to identify properly the class
: value_(value) : value_(value)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
T HoldAValue<T>::GetValue() const T HoldAValue<T>::GetValue() const
{ {
return value_; return value_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
#include <string> #include <string>
{ {
HoldAValue integer {5}; HoldAValue integer {5};
std::cout << "Integer hold: " << integer.GetValue() << std::endl; std::cout << "Integer hold: " << integer.GetValue() << std::endl;
HoldAValue<std::string> string {"Hello world!"}; // If type not specified explicitly it would have been char*... HoldAValue<std::string> string {"Hello world!"}; // If type not specified explicitly it would have been char*...
std::cout << "String hold: " << string.GetValue() << std::endl; std::cout << "String hold: " << string.GetValue() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The template must be reminded in the definition as well; please notice before the `::` the brackets with the template parameters. The template must be reminded in the definition as well; please notice before the `::` the brackets with the template parameters.
Spot the template type is not defined for the `integer` variable. The type is automatically deduced from the `5` argument value as an `int`. This variable could have been defined as `HoldAValue<int> integer {5};` (cf. [types deduction](../1-ProceduralProgramming/3-Types.ipynb#decltype-and-auto)) Spot the template type is not defined for the `integer` variable. The type is automatically deduced from the `5` argument value as an `int`. This variable could have been defined as `HoldAValue<int> integer {5};` (cf. [types deduction](../1-ProceduralProgramming/3-Types.ipynb#decltype-and-auto))
### Template method of a template class ### Template method of a template class
Notice a template class may provide template methods: Notice a template class may provide template methods:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
class HoldAValue2 class HoldAValue2
{ {
public: public:
HoldAValue2(T value); HoldAValue2(T value);
T GetValue() const; T GetValue() const;
template<class U> template<class U>
U Convert() const; U Convert() const;
private: private:
T value_; T value_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
HoldAValue2<T>::HoldAValue2(T value) HoldAValue2<T>::HoldAValue2(T value)
: value_(value) : value_(value)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
T HoldAValue2<T>::GetValue() const T HoldAValue2<T>::GetValue() const
{ {
return value_; return value_;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In this case there are two `template` keyword for the definition: one for the class and the other for the method: In this case there are two `template` keyword for the definition: one for the class and the other for the method:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> // template type for the class first template<class T> // template type for the class first
template<class U> // then template type for the method template<class U> // then template type for the method
U HoldAValue2<T>::Convert() const U HoldAValue2<T>::Convert() const
{ {
return static_cast<U>(value_); return static_cast<U>(value_);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream>
{ {
HoldAValue2 hold(9); HoldAValue2 hold(9);
hold.Convert<double>(); const double converted_value = hold.Convert<double>();
std::cout << "Value = " << converted_value << '\n';
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### Friendship syntax ### Friendship syntax
There is a weird and specific syntax that is expected if you want to declare a friendship to a function. Quite naturally you would probably write something like: There is a weird and specific syntax that is expected if you want to declare a friendship to a function. Quite naturally you would probably write something like:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
class HoldAValue3 class HoldAValue3
{ {
public: public:
HoldAValue3(T value); HoldAValue3(T value);
friend void Print(const HoldAValue3<T>& obj); friend void Print(const HoldAValue3<T>& obj);
private: private:
T value_; T value_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
HoldAValue3<T>::HoldAValue3(T value) HoldAValue3<T>::HoldAValue3(T value)
: value_(value) : value_(value)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
template<class T> template<class T>
void Print(const HoldAValue3<T>& obj) void Print(const HoldAValue3<T>& obj)
{ {
// Friendship required to access private data. // Friendship required to access private data.
// I wouldn't recommend friendship where an accessor would do the same task easily! // I wouldn't recommend friendship where an accessor would do the same task easily!
std::cout << "Underlying value is " << obj.value_ << std::endl; std::cout << "Underlying value is " << obj.value_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
HoldAValue3<int> hold(5); HoldAValue3<int> hold(5);
Print(hold); // LINK ERROR, and that is not something amiss in Xeus-cling! Print(hold); // LINK ERROR, and that is not something amiss with notebook kernel!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To make the friendship work, you have to use in the friendship declaration another label for the template parameter: To make the friendship work, you have to use in the friendship declaration another label for the template parameter:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics cppyy/cppdef
template<class T> template<class T>
class HoldAValue4 class HoldAValue4
{ {
public: public:
HoldAValue4(T value); HoldAValue4(T value);
// 'Repeating' the list of template arguments and not using the ones from the class will fix the issue... // 'Repeating' the list of template arguments and not using the ones from the class will fix the issue...
// T wouldn't have work here; the label MUST differ. // T wouldn't have work here; the label MUST differ.
template<class U> template<class U>
friend void Print(const HoldAValue4<U>& obj); friend void Print(const HoldAValue4<U>& obj);
private: private:
T value_; T value_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics cppyy/cppdef
template<class T> template<class T>
HoldAValue4<T>::HoldAValue4(T value) HoldAValue4<T>::HoldAValue4(T value)
: value_(value) : value_{value}
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
%%cppmagics cppyy/cppdef
#include <iostream> #include <iostream>
// Notice it is only a label: in the definition I'm free to use the same label as for the class definitions! // Notice it is only a label: in the definition I'm free to use the same label as for the class definitions!
template<class T> template<class T>
void Print(const HoldAValue4<T>& obj) void Print(const HoldAValue4<T>& obj)
{ {
std::cout << "Underlying value is " << obj.value_ << std::endl; std::cout << "Underlying value is " << obj.value_ << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
HoldAValue4<int> hold(5); HoldAValue4<int> hold(5);
Print(hold); // Ok! Print(hold); // Ok!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
This way of declaring friendship works but is not entirely fullproof: `Print<int>` is hence a friend of `HoldAValue4<double>`, which was not what was sought. Most of the time it's ok but there are 2 other ways to declare friendship; have a look at [this link](https://web.archive.org/web/20211003194958/https://web.mst.edu/~nmjxv3/articles/templates.html) if you want to learn more about it. This way of declaring friendship works but is not entirely fullproof: `Print<int>` is hence a friend of `HoldAValue4<double>`, which was not what was sought. Most of the time it's ok but there are 2 other ways to declare friendship; have a look at [this link](https://web.archive.org/web/20211003194958/https://web.mst.edu/~nmjxv3/articles/templates.html) if you want to learn more about it.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Type or non-type template parameter ## Type or non-type template parameter
The examples so far are using **type** parameters: the `T` in the example stands for a type and is deemed to be substituted by a type. Templates may also use **non type** parameters, which are in most cases `enum` or `integral constant` types (beware: floating point types parameters or `std::string` **can't** be used as template parameters!) The examples so far are using **type** parameters: the `T` in the example stands for a type and is deemed to be substituted by a type. Templates may also use **non type** parameters, which are in most cases `enum` or `integral constant` types (beware: floating point types parameters or `std::string` **can't** be used as template parameters!)
Both can be mixed in a given template declaration: Both can be mixed in a given template declaration:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class TypeT, std::size_t Nelts> template<class TypeT, std::size_t Nelts>
class MyArray class MyArray
{ {
public: public:
explicit MyArray(TypeT initial_value); explicit MyArray(TypeT initial_value);
private: private:
TypeT content_[Nelts]; TypeT content_[Nelts];
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class TypeT, std::size_t Nelts> template<class TypeT, std::size_t Nelts>
MyArray<TypeT, Nelts>::MyArray(TypeT initial_value) MyArray<TypeT, Nelts>::MyArray(TypeT initial_value)
{ {
for (std::size_t i = 0; i < Nelts; ++i) for (std::size_t i = 0; i < Nelts; ++i)
content_[i] = initial_value; content_[i] = initial_value;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
MyArray<int, 5ul> array1(2); MyArray<int, 5ul> array1(2);
MyArray<double, 2ul> array2(3.3); MyArray<double, 2ul> array2(3.3);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, you can't provide a type parameter where a non-type is expected (and vice-versa): However, you can't provide a type parameter where a non-type is expected (and vice-versa):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
MyArray<5ul, 5ul> array1(2); // COMPILATION ERROR! MyArray<5ul, 5ul> array1(2); // COMPILATION ERROR!
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
MyArray<int, int> array1(2); // COMPILATION ERROR! MyArray<int, int> array1(2); // COMPILATION ERROR!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Few precisions about templates ## Few precisions about templates
* Template parameters must be known or computed at **compile time**. You can't therefore instantiate a template from a value that was entered by the user of the program or computed at runtime. * Template parameters must be known or computed at **compile time**. You can't therefore instantiate a template from a value that was entered by the user of the program or computed at runtime.
* In the template syntax, `class` might be replaced by `typename`: * In the template syntax, `class` might be replaced by `typename`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<typename T> template<typename T>
T Convert3(int value) T Convert3(int value)
{ {
return static_cast<T>(value); return static_cast<T>(value);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Since C++ 17, there are exactly zero differences between both keywords (before C++ 17, they were almost everywhere equivalent except in a very specific case - called template template parameter that we'll see shortly - in which typename did not work. You can check [this link](https://stackoverflow.com/questions/2023977/what-is-the-difference-between-typename-and-class-template-parameters) if you want to know more); some authors suggest conventions (e.g. use `typename` for POD types and `class` for the more complicated types) but they are just that: conventions! Since C++ 17, there are exactly zero differences between both keywords (before C++ 17, they were almost everywhere equivalent except in a very specific case - called template template parameter that we'll see shortly - in which typename did not work. You can check [this link](https://stackoverflow.com/questions/2023977/what-is-the-difference-between-typename-and-class-template-parameters) if you want to know more); some authors suggest conventions (e.g. use `typename` for POD types and `class` for the more complicated types) but they are just that: conventions!
* The definition of the templates must be provided in header files, not in compiled files. The reason is that the compiler can't know all the types for which the template might be used: you may use `std::min` for your own defined types (provided they define a `<` operator...) and obviously STL writers can't foresee which type you will come up with! * The definition of the templates must be provided in header files, not in compiled files. The reason is that the compiler can't know all the types for which the template might be used: you may use `std::min` for your own defined types (provided they define a `<` operator...) and obviously STL writers can't foresee which type you will come up with!
* The compiler will generate the code for each type given to the template; for instance if `Convert<double>`, `Convert<unsigned int>` and `Convert<short>` are used in your code, the content of this template function will be instantiated thrice! This can lead to **code bloat**: lots of assembler code is generated, and compilation time may increase significantly. * The compiler will generate the code for each type given to the template; for instance if `Convert<double>`, `Convert<unsigned int>` and `Convert<short>` are used in your code, the content of this template function will be instantiated thrice! This can lead to **code bloat**: lots of assembler code is generated, and compilation time may increase significantly.
* So templates are a _very_ nice tool, but make sure to use them only when they're relevant, and you may employ techniques to limit the amount of code actually defined within the template definitions - the more straightforward being defining in a non template function the parts of the implementation that do not depend on the template parameter type. This way, this part of the code is not duplicated for each different type. * So templates are a _very_ nice tool, but make sure to use them only when they're relevant, and you may employ techniques to limit the amount of code actually defined within the template definitions - the more straightforward being defining in a non template function the parts of the implementation that do not depend on the template parameter type. This way, this part of the code is not duplicated for each different type.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
...@@ -171,16 +171,15 @@ ...@@ -171,16 +171,15 @@
], ],
"metadata": { "metadata": {
"kernelspec": { "kernelspec": {
"display_name": "C++17", "display_name": "Cppyy",
"language": "C++17", "language": "c++",
"name": "xcpp17" "name": "cppyy"
}, },
"language_info": { "language_info": {
"codemirror_mode": "text/x-c++src", "codemirror_mode": "c++",
"file_extension": ".cpp", "file_extension": ".cpp",
"mimetype": "text/x-c++src", "mimetype": "text/x-c++src",
"name": "c++", "name": "c++"
"version": "17"
}, },
"latex_envs": { "latex_envs": {
"LaTeX_envs_menu_present": true, "LaTeX_envs_menu_present": true,
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Specialization](./2-Specialization.ipynb) # [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Specialization](./2-Specialization.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Total specialization ## Total specialization
### For template functions ### For template functions
Let's take back our previous `Convert` example: the conversion into a `std::string` wasn't valid with the proposed instantiation, but converting an integer into a `std::string` is not an outlandish idea either... Let's take back our previous `Convert` example: the conversion into a `std::string` wasn't valid with the proposed instantiation, but converting an integer into a `std::string` is not an outlandish idea either...
There is a mechanism to provide specific instantiation for a type: the **(total) specialization** (we'll see why _total_ shortly): There is a mechanism to provide specific instantiation for a type: the **(total) specialization** (we'll see why _total_ shortly):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
template<class T> template<class T>
T Convert(int value) T Convert(int value)
{ {
std::cout << "Generic instantiation called!" << std::endl; std::cout << "Generic instantiation called!" << std::endl;
return static_cast<T>(value); return static_cast<T>(value);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
template<> // notice the empty brackets! template<> // notice the empty brackets!
std::string Convert<std::string>(int value) std::string Convert<std::string>(int value)
{ {
std::cout << "std::string instantiation called!" << std::endl; std::cout << "std::string instantiation called!" << std::endl;
return std::to_string(value); return std::to_string(value);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
Convert<double>(5); Convert<double>(5);
Convert<std::string>(5); Convert<std::string>(5);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Please notice the syntax: the definition provides template with no parameter in the brackets, and the `T` is replaced by the specialized type. As there are no template parameters left, the definition of the specialization must be put in a compiled file (or with an [`inline` keyword](../1-ProceduralProgramming/4-Functions.ipynb#inline-functions) in header file) Please notice the syntax: the definition provides template with no parameter in the brackets, and the `T` is replaced by the specialized type. As there are no template parameters left, the definition of the specialization must be put in a compiled file (or with an [`inline` keyword](../1-ProceduralProgramming/4-Functions.ipynb#inline-functions) in header file)
Of course, as many specialization as you wish may be provided: Of course, as many specialization as you wish may be provided:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
class HoldAnInt class HoldAnInt
{ {
public: public:
explicit HoldAnInt(int value); explicit HoldAnInt(int value);
private: private:
int value_; int value_;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
HoldAnInt::HoldAnInt(int value) HoldAnInt::HoldAnInt(int value)
: value_(value) : value_(value)
{ } { }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<> template<>
HoldAnInt Convert(int value) HoldAnInt Convert(int value)
{ {
std::cout << "HoldAnInt instantiation called!" << std::endl; std::cout << "HoldAnInt instantiation called!" << std::endl;
return HoldAnInt(value); return HoldAnInt(value);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
{ {
Convert<double>(5); Convert<double>(5);
Convert<std::string>(5); Convert<std::string>(5);
Convert<HoldAnInt>(5); Convert<HoldAnInt>(5);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### For template classes ### For template classes
Total specialization was illustrated on a function, but it works as well for a class. You may choose to: Total specialization was illustrated on a function, but it works as well for a class. You may choose to:
* Specialize either the entire class (in which case you have to define the complete API for the specialized class) * Specialize either the entire class (in which case you have to define the complete API for the specialized class)
* Or just specialize a method. * Or just specialize a method.
The case below provides entire class specialization for `double` and just method specialization for `std::string`: The case below provides entire class specialization for `double` and just method specialization for `std::string`:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// First the generic class `HoldAValue` // First the generic class `HoldAValue`
#include <iostream> #include <iostream>
#include <string> #include <string>
template<class T> template<class T>
class HoldAValue class HoldAValue
{ {
public: public:
HoldAValue(T value); HoldAValue(T value);
T GetValue() const; T GetValue() const;
private: private:
T value_; T value_;
}; };
template<class T> template<class T>
HoldAValue<T>::HoldAValue(T value) HoldAValue<T>::HoldAValue(T value)
: value_(value) : value_(value)
{ } { }
template<class T> template<class T>
T HoldAValue<T>::GetValue() const T HoldAValue<T>::GetValue() const
{ {
return value_; return value_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Method specialization for std::string // Method specialization for std::string
template<> template<>
std::string HoldAValue<std::string>::GetValue() const std::string HoldAValue<std::string>::GetValue() const
{ {
return "String case (through method specialization): " + value_; return "String case (through method specialization): " + value_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// Class specialization for double // Class specialization for double
template<> template<>
class HoldAValue<double> class HoldAValue<double>
{ {
public: public:
HoldAValue(std::string blah, double value); HoldAValue(std::string blah, double value);
double GetValue() const; double GetValue() const;
private: private:
std::string blah_; std::string blah_;
double value_; double value_;
}; };
HoldAValue<double>::HoldAValue(std::string blah, double value) HoldAValue<double>::HoldAValue(std::string blah, double value)
: blah_(blah), : blah_(blah),
value_(value) value_(value)
{ } { }
double HoldAValue<double>::GetValue() const double HoldAValue<double>::GetValue() const
{ {
std::cout << blah_; std::cout << blah_;
return value_; return value_;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
HoldAValue<int> integer(5); HoldAValue<int> integer(5);
std::cout << integer.GetValue() << std::endl; std::cout << integer.GetValue() << std::endl;
HoldAValue<std::string> string("Hello world"); HoldAValue<std::string> string("Hello world");
std::cout << string.GetValue() << std::endl; std::cout << string.GetValue() << std::endl;
HoldAValue<double> dbl("Double case (through class specialization): ", 3.14); HoldAValue<double> dbl("Double case (through class specialization): ", 3.14);
std::cout << dbl.GetValue() << std::endl; std::cout << dbl.GetValue() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Partial specialization ## Partial specialization
### For classes ### For classes
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
It is also possible for classes to **specialize partially** a template class: It is also possible for classes to **specialize partially** a template class:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class TypeT, std::size_t Nelts> template<class TypeT, std::size_t Nelts>
class MyArray class MyArray
{ {
public: public:
explicit MyArray(TypeT initial_value); explicit MyArray(TypeT initial_value);
private: private:
TypeT content_[Nelts]; TypeT content_[Nelts];
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
template<class TypeT, std::size_t Nelts> template<class TypeT, std::size_t Nelts>
MyArray<TypeT, Nelts>::MyArray(TypeT initial_value) MyArray<TypeT, Nelts>::MyArray(TypeT initial_value)
{ {
std::cout << "Generic constructor" << std::endl; std::cout << "Generic constructor" << std::endl;
for (auto i = 0ul; i < Nelts; ++i) for (auto i = 0ul; i < Nelts; ++i)
content_[i] = initial_value; content_[i] = initial_value;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class TypeT> template<class TypeT>
class MyArray<TypeT, 10ul> class MyArray<TypeT, 10ul>
{ {
public: public:
explicit MyArray(TypeT initial_value); explicit MyArray(TypeT initial_value);
private: private:
TypeT content_[10ul]; TypeT content_[10ul];
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
template<class TypeT> template<class TypeT>
MyArray<TypeT, 10ul>::MyArray(TypeT initial_value) MyArray<TypeT, 10ul>::MyArray(TypeT initial_value)
{ {
std::cout << "Partial specialization constructor: type is still templated but size is fixed " << std::endl; std::cout << "Partial specialization constructor: type is still templated but size is fixed " << std::endl;
for (auto i = 0ul; i < 10ul; ++i) for (auto i = 0ul; i < 10ul; ++i)
content_[i] = initial_value; content_[i] = initial_value;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
MyArray<int, 8ul> generic_array(2); MyArray<int, 8ul> generic_array(2);
MyArray<int, 10ul> specific_array(2); MyArray<int, 10ul> specific_array(2);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### But not for functions! ### But not for functions!
However, partial specialization is **forbidden** for template functions: However, partial specialization is **forbidden** for template functions:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
template<class T, class U> template<class T, class U>
void PrintSquare(T t, U u) void PrintSquare(T t, U u)
{ {
std::cout << "Generic instantiation" << std::endl; std::cout << "Generic instantiation" << std::endl;
std::cout << "(t^2, u^2) = (" << t * t << ", " << u * u << ")" << std::endl; std::cout << "(t^2, u^2) = (" << t * t << ", " << u * u << ")" << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
PrintSquare(5, 7.); PrintSquare(5, 7.);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
template<class T> template<class T>
void PrintSquare<T, std::string>(T t, std::string u) // COMPILATION ERROR! void PrintSquare<T, std::string>(T t, std::string u) // COMPILATION ERROR!
{ {
std::cout << "Partial function specialization: doesn't compile!" << std::endl; std::cout << "Partial function specialization: doesn't compile!" << std::endl;
std::cout << "(t^2, u^2) = (" << t * t << ", " << (u + u) << ")" << std::endl; std::cout << "(t^2, u^2) = (" << t * t << ", " << (u + u) << ")" << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You might have the impress it works if you try defining the function without specifying the brackets You might have the impress it works if you try defining the function without specifying the brackets:
_(please use [@Coliru](https://coliru.stacked-crooked.com/a/ad6d2ceca1d05b0f) - the code below no longer works in Xeus-cling as of September 2022)_
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// DOESN'T WORK ON XEUS-CLING!
#include <iostream> #include <iostream>
#include <string> #include <string>
template<class T, class U> template<class T, class U>
void PrintSquare(T t, U u) void PrintSquare(T t, U u)
{ {
std::cout << "Generic instantiation" << std::endl; std::cout << "Generic instantiation" << std::endl;
std::cout << "(t^2, u^2) = (" << t * t << ", " << u * u << ")" << std::endl; std::cout << "(t^2, u^2) = (" << t * t << ", " << u * u << ")" << std::endl;
// As '*' is not defined for std::string, one would expect a failure if invoked with // As '*' is not defined for std::string, one would expect a failure if invoked with
// `std::string` for T or U // `std::string` for T or U
} }
```
%% Cell type:code id: tags:
``` c++
template<class T> template<class T>
void PrintSquare(T t, std::string u) void PrintSquare(T t, std::string u)
{ {
std::cout << "Seemingly ok function template specialization " << std::endl; std::cout << "Seemingly ok function template specialization " << std::endl;
std::cout << "(t^2, u^2) = (" << t * t << ", " << (u + u) << ")" << std::endl; std::cout << "(t^2, u^2) = (" << t * t << ", " << (u + u) << ")" << std::endl;
} }
```
%% Cell type:code id: tags:
int main(int argc, char** argv) ``` c++
{ {
std::string hello("Hello"); std::string hello("Hello");
PrintSquare(5., hello); PrintSquare(5., hello);
}
```
// But if you uncomment this line which explicitly calls the template function %% Cell type:code id: tags:
// with both types explicitly given the compilation will fail...
// PrintSquare<double, std::string>(5., std::string("Hello"));
// ... whereas it is a perfectly legit way of calling a template instantiation, as you may check with: ``` c++
// PrintSquare<double, int>(5., 3); {
PrintSquare<double, std::string>(5., std::string("Hello")); // FAILS!
}
```
%% Cell type:markdown id: tags:
If you have any doubt, above syntax is perfectly valid to call template specialization as seen in the example below:
%% Cell type:code id: tags:
return EXIT_SUCCESS; ``` c++
{
PrintSquare<double, int>(5., 3);
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
To be honest, the reason partial specialization are forbidden are quite unclear for me; this seems to be internal reasons in the way compilers are parsing the C++ code. I've seen on forums discussions to lift this limitation over the years, but to my knowledge no concrete plan is in motion to do so. To be honest, the reason partial specialization are forbidden are quite unclear for me; this seems to be internal reasons in the way compilers are parsing the C++ code. I've seen on forums discussions to lift this limitation over the years, but to my knowledge no concrete plan is in motion to do so.
### Mimicking the partial template specialization for functions ### Mimicking the partial template specialization for functions
#### Using a class and a static method #### Using a class and a static method
There is a quite verbose way to circumvent the impossibility: use a template struct (which is partially specializable) with a static method... There is a quite verbose way to circumvent the impossibility: use a template struct (which is partially specializable) with a static method...
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
template<class T, class U> template<class T, class U>
struct PrintSquareHelper struct PrintSquareHelper
{ {
static void Do(T t, U u) static void Do(T t, U u)
{ {
std::cout << "Generic instantiation" << std::endl; std::cout << "Generic instantiation" << std::endl;
std::cout << "(t^2, u^2) = (" << t * t << ", " << u * u << ")" << std::endl; std::cout << "(t^2, u^2) = (" << t * t << ", " << u * u << ")" << std::endl;
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <string> #include <string>
template<class T> template<class T>
struct PrintSquareHelper<T, std::string> struct PrintSquareHelper<T, std::string>
{ {
static void Do(T t, std::string u) static void Do(T t, std::string u)
{ {
std::cout << "Specialized instantiation" << std::endl; std::cout << "Specialized instantiation" << std::endl;
std::cout << "(t^2, u^2) = (" << t * t << ", " << (u + u) << ")" << std::endl; std::cout << "(t^2, u^2) = (" << t * t << ", " << (u + u) << ")" << std::endl;
} }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T, class U> template<class T, class U>
void PrintSquare2(T t, U u) void PrintSquare2(T t, U u)
{ {
PrintSquareHelper<T, U>::Do(t, u); PrintSquareHelper<T, U>::Do(t, u);
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
PrintSquare2<double, std::string>(5., std::string("Hello")); PrintSquare2<double, std::string>(5., std::string("Hello"));
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### If constexpr ### If constexpr
C++ 17 introduced (at last!) a special `if` that is explicitly checked at compile time. C++ 17 introduced (at last!) a special `if` that is explicitly checked at compile time.
This alleviates greatly the codes and is really something I yearned for years: This alleviates greatly the codes and is really something I yearned for years:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
#include <string> #include <string>
template<class T, class U> template<class T, class U>
void Print3(T t, U u) void Print3(T t, U u)
{ {
if constexpr (std::is_same<U, std::string>()) // Don't worry we will see STL algorithms later if constexpr (std::is_same<U, std::string>()) // Don't worry we will see STL algorithms later
{ {
std::cout << "Specialized instantiation" << std::endl; std::cout << "Specialized instantiation" << std::endl;
std::cout << "(t^2, u^2) = (" << t * t << ", " << (u + u) << ")" << std::endl; std::cout << "(t^2, u^2) = (" << t * t << ", " << (u + u) << ")" << std::endl;
} }
else else
{ {
std::cout << "Generic instantiation" << std::endl; std::cout << "Generic instantiation" << std::endl;
std::cout << "(t^2, u^2) = (" << t * t << ", " << u * u << ")" << std::endl; std::cout << "(t^2, u^2) = (" << t * t << ", " << u * u << ")" << std::endl;
} }
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Print3<double, std::string>(5., "hello"); Print3<double, std::string>(5., "hello");
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
**Note: and without constexpr?** **Note: and without constexpr?**
I haven't specified it so far, but the `constexpr` is crucial: without it the compiler must instantiate both branches, even if only one is kept at the end. And if one can't be instantiated we're screwed: I haven't specified it so far, but the `constexpr` is crucial: without it the compiler must instantiate both branches, even if only one is kept at the end. And if one can't be instantiated we're screwed:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
template<class T, class U> template<class T, class U>
void Print4(T t, U u) void Print4(T t, U u)
{ {
if (std::is_same<U, std::string>()) if (std::is_same<U, std::string>())
{ {
std::cout << "Specialized instantiation" << std::endl; std::cout << "Specialized instantiation" << std::endl;
std::cout << "(t^2, u^2) = (" << t * t << ", " << (u + u) << ")" << std::endl; std::cout << "(t^2, u^2) = (" << t * t << ", " << (u + u) << ")" << std::endl;
} }
else else
{ {
std::cout << "Generic instantiation" << std::endl; std::cout << "Generic instantiation" << std::endl;
std::cout << "(t^2, u^2) = (" << t * t << ", " << u * u << ")" << std::endl; std::cout << "(t^2, u^2) = (" << t * t << ", " << u * u << ")" << std::endl;
} }
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Print4<double, std::string>(5., "Hello"); // Compilation error! Print4<double, std::string>(5., "Hello"); // Compilation error!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
# [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Special syntax: typename, template and mandatory this](./3-Syntax.ipynb) # [Getting started in C++](./) - [Templates](./0-main.ipynb) - [Special syntax: typename, template and mandatory this](./3-Syntax.ipynb)
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
In this chapter, we will review some cases involving templates in which additional syntax are required to help the compiler to do its job; it is useful to have a look because these not that difficult rules might be puzzling if their whereabouts have not been explained a bit. In this chapter, we will review some cases involving templates in which additional syntax are required to help the compiler to do its job; it is useful to have a look because these not that difficult rules might be puzzling if their whereabouts have not been explained a bit.
## Mandatory `this` in calling base class methods ## Mandatory `this` in calling base class methods
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
class Base class Base
{ {
public: public:
Base() = default; Base() = default;
void BaseMethod() void BaseMethod()
{ } { }
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
class Derived : public Base<T> class Derived : public Base<T>
{ {
public: public:
Derived() = default; Derived() = default;
void DerivedMethod(); void DerivedMethod();
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
void Derived<T>::DerivedMethod() void Derived<T>::DerivedMethod()
{ {
BaseMethod(); // Compilation error! BaseMethod(); // Compilation error!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Same code without the template argument would be accepted without issue... but with templates the compiler doesn't manage to properly find the base method. Same code without the template argument would be accepted without issue... but with templates the compiler doesn't manage to properly find the base method.
To circumvent this, you must explicitly tell `BaseMethod()` is a method; the most obvious way is to put explicit `this` (which we introduced [here](../2-ObjectProgramming/2-Member-functions.ipynb#The-this-keyword)): To circumvent this, you must explicitly tell `BaseMethod()` is a method; the most obvious way is to put explicit `this` (which we introduced [here](../2-ObjectProgramming/2-Member-functions.ipynb#The-this-keyword)):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
void Derived<T>::DerivedMethod() void Derived<T>::DerivedMethod()
{ {
this->BaseMethod(); // Now ok! this->BaseMethod(); // Now ok!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Personally, I even prefer to use an alias which points to the base class: this way it is even clearer where the method (even more so if there are several parent classes). Personally, I even prefer to use an alias which points to the base class: this way it is even clearer where the method (even more so if there are several parent classes).
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class T> template<class T>
class Derived2 : public Base<T> class Derived2 : public Base<T>
{ {
public: public:
Derived2() = default; Derived2() = default;
using parent = Base<T>; using parent = Base<T>;
void DerivedMethod() void DerivedMethod()
{ {
parent::BaseMethod(); // also ok! parent::BaseMethod(); // also ok!
} }
}; };
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Mandatory `typename` keyword ## Mandatory `typename` keyword
In a `std::vector`, as in several STL constructs, there are several **traits** which are defined: a trait is a type information concerning the class. [This page](https://en.cppreference.com/w/cpp/container/vector) mentions all of them in the `Member types` section. In a `std::vector`, as in several STL constructs, there are several **traits** which are defined: a trait is a type information concerning the class. [This page](https://en.cppreference.com/w/cpp/container/vector) mentions all of them in the `Member types` section.
The first one simply returns the type of the value stored in a `std::vector`, for instance for `std::vector<int>::value_type` is just `int`. The first one simply returns the type of the value stored in a `std::vector`, for instance for `std::vector<int>::value_type` is just `int`.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <vector> #include <vector>
{ {
std::vector<int>::value_type i = 5; // convoluted way to just say int i = 5! std::vector<int>::value_type i = 5; // convoluted way to just say int i = 5!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
However, this simple construct fails in the following case: However, this simple construct fails in the following case:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
template<class ContainerT> template<class ContainerT>
void PrintFive() void PrintFive()
{ {
std::cout << static_cast<ContainerT::value_type>(5) << std::endl; std::cout << static_cast<ContainerT::value_type>(5) << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
You might see it's just that it was never mentioned `ContainerT` was intended to be a `std::vector`... and you would be right. That's not the full story though: the point of a template function is to just work with any given class that just respects the interface (it's very similar to the [**duck typing**](https://en.wikipedia.org/wiki/Duck_typing) so dear to Pythonists: _If it walks like a duck and it quacks like a duck, then it must be a duck_). You might see it's just that it was never mentioned `ContainerT` was intended to be a `std::vector`... and you would be right. That's not the full story though: the point of a template function is to just work with any given class that just respects the interface (it's very similar to the [**duck typing**](https://en.wikipedia.org/wiki/Duck_typing) so dear to Pythonists: _If it walks like a duck and it quacks like a duck, then it must be a duck_).
The issue in fact here is that the compiler doesn't even know that `value_type` is expected to be a type and not an attribute. So you have to help it with the keyword `typename` just before the type in question (just as indicated by the compiler: fortunately they are now rather helpful in pointing out this kind of issue): The issue in fact here is that the compiler doesn't even know that `value_type` is expected to be a type and not an attribute. So you have to help it with the keyword `typename` just before the type in question (just as indicated by the compiler: fortunately they are now rather helpful in pointing out this kind of issue):
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
template<class ContainerT> template<class ContainerT>
void PrintFive() void PrintFive()
{ {
std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl; std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
PrintFive<int>(); // Won't compile: no trait 'value_type' for an 'int'! PrintFive<int>(); // Won't compile: no trait 'value_type' for an 'int'!
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
PrintFive<std::vector<int>>(); // Ok! PrintFive<std::vector<int>>(); // Ok!
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
C++ 20 alleviates the need for `typename` keyword in some cases (see for instance this [StackOverflow](https://stackoverflow.com/questions/61990971/why-dont-i-need-to-specify-typename-before-a-dependent-type-in-c20) post with two very interesting links related to that topic); at the moment I must admit I keep putting it everywhere as previously and I am not very knowledgeable when it can be avoided. C++ 20 alleviates the need for `typename` keyword in some cases (see for instance this [StackOverflow](https://stackoverflow.com/questions/61990971/why-dont-i-need-to-specify-typename-before-a-dependent-type-in-c20) post with two very interesting links related to that topic); at the moment I must admit I keep putting it everywhere as previously and I am not very knowledgeable when it can be avoided.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
### [optional] A bit of wandering: how will C++ 20 concepts will help to enforce the constraint upon the template parameter ### [optional] A bit of wandering: how will C++ 20 concepts will help to enforce the constraint upon the template parameter
Once again, C++ 20 concept will be much appreciated here to provide more straightforward error messages (see [@Coliru](https://coliru.stacked-crooked.com/a/4cc87e5a41d1a94c)): Once again, C++ 20 concept will be much appreciated here to provide more straightforward error messages:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
// C++ 20 code - run it in Coliru! %%cppmagics clang
// `cling` C++ interpreter used by kernel doesn't support yet C++ 20 concepts.
#include <iostream> #include <iostream>
#include <vector> #include <vector>
#include <list> #include <list>
template<typename T> template<typename T>
concept HasValueType = requires(T) concept HasValueType = requires(T)
{ {
typename T::value_type; typename T::value_type;
}; };
template<class ContainerT> template<HasValueType ContainerT>
requires HasValueType<ContainerT>
void PrintFive() void PrintFive()
{ {
std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl; std::cout << static_cast<typename ContainerT::value_type>(5) << std::endl;
} }
int main(int argc, char** argv) int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv)
{ {
PrintFive<std::vector<int>>(); PrintFive<std::vector<int>>();
PrintFive<std::list<int>>(); PrintFive<std::list<int>>();
// PrintFive<int>(); // COMPILATION ERROR! But with a clear error message. PrintFive<int>(); // COMPILATION ERROR! But with a clear error message.
return EXIT_SUCCESS; return EXIT_SUCCESS;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
The error message (with gcc) includes all the relevant information to understand what is wrong the the `int` case:
```shell
main.cpp: In function 'int main(int, char**)':
main.cpp:27:20: error: use of function 'void PrintFive() [with ContainerT = int]' with unsatisfied constraints
27 | PrintFive<int>(); // COMPILATION ERROR! But with a clear error message.
...
main.cpp:8:14: note: the required type 'typename T::value_type' is invalid
8 | typename T::value_type;
```
%% Cell type:markdown id: tags:
## Mandatory `template` keyword ## Mandatory `template` keyword
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Likewise, compiler sometimes needs help to figure out a template is involved. For instance: Likewise, compiler sometimes needs help to figure out a template is involved. For instance:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
struct Foo struct Foo
{ {
template<int N> template<int N>
void PrintN() const; void PrintN() const;
}; };
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
#include <iostream> #include <iostream>
template<int N> template<int N>
void Foo::PrintN() const void Foo::PrintN() const
{ {
std::cout << N << std::endl; std::cout << N << std::endl;
} }
``` ```
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class U> template<class U>
void Print(const U& u) void Print(const U& u)
{ {
std::cout << u.PrintN<0>() << std::endl; std::cout << u.PrintN<0>() << std::endl;
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Here the compiler is confused and doesn't understand we mean `PrintN` to be a template method; there is in fact an ambiguity with operators `<` and `>`. These ambiguity cases arises when two templates are involved. Here the compiler is confused and doesn't understand we mean `PrintN` to be a template method; there is in fact an ambiguity with operators `<` and `>`. These ambiguity cases arises when two templates are involved.
To help the compiler figure it out, the solution is very similar to the `typename` one: we specify an explicit template to help the compiler. To help the compiler figure it out, the solution is very similar to the `typename` one: we specify an explicit template to help the compiler.
Fortunately, compilers (at least clang and gcc) became very recently much more helpful and gives away in the error message the probable fix. It wasn't the case until very recently (the "fortunately" at the beginning of this sentence was an "unfortunately" in 2022...); so keep it in mind as it could be quite puzzling the first time you encounter it if the compiler doesn't give you the hint, especially if you're working with a fairly old compiler. Fortunately, compilers (at least clang and gcc) became very recently much more helpful and gives away in the error message the probable fix. It wasn't the case until very recently (the "fortunately" at the beginning of this sentence was an "unfortunately" in 2022...); so keep it in mind as it could be quite puzzling the first time you encounter it if the compiler doesn't give you the hint, especially if you're working with a fairly old compiler.
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
template<class U> template<class U>
void Print(const U& u) void Print(const U& u)
{ {
std::cout << u.template PrintN<0>() << std::endl; // Notice the template keyword! std::cout << u.template PrintN<0>() << std::endl; // Notice the template keyword!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## Good practice: always check obvious template instantiations in test! ## Good practice: always check obvious template instantiations in test!
If you have been very attentive, you might see `Print()` above is faulty: we ask to print on screen a void function... An instantiation reveals the issue: If you have been very attentive, you might see `Print()` above is faulty: we ask to print on screen a void function... An instantiation reveals the issue:
%% Cell type:code id: tags: %% Cell type:code id: tags:
``` C++17 ``` c++
{ {
Foo foo; Foo foo;
Print(foo); // Compilation error! Print(foo); // Compilation error!
} }
``` ```
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
Without instantiation, the compiler can't easily figure out there is an issue; in `Print()` there are actually implicit conditions on the nature if the template parameter `U`: Without instantiation, the compiler can't easily figure out there is an issue; in `Print()` there are actually implicit conditions on the nature if the template parameter `U`:
* `U` must provide a template `PrintN` method with an integer parameter. * `U` must provide a template `PrintN` method with an integer parameter.
* An overload of operator `<<` must be provided for the return type of this method. * An overload of operator `<<` must be provided for the return type of this method.
`Foo` doesn't check the second condition... but you can't really see it without checking it (once again C++ 20 concepts will help on that matter, but the advise here still stands). `Foo` doesn't check the second condition... but you can't really see it without checking it (once again C++ 20 concepts will help on that matter, but the advise here still stands).
So if the template is intended to work with a `Foo`, it is a good idea to provide a dedicated test that checks it actually works. So if the template is intended to work with a `Foo`, it is a good idea to provide a dedicated test that checks it actually works.
**Attention!** Code coverage tools (at least those we are aware of) won't help you here: a template that is not instantiated isn't considered as code to cover... So you may have a 100 % coverage and still gets a template function or class with a faulty implementation if you never instantiate it. **Attention!** Code coverage tools (at least those we are aware of) won't help you here: a template that is not instantiated isn't considered as code to cover... So you may have a 100 % coverage and still gets a template function or class with a faulty implementation if you never instantiate it.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
## No virtual template method! ## No virtual template method!
All is in the title here: a method can't be both virtual and template... All is in the title here: a method can't be both virtual and template...
We are speaking specifically of the methods here: a template class may feature virtual (non template) methods without any issue. We are speaking specifically of the methods here: a template class may feature virtual (non template) methods without any issue.
%% Cell type:markdown id: tags: %% Cell type:markdown id: tags:
[© Copyright](../COPYRIGHT.md) [© Copyright](../COPYRIGHT.md)
......