Regular types
Ilio Catallo - info@iliocatallo.it
Outline
• Fundamental opera/ons
• Regular types
• Semi-regular & totally ordered types
• Making vector_int regular
• Bibliography
Fundamental opera.ons
Commonali(es among types
What is in common between chars and doubles1
?
char c1 = 'a', c2 = 'b';
double d1 = 1.3, d2 = 2.7;
1
For the remainder of this presenta1on, we will limit ourselves to non-singular double values, i.e., we will exclude
NaN from the set of possible values
Commonali(es among types
For a start, both chars and doubles and can be swapped
std::swap(c1, c2);
std::swap(d1, d2);
Why is that so?
swap implementa*on for doubles
Let us look at the implementa0on of swap as if it was wri4en just
for doubles
void swap(double& x, double& y) {
double tmp = x;
x = y;
y = tmp;
}
Requirements on doubles
Which requirements on doubles are needed for swap to work?
void swap(double& x, double& y) {
double tmp = x;
x = y;
y = tmp;
}
Requirements on doubles
First, it should be possible to copy construct a double
void swap(double& x, double& y) {
double tmp = x; // tmp is copy constructed from x
x = y;
y = tmp;
}
Requirements on doubles
Second, it should be possible to assign a double
void swap(double& x, double& y) {
double tmp = x;
x = y; // x is assigned y
y = tmp; // y is assigned tmp
}
Requirements on doubles
Given that double values are in fact both assignable and copy
construc1ble, we can run swap on them
std::swap(d1, d2);
swap implementa*on for chars
Let us now turn our a,en-on to a version of swap tailored for
chars
void swap(char& x, char& y) {
char tmp = x;
x = y;
y = tmp;
}
Requirements on chars
As before, we no,ce that char values need to be assignable and
copy construc1ble
void swap(char& x, char& y) {
char tmp = x;
x = y;
y = tmp;
}
Requirements on chars
As before, since char values are in fact assignable and copy
construc1ble, it is possible to swap them
std::swap(c1, c2);
Swapping built-in types
It is immediate to see that our finding generalizes to all built-in
types
std::swap(b1, b2); // b1 and b2 are bools
std::swap(f1, f2); // f1 and f2 are floats
std::swap(i1, i2); // i1 and i2 are ints
Common opera*ons
We conclude that assignment and copy construc0on are
opera/ons common to all built-in types
Common opera*ons
That is, for any built-in type T the following is always possible:
T a; a = b; // assignment
T c = d; // copy construction
Common opera*ons
In light of this, we can write a uniform implementa.on of swap for
all built-in types
void swap(T& x, T& y) { // T is a built-in type
T tmp = x;
x = y;
y = tmp;
}
Do built-in types share any addi3onal opera3ons?
Fundamental opera.ons
As a ma&er of fact, we observe that there is a core set of
opera4ons which are defined for all built-in types
Fundamental opera.ons
We call such opera-ons the fundamental opera.ons
┌────────────────────────┬──────────┐
│ Default construction │ T a; │
├────────────────────────┼──────────┤
│ Copy construction │ T a = b; │
├────────────────────────┼──────────┤
│ Destruction │ ~T(a); │
├────────────────────────┼──────────┤
│ Assignment │ a = b; │
├────────────────────────┼──────────┤
│ Equality │ a == b; │
├────────────────────────┼──────────┤
│ Inequality │ a != b; │
├────────────────────────┼──────────┤
│ Ordering │ a < b; │
└────────────────────────┴──────────┘
Regular types
Operators for UDTs
The C++ programming language allows the use of built-in type
operator syntax for user-defined types (UDTs)
Example: the point2
UDT
struct point {
point(double x, double y): x(x), y(y) {}
double x;
double y;
};
2
Remember that there is no difference between a struct and a class, up to default members' visibility.
Conven=onally, we use structs when our UDTs do not need a private sec=on
Example: the point UDT
Intui&vely, it makes sense to define operator == as equality:
bool operator==(point const& p1, point const& p2) {
return p1.x == p2.x &&
p1.y == p2.y;
}
Overload seman-cs
However, C++ does not dictate any seman&c rules on operator
overloading
Overload seman-cs
That is, developers are free to define == as:
bool operator==(point const& p1, point const& p2) {
return p1.x * p2.x >
p1.y * p2.y;
}
Seman&cs mismatch
This could lead to a mismatch between the expected and the
actual seman4cs of an operator
Seman&cs mismatch
This could lead to a mismatch between the expected and the
actual seman4cs of an operator
( °□°
Seman&cs mismatch
"I admire the commi.ee dedica/on to programming freedom, but I
claim that such freedom is an illusion. Finding the laws that govern
so=ware components gives us freedom to write complex programs the
same way that finding the laws of physics allows us to construct
complex mechanical and electrical systems."
(A. Stepanov)
Built-in types as a theore1cal founda1on
The opera)ons common to all built-in types are among the most
well-understood and pervasively used
Built-in types as a theore1cal founda1on
This is because, over the years, the development of built-in types
has led to consistent defini9ons that match
• the programmer intui-on, and
• the underlying mathema-cal understanding
Idea: deriving the seman.cs of user-defined types from that of
built-in types
Preserving opera-on seman-cs
The idea is that when a code fragment has a certain meaning for
built-in types, it should preserve the same meaning for UDTs
Regular types
To this end, we introduce the concept of a regular type as a type
behaving like the built-in types
Regular types
We call such types regular, since their use guarantees regularity of
behavior and – therefore – interoperability
std::vector is regular
For instance, std::vector<T> is regular whenever T is regular
std::vector is regular
As with built-in types, std::vector<T> is thus copy
construc+ble and assignable for each regular type T
std::vector<int> w = v; // copy construction
std::vector<int> u; u = w; // assignment
std::vector is swappable
Hence, we can swap3
two vectors of any regular type T:
std::vector<int> v = {7, 3, 5};
std::vector<int> w = {10, 11, 4};
std::swap(v, w);
3
In reality, for the sake of performance, the Standard Library provides a specialized version std::swap for the
specific case of std::vector. Although less performing, the general purpose version would however equally work
How can we assure our UDTs to be regular?
What does it mean to be regular?
Ul#mately, we need to inves#gate how several of the built-in
operators should behave when applied to user-defined types
What does it mean to be regular?
This amounts to understanding how to apply the fundamental
opera5ons to UDTs
┌────────────────────────┬──────────┐
│ Default construction │ T a; │
├────────────────────────┼──────────┤
│ Copy construction │ T a = b; │
├────────────────────────┼──────────┤
│ Destruction │ ~T(a); │
├────────────────────────┼──────────┤
│ Assignment │ a = b; │
├────────────────────────┼──────────┤
│ Equality │ a == b; │
├────────────────────────┼──────────┤
│ Inequality │ a != b; │
├────────────────────────┼──────────┤
│ Ordering │ a < b; │
└────────────────────────┴──────────┘
What does it mean to be regular?
First, we no,ce that the fundamental opera,ons can be divided
into three groups
┌────────────────────────┬──────────┐
│ │ T a; │
│ ├──────────┤
│ │ T a = b; │
│ Copying and assignment ├──────────┤
│ │ ~T(a); │
│ ├──────────┤
│ │ a = b; │
├────────────────────────┼──────────┤
│ │ a == b; │
│ Equality ├──────────┤
│ │ a != b; │
├────────────────────────┼──────────┤
│ Ordering │ a < b; │
└────────────────────────┴──────────┘
What does it mean to be regular?
Let us now inves,gate in greater detail the proper%es of each such
group
┌────────────────────────┬──────────┐
│ │ T a; │
│ ├──────────┤
│ │ T a = b; │
│ Copying and assignment ├──────────┤
│ │ ~T(a); │
│ ├──────────┤
│ │ a = b; │
├────────────────────────┼──────────┤
│ │ a == b; │
│ Equality ├──────────┤
│ │ a != b; │
├────────────────────────┼──────────┤
│ Ordering │ a < b; │
└────────────────────────┴──────────┘
Copying and assignment
Copy and assignment are interchangeable
We know that built-in types allow wri4ng interchangeably:
T a; a = b; // assignment
T a = b; // copy construction
Copy and assignment are interchangeable
The ra'onale behind this is that it allows natural code such as:
T a;
if (something) a = b;
else a = c;
Default construc.on
Observe that we need default construc.on to make assignment
and copy construc7on interchangeable
T a = b; T a; a = b; T a;
Default construc.on
Note that we require a default constructed regular type to be
assignable and destruc.ble
Par$al construc$on
That is, we require default ini2aliza2on only to par$ally form an
object
Preserving equality
The next step is to note that for all built-in types copy construc5on
and assignment are equality-preserving opera5ons
T a = b; // copy construction
a == b; // true!
Preserving equality
The next step is to note that for all built-in types copy construc5on
and assignment are equality-preserving opera5ons
T a; a = b; // assignment
a == b; // true!
Preserving equality
• Copy construc-ng means to make a copy that is equal to the
original
• Assignment copies the right-hand side object to the le;-hand
side object, leaving their values equal
Dependency on equality
Since we need the ability to test for equality, we have that copy
construc7on and assignment depend on equality
A refined defini)on of regularity
In other words, regular types possess the equality operator and
equality-preserving copy construc4on and assignment
Defining equality
Although equality is conceptually central, we do not yet have a
sa.sfactory defini.on of equality of two objects of a regular type
Equality
Equality for built-in types
On built-in types, the equality rela3on is normally defined as
bitwise equality
Equality for UDTs
Star%ng from this, we can devise a natural defini,on of equality for
user-defined types
Equality for UDTs
Namely, we can define equality of user-defined types from the
equality of its parts
Equality for UDTs
However, in order to build the equality operator from the equality
operator of its parts, we must iden%fy its parts
Example: the point type
struct point {
point(double x, double y): x(x), y(y) {}
double x;
double y;
};
Example: operator== for points
bool operator==(point const& p1, point const& p2) {
return p1.x == p2.x &&
p1.y == p2.y;
}
Remote parts
Complex objects are o&en constructed out of mul0ple simpler
objects, connected by pointers
Remote parts
In such cases, we say that the given complex object has remote
parts
Example: the person type
struct mail_address {...};
class person {
public:
person(...) {...}
private:
std::string name;
std::string surname;
mail_address* address;
};
1st
a&empt: operator== for persons
friend
bool operator==(person const& p1, person const& p2) {
return p1.name == p2.name &&
p1.surname == p2.surname;
}
2nd
a&empt: operator== for persons
friend
bool operator==(person const& p1, person const& p2) {
return p1.name == p2.name &&
p1.surname == p2.surname &&
p1.address == p2.address;
}
3rd
a&empt: operator== for persons
friend
bool operator==(person const& p1, person const& p2) {
return p1.name == p2.name &&
p1.surname == p2.surname &&
*p1.address == *p2.address;
}
Comparing remote parts
For objects with remote parts, the equality operator must compare
the corresponding remote parts, rather than the pointers to them
Comparing remote parts
We do not want objects with equal remote parts to compare
unequal just because the remote parts were in different memory
loca3ons
Defini&on of equality
We can therefore devise the following defini4on of equality:
Two objects are equal if their corresponding parts are equal (applied
recursively), including remote parts (but not comparing their addresses),
excluding inessen>al components, and excluding components which
iden>fy related objects
Equality comes with inequality
It is not enough to define equality, we also need to define
inequality to match it
Unequal vs. not equal
We need to do so in order to preserve the ability to write
interchangeably:
a != b; // unequal
!(a == b); // not equal
Unequal vs. not equal
That is, the statements "two things are unequal to each other" and
"two 6ngs are not equal to each other" should be equivalent
a != b; // unequal
!(a == b); // not equal
Inequality operator
Fortunately, this is very simple:
// T is some user-defined type
bool operator!=(T const& x, T const& y) {
return !(x == y);
}
Equality and inequality
C++ does not enforce that equality and inequality should be
defined together, so remember to define inequality every 7me
equality is defined
Ordering
The importance of ordering
We need to have ordering if we want to find things quickly, as
ordering enables sor$ng, and therefore binary search
Ordering
For defining ordering on a user-defined type we need to set in
place the four rela%onal operators <, >, <=, >=
Rela%onal operators
However, we need to implement only one rela(onal operator,
which can then be used to derive the defini8on on the remaining
three
Less-than operator
We choose < as the primary operator, since when we refer to
ordering we usually mean ascending ordering
Less-than operator seman.cs
Intui&vely, since we are overloading the less-than operator <, the
ordering criterion we implement must behave like <
How to make our overload behave the way that < behaves?
Strictness
First, let us no-ce this natural property of < when used as an
arithme-c operator:
5 ≮ 5 12 ≮ 12 etc.
Strictness
Hence, also our ordering rela0on of choice must be strict, that is:
a ≮ a for all a in T
Is this defini+on strict?
// T is some user-defined type
bool operator<(T const& x, T const& y) {
return true;
}
Is this defini+on strict?
// T is some user-defined type
bool operator<(T const& x, T const& y) {
return true; // according to this 5 < 5,
// which violates strictness
}
Transi'vity
Second, it is immediate to see that the less-than operator is
transi've:
4 < 7 and 7 < 12 implies 4 < 12
Transi'vity
Hence, also our ordering rela0on of choice must be transi've
// T is some user-defined type
bool operator<(T const& x, T const& y) {
// maintain transitivity
}
Trichotomy law
Finally, the less-than operator < obeys the trichotomy law:
For every pair of elements, exactly one of the following holds:
a < b, b < a or a == b
Trichotomy law
Note that the trichotomy law requires the defini5on of < to be
consistent with the defini5on of == so that:
!(a < b) && !(b < a) is equivalent to a == b
Equality is central
Hence, as with assignment and copy, also ordering depends on
equality
!(a < b) && !(b < a) is equivalent to a == b
Is this a well-formed overload?
bool operator<(point const& p1, point const& p2) {
return p1.x < p2.x;
}
Is this a well-formed overload?
It respects strictness
bool operator<(point const& p1, point const& p2) {
return p1.x < p2.x; // (5, 2) ≮ (5, 2)
}
Is this a well-formed overload?
It respects transi,vity
bool operator<(point const& p1, point const& p2) {
return p1.x < p2.x; // (5,2) < (6, 3) < (10, 5)
}
Is this a well-formed overload?
But it does not obey to the trichotomy law
bool operator<(point const& p1, point const& p2) {
return p1.x < p2.x; // (5, 3) ≮ (5, 8) and
// (5, 8) ≮ (5, 3) but...
// (5, 8) ≠ (5, 3)
}
Is this a well-formed overload?
bool operator<(point const& p1, point const& p2) {
if (p1.x < p2.x) return true;
if (p1.x > p2.x) return false;
return p1.y < p2.y;
}
Is this a well-formed overload?
We are imposing a lexicographic ordering over points
bool operator<(point const& p1, point const& p2) {
if (p1.x < p2.x) return true;
if (p1.x > p2.x) return false;
return p1.y < p2.y;
}
Is this a well-formed overload?
Lexicographic ordering respects strictness, transi2vity and the
trichotomy law
bool operator<(point const& p1, point const& p2) {
if (p1.x < p2.x) return true;
if (p1.x > p2.x) return false;
return p1.y < p2.y;
}
Strict total ordering
We refer to an ordering rela.on that is strict, transi.ve and that
obeys the trichotomy law as a strict total ordering
Remaining operators
As with equality, we must define manually the remaining rela5onal
operators >, >=, <=
Greater-than operators
// T is some user-defined type
bool operator>(T const& x, T const& y) {
return y < x;
}
Greater-or-equal-than operator
// T is some user-defined type
bool operator>=(T const& x, T const& y) {
return !(x < y);
}
Less-or-equal-than operator
// T is some user-defined type
bool operator<=(T const& x, T const& y) {
return !(y < x);
}
Semi-regular &
totally ordered types
Regularity
Some%mes, although a type looks like a regular type, we cannot
formally guarantee its regularity
complex seems regular
For instance, the complex type we previously wrote feels like a
regular type
complex a; // default construction
complex b = a; // copy construction
complex c; c = b; // assignment
a == b; // equality
a != c; // inequality
complex numbers cannot be ordered
However, we cannot impose a natural order on complex numbers4
complex a(5, 2);
complex b(8, 0);
a < b; // not defined
4
See, e.g., h)p://math.stackexchange.com/a/488015/79684
complex is non-regular
As such, complex is formally a non-regular type
complex a(5, 2);
complex b(8, 0);
a < b; // not defined
complex is non-regular
However, this might be too restric1ve. For this reason, we opt for a
more fine-grained classifica.on
A relaxed no+on of regularity
We relax our defini.on of regular types and consider regular also
types for which it is not possible to devise a natural order
complex a(5, 2);
complex b(8, 0);
a < b; // not defined
Totally ordered types
We instead refer to regular types for which a natural order is
possible as totally ordered types
Semi-regular types
Similarly, we refer to type that supports equality-preserving copy
and assignment without being equality comparable as semi-regular
Semi-regular types
Intui&vely, this means that a semi-regular type is copyable
Type classifica,on
┌────────────────────────┬──────────┐
│ │ T a; │
│ ├──────────┤
│ │ T a = b; │
│ Semi-regular ├──────────┤
│ │ ~T(a); │
│ ├──────────┤
│ │ a = b; │
├────────────────────────┼──────────┤
│ │ a == b; │
│ Regular ├──────────┤
│ │ a != b; │
├────────────────────────┼──────────┤
│ Totally ordered │ a < b; │
└────────────────────────┴──────────┘
Making vector_int regular
vector_int
class vector_int {
public:
vector_int(): sz(0), elem(nullptr) {}
vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...}
vector_int(vector_int const& v): sz(v.sz), elem(new int[v.sz]) {...}
~vector_int() {...}
std::size_t size() const {...}
int operator[](std::size_t i) const {...}
int& operator[](std::size_t i) {...}
vector_int& operator=(vector_int const& v) {...}
private:
std::size_t sz;
int* elem;
};
Making vector_int regular
In order to make vector_int regular we need to define
(in)equality and the four rela-on operators
Equality
class vector_int {
public:
...
bool operator==(vector_int const& v) const {
if (sz != v.sz) return false;
for (std::size_t i = 0; i < sz; ++i)
if (elem[i] != v.elem[i]) return false;
return true;
}
};
Less-than operator
class vector_int {
public:
...
bool operator<(vector_int const& v) const {
std::size_t min_size = std::min(sz, v.sz);
std::size_t i = 0;
while (i < min_size && elem[i] == v.elem[i]) ++i;
if (i < min_size)
return elem[i] < v.elem[i];
else
return sz < v.sz;
}
};
Bibliography
Bibliography
• J.C. Dehnert, A. Stepanov, Fundamentals of generic programming
• A. Stepanov, Notes on programming
• A. Stepanov, P. McJones, Elements of programming
Bibliography
• A. Stepanov, D. Rose, From Mathema6cs to Generic
Programming
• A. Stepanov, Efficient Programming with Components (Lecture 1
and Lecture 2)

Regular types in C++

  • 1.
    Regular types Ilio Catallo- info@iliocatallo.it
  • 2.
    Outline • Fundamental opera/ons •Regular types • Semi-regular & totally ordered types • Making vector_int regular • Bibliography
  • 3.
  • 4.
    Commonali(es among types Whatis in common between chars and doubles1 ? char c1 = 'a', c2 = 'b'; double d1 = 1.3, d2 = 2.7; 1 For the remainder of this presenta1on, we will limit ourselves to non-singular double values, i.e., we will exclude NaN from the set of possible values
  • 5.
    Commonali(es among types Fora start, both chars and doubles and can be swapped std::swap(c1, c2); std::swap(d1, d2);
  • 6.
  • 7.
    swap implementa*on fordoubles Let us look at the implementa0on of swap as if it was wri4en just for doubles void swap(double& x, double& y) { double tmp = x; x = y; y = tmp; }
  • 8.
    Requirements on doubles Whichrequirements on doubles are needed for swap to work? void swap(double& x, double& y) { double tmp = x; x = y; y = tmp; }
  • 9.
    Requirements on doubles First,it should be possible to copy construct a double void swap(double& x, double& y) { double tmp = x; // tmp is copy constructed from x x = y; y = tmp; }
  • 10.
    Requirements on doubles Second,it should be possible to assign a double void swap(double& x, double& y) { double tmp = x; x = y; // x is assigned y y = tmp; // y is assigned tmp }
  • 11.
    Requirements on doubles Giventhat double values are in fact both assignable and copy construc1ble, we can run swap on them std::swap(d1, d2);
  • 12.
    swap implementa*on forchars Let us now turn our a,en-on to a version of swap tailored for chars void swap(char& x, char& y) { char tmp = x; x = y; y = tmp; }
  • 13.
    Requirements on chars Asbefore, we no,ce that char values need to be assignable and copy construc1ble void swap(char& x, char& y) { char tmp = x; x = y; y = tmp; }
  • 14.
    Requirements on chars Asbefore, since char values are in fact assignable and copy construc1ble, it is possible to swap them std::swap(c1, c2);
  • 15.
    Swapping built-in types Itis immediate to see that our finding generalizes to all built-in types std::swap(b1, b2); // b1 and b2 are bools std::swap(f1, f2); // f1 and f2 are floats std::swap(i1, i2); // i1 and i2 are ints
  • 16.
    Common opera*ons We concludethat assignment and copy construc0on are opera/ons common to all built-in types
  • 17.
    Common opera*ons That is,for any built-in type T the following is always possible: T a; a = b; // assignment T c = d; // copy construction
  • 18.
    Common opera*ons In lightof this, we can write a uniform implementa.on of swap for all built-in types void swap(T& x, T& y) { // T is a built-in type T tmp = x; x = y; y = tmp; }
  • 19.
    Do built-in typesshare any addi3onal opera3ons?
  • 20.
    Fundamental opera.ons As ama&er of fact, we observe that there is a core set of opera4ons which are defined for all built-in types
  • 21.
    Fundamental opera.ons We callsuch opera-ons the fundamental opera.ons ┌────────────────────────┬──────────┐ │ Default construction │ T a; │ ├────────────────────────┼──────────┤ │ Copy construction │ T a = b; │ ├────────────────────────┼──────────┤ │ Destruction │ ~T(a); │ ├────────────────────────┼──────────┤ │ Assignment │ a = b; │ ├────────────────────────┼──────────┤ │ Equality │ a == b; │ ├────────────────────────┼──────────┤ │ Inequality │ a != b; │ ├────────────────────────┼──────────┤ │ Ordering │ a < b; │ └────────────────────────┴──────────┘
  • 22.
  • 23.
    Operators for UDTs TheC++ programming language allows the use of built-in type operator syntax for user-defined types (UDTs)
  • 24.
    Example: the point2 UDT structpoint { point(double x, double y): x(x), y(y) {} double x; double y; }; 2 Remember that there is no difference between a struct and a class, up to default members' visibility. Conven=onally, we use structs when our UDTs do not need a private sec=on
  • 25.
    Example: the pointUDT Intui&vely, it makes sense to define operator == as equality: bool operator==(point const& p1, point const& p2) { return p1.x == p2.x && p1.y == p2.y; }
  • 26.
    Overload seman-cs However, C++does not dictate any seman&c rules on operator overloading
  • 27.
    Overload seman-cs That is,developers are free to define == as: bool operator==(point const& p1, point const& p2) { return p1.x * p2.x > p1.y * p2.y; }
  • 28.
    Seman&cs mismatch This couldlead to a mismatch between the expected and the actual seman4cs of an operator
  • 29.
    Seman&cs mismatch This couldlead to a mismatch between the expected and the actual seman4cs of an operator ( °□°
  • 30.
    Seman&cs mismatch "I admirethe commi.ee dedica/on to programming freedom, but I claim that such freedom is an illusion. Finding the laws that govern so=ware components gives us freedom to write complex programs the same way that finding the laws of physics allows us to construct complex mechanical and electrical systems." (A. Stepanov)
  • 31.
    Built-in types asa theore1cal founda1on The opera)ons common to all built-in types are among the most well-understood and pervasively used
  • 32.
    Built-in types asa theore1cal founda1on This is because, over the years, the development of built-in types has led to consistent defini9ons that match • the programmer intui-on, and • the underlying mathema-cal understanding
  • 33.
    Idea: deriving theseman.cs of user-defined types from that of built-in types
  • 34.
    Preserving opera-on seman-cs Theidea is that when a code fragment has a certain meaning for built-in types, it should preserve the same meaning for UDTs
  • 35.
    Regular types To thisend, we introduce the concept of a regular type as a type behaving like the built-in types
  • 36.
    Regular types We callsuch types regular, since their use guarantees regularity of behavior and – therefore – interoperability
  • 37.
    std::vector is regular Forinstance, std::vector<T> is regular whenever T is regular
  • 38.
    std::vector is regular Aswith built-in types, std::vector<T> is thus copy construc+ble and assignable for each regular type T std::vector<int> w = v; // copy construction std::vector<int> u; u = w; // assignment
  • 39.
    std::vector is swappable Hence,we can swap3 two vectors of any regular type T: std::vector<int> v = {7, 3, 5}; std::vector<int> w = {10, 11, 4}; std::swap(v, w); 3 In reality, for the sake of performance, the Standard Library provides a specialized version std::swap for the specific case of std::vector. Although less performing, the general purpose version would however equally work
  • 40.
    How can weassure our UDTs to be regular?
  • 41.
    What does itmean to be regular? Ul#mately, we need to inves#gate how several of the built-in operators should behave when applied to user-defined types
  • 42.
    What does itmean to be regular? This amounts to understanding how to apply the fundamental opera5ons to UDTs ┌────────────────────────┬──────────┐ │ Default construction │ T a; │ ├────────────────────────┼──────────┤ │ Copy construction │ T a = b; │ ├────────────────────────┼──────────┤ │ Destruction │ ~T(a); │ ├────────────────────────┼──────────┤ │ Assignment │ a = b; │ ├────────────────────────┼──────────┤ │ Equality │ a == b; │ ├────────────────────────┼──────────┤ │ Inequality │ a != b; │ ├────────────────────────┼──────────┤ │ Ordering │ a < b; │ └────────────────────────┴──────────┘
  • 43.
    What does itmean to be regular? First, we no,ce that the fundamental opera,ons can be divided into three groups ┌────────────────────────┬──────────┐ │ │ T a; │ │ ├──────────┤ │ │ T a = b; │ │ Copying and assignment ├──────────┤ │ │ ~T(a); │ │ ├──────────┤ │ │ a = b; │ ├────────────────────────┼──────────┤ │ │ a == b; │ │ Equality ├──────────┤ │ │ a != b; │ ├────────────────────────┼──────────┤ │ Ordering │ a < b; │ └────────────────────────┴──────────┘
  • 44.
    What does itmean to be regular? Let us now inves,gate in greater detail the proper%es of each such group ┌────────────────────────┬──────────┐ │ │ T a; │ │ ├──────────┤ │ │ T a = b; │ │ Copying and assignment ├──────────┤ │ │ ~T(a); │ │ ├──────────┤ │ │ a = b; │ ├────────────────────────┼──────────┤ │ │ a == b; │ │ Equality ├──────────┤ │ │ a != b; │ ├────────────────────────┼──────────┤ │ Ordering │ a < b; │ └────────────────────────┴──────────┘
  • 45.
  • 46.
    Copy and assignmentare interchangeable We know that built-in types allow wri4ng interchangeably: T a; a = b; // assignment T a = b; // copy construction
  • 47.
    Copy and assignmentare interchangeable The ra'onale behind this is that it allows natural code such as: T a; if (something) a = b; else a = c;
  • 48.
    Default construc.on Observe thatwe need default construc.on to make assignment and copy construc7on interchangeable T a = b; T a; a = b; T a;
  • 49.
    Default construc.on Note thatwe require a default constructed regular type to be assignable and destruc.ble
  • 50.
    Par$al construc$on That is,we require default ini2aliza2on only to par$ally form an object
  • 51.
    Preserving equality The nextstep is to note that for all built-in types copy construc5on and assignment are equality-preserving opera5ons T a = b; // copy construction a == b; // true!
  • 52.
    Preserving equality The nextstep is to note that for all built-in types copy construc5on and assignment are equality-preserving opera5ons T a; a = b; // assignment a == b; // true!
  • 53.
    Preserving equality • Copyconstruc-ng means to make a copy that is equal to the original • Assignment copies the right-hand side object to the le;-hand side object, leaving their values equal
  • 54.
    Dependency on equality Sincewe need the ability to test for equality, we have that copy construc7on and assignment depend on equality
  • 55.
    A refined defini)onof regularity In other words, regular types possess the equality operator and equality-preserving copy construc4on and assignment
  • 56.
    Defining equality Although equalityis conceptually central, we do not yet have a sa.sfactory defini.on of equality of two objects of a regular type
  • 57.
  • 58.
    Equality for built-intypes On built-in types, the equality rela3on is normally defined as bitwise equality
  • 59.
    Equality for UDTs Star%ngfrom this, we can devise a natural defini,on of equality for user-defined types
  • 60.
    Equality for UDTs Namely,we can define equality of user-defined types from the equality of its parts
  • 61.
    Equality for UDTs However,in order to build the equality operator from the equality operator of its parts, we must iden%fy its parts
  • 62.
    Example: the pointtype struct point { point(double x, double y): x(x), y(y) {} double x; double y; };
  • 63.
    Example: operator== forpoints bool operator==(point const& p1, point const& p2) { return p1.x == p2.x && p1.y == p2.y; }
  • 64.
    Remote parts Complex objectsare o&en constructed out of mul0ple simpler objects, connected by pointers
  • 65.
    Remote parts In suchcases, we say that the given complex object has remote parts
  • 66.
    Example: the persontype struct mail_address {...}; class person { public: person(...) {...} private: std::string name; std::string surname; mail_address* address; };
  • 67.
    1st a&empt: operator== forpersons friend bool operator==(person const& p1, person const& p2) { return p1.name == p2.name && p1.surname == p2.surname; }
  • 68.
    2nd a&empt: operator== forpersons friend bool operator==(person const& p1, person const& p2) { return p1.name == p2.name && p1.surname == p2.surname && p1.address == p2.address; }
  • 69.
    3rd a&empt: operator== forpersons friend bool operator==(person const& p1, person const& p2) { return p1.name == p2.name && p1.surname == p2.surname && *p1.address == *p2.address; }
  • 70.
    Comparing remote parts Forobjects with remote parts, the equality operator must compare the corresponding remote parts, rather than the pointers to them
  • 71.
    Comparing remote parts Wedo not want objects with equal remote parts to compare unequal just because the remote parts were in different memory loca3ons
  • 72.
    Defini&on of equality Wecan therefore devise the following defini4on of equality: Two objects are equal if their corresponding parts are equal (applied recursively), including remote parts (but not comparing their addresses), excluding inessen>al components, and excluding components which iden>fy related objects
  • 73.
    Equality comes withinequality It is not enough to define equality, we also need to define inequality to match it
  • 74.
    Unequal vs. notequal We need to do so in order to preserve the ability to write interchangeably: a != b; // unequal !(a == b); // not equal
  • 75.
    Unequal vs. notequal That is, the statements "two things are unequal to each other" and "two 6ngs are not equal to each other" should be equivalent a != b; // unequal !(a == b); // not equal
  • 76.
    Inequality operator Fortunately, thisis very simple: // T is some user-defined type bool operator!=(T const& x, T const& y) { return !(x == y); }
  • 77.
    Equality and inequality C++does not enforce that equality and inequality should be defined together, so remember to define inequality every 7me equality is defined
  • 78.
  • 79.
    The importance ofordering We need to have ordering if we want to find things quickly, as ordering enables sor$ng, and therefore binary search
  • 80.
    Ordering For defining orderingon a user-defined type we need to set in place the four rela%onal operators <, >, <=, >=
  • 81.
    Rela%onal operators However, weneed to implement only one rela(onal operator, which can then be used to derive the defini8on on the remaining three
  • 82.
    Less-than operator We choose< as the primary operator, since when we refer to ordering we usually mean ascending ordering
  • 83.
    Less-than operator seman.cs Intui&vely,since we are overloading the less-than operator <, the ordering criterion we implement must behave like <
  • 84.
    How to makeour overload behave the way that < behaves?
  • 85.
    Strictness First, let usno-ce this natural property of < when used as an arithme-c operator: 5 ≮ 5 12 ≮ 12 etc.
  • 86.
    Strictness Hence, also ourordering rela0on of choice must be strict, that is: a ≮ a for all a in T
  • 87.
    Is this defini+onstrict? // T is some user-defined type bool operator<(T const& x, T const& y) { return true; }
  • 88.
    Is this defini+onstrict? // T is some user-defined type bool operator<(T const& x, T const& y) { return true; // according to this 5 < 5, // which violates strictness }
  • 89.
    Transi'vity Second, it isimmediate to see that the less-than operator is transi've: 4 < 7 and 7 < 12 implies 4 < 12
  • 90.
    Transi'vity Hence, also ourordering rela0on of choice must be transi've // T is some user-defined type bool operator<(T const& x, T const& y) { // maintain transitivity }
  • 91.
    Trichotomy law Finally, theless-than operator < obeys the trichotomy law: For every pair of elements, exactly one of the following holds: a < b, b < a or a == b
  • 92.
    Trichotomy law Note thatthe trichotomy law requires the defini5on of < to be consistent with the defini5on of == so that: !(a < b) && !(b < a) is equivalent to a == b
  • 93.
    Equality is central Hence,as with assignment and copy, also ordering depends on equality !(a < b) && !(b < a) is equivalent to a == b
  • 94.
    Is this awell-formed overload? bool operator<(point const& p1, point const& p2) { return p1.x < p2.x; }
  • 95.
    Is this awell-formed overload? It respects strictness bool operator<(point const& p1, point const& p2) { return p1.x < p2.x; // (5, 2) ≮ (5, 2) }
  • 96.
    Is this awell-formed overload? It respects transi,vity bool operator<(point const& p1, point const& p2) { return p1.x < p2.x; // (5,2) < (6, 3) < (10, 5) }
  • 97.
    Is this awell-formed overload? But it does not obey to the trichotomy law bool operator<(point const& p1, point const& p2) { return p1.x < p2.x; // (5, 3) ≮ (5, 8) and // (5, 8) ≮ (5, 3) but... // (5, 8) ≠ (5, 3) }
  • 98.
    Is this awell-formed overload? bool operator<(point const& p1, point const& p2) { if (p1.x < p2.x) return true; if (p1.x > p2.x) return false; return p1.y < p2.y; }
  • 99.
    Is this awell-formed overload? We are imposing a lexicographic ordering over points bool operator<(point const& p1, point const& p2) { if (p1.x < p2.x) return true; if (p1.x > p2.x) return false; return p1.y < p2.y; }
  • 100.
    Is this awell-formed overload? Lexicographic ordering respects strictness, transi2vity and the trichotomy law bool operator<(point const& p1, point const& p2) { if (p1.x < p2.x) return true; if (p1.x > p2.x) return false; return p1.y < p2.y; }
  • 101.
    Strict total ordering Werefer to an ordering rela.on that is strict, transi.ve and that obeys the trichotomy law as a strict total ordering
  • 102.
    Remaining operators As withequality, we must define manually the remaining rela5onal operators >, >=, <=
  • 103.
    Greater-than operators // Tis some user-defined type bool operator>(T const& x, T const& y) { return y < x; }
  • 104.
    Greater-or-equal-than operator // Tis some user-defined type bool operator>=(T const& x, T const& y) { return !(x < y); }
  • 105.
    Less-or-equal-than operator // Tis some user-defined type bool operator<=(T const& x, T const& y) { return !(y < x); }
  • 106.
  • 107.
    Regularity Some%mes, although atype looks like a regular type, we cannot formally guarantee its regularity
  • 108.
    complex seems regular Forinstance, the complex type we previously wrote feels like a regular type complex a; // default construction complex b = a; // copy construction complex c; c = b; // assignment a == b; // equality a != c; // inequality
  • 109.
    complex numbers cannotbe ordered However, we cannot impose a natural order on complex numbers4 complex a(5, 2); complex b(8, 0); a < b; // not defined 4 See, e.g., h)p://math.stackexchange.com/a/488015/79684
  • 110.
    complex is non-regular Assuch, complex is formally a non-regular type complex a(5, 2); complex b(8, 0); a < b; // not defined
  • 111.
    complex is non-regular However,this might be too restric1ve. For this reason, we opt for a more fine-grained classifica.on
  • 112.
    A relaxed no+onof regularity We relax our defini.on of regular types and consider regular also types for which it is not possible to devise a natural order complex a(5, 2); complex b(8, 0); a < b; // not defined
  • 113.
    Totally ordered types Weinstead refer to regular types for which a natural order is possible as totally ordered types
  • 114.
    Semi-regular types Similarly, werefer to type that supports equality-preserving copy and assignment without being equality comparable as semi-regular
  • 115.
    Semi-regular types Intui&vely, thismeans that a semi-regular type is copyable
  • 116.
    Type classifica,on ┌────────────────────────┬──────────┐ │ │T a; │ │ ├──────────┤ │ │ T a = b; │ │ Semi-regular ├──────────┤ │ │ ~T(a); │ │ ├──────────┤ │ │ a = b; │ ├────────────────────────┼──────────┤ │ │ a == b; │ │ Regular ├──────────┤ │ │ a != b; │ ├────────────────────────┼──────────┤ │ Totally ordered │ a < b; │ └────────────────────────┴──────────┘
  • 117.
  • 118.
    vector_int class vector_int { public: vector_int():sz(0), elem(nullptr) {} vector_int(std::size_t sz): sz(sz), elem(new int[sz]) {...} vector_int(vector_int const& v): sz(v.sz), elem(new int[v.sz]) {...} ~vector_int() {...} std::size_t size() const {...} int operator[](std::size_t i) const {...} int& operator[](std::size_t i) {...} vector_int& operator=(vector_int const& v) {...} private: std::size_t sz; int* elem; };
  • 119.
    Making vector_int regular Inorder to make vector_int regular we need to define (in)equality and the four rela-on operators
  • 120.
    Equality class vector_int { public: ... booloperator==(vector_int const& v) const { if (sz != v.sz) return false; for (std::size_t i = 0; i < sz; ++i) if (elem[i] != v.elem[i]) return false; return true; } };
  • 121.
    Less-than operator class vector_int{ public: ... bool operator<(vector_int const& v) const { std::size_t min_size = std::min(sz, v.sz); std::size_t i = 0; while (i < min_size && elem[i] == v.elem[i]) ++i; if (i < min_size) return elem[i] < v.elem[i]; else return sz < v.sz; } };
  • 122.
  • 123.
    Bibliography • J.C. Dehnert,A. Stepanov, Fundamentals of generic programming • A. Stepanov, Notes on programming • A. Stepanov, P. McJones, Elements of programming
  • 124.
    Bibliography • A. Stepanov,D. Rose, From Mathema6cs to Generic Programming • A. Stepanov, Efficient Programming with Components (Lecture 1 and Lecture 2)