Skip to content

Commit 2338cb8

Browse files
committed
Review operators exercise and split a preceding classes exercise from it.
1 parent 86a5878 commit 2338cb8

16 files changed

+516
-226
lines changed

exercises/ExerciseSchedule_EssentialCourse.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,12 @@ People should replay them and discover the tools by themselves.
3737
Day 2 - OO Exercises
3838
--------------------
3939

40-
### Polymorphism (directory: [`polymorphism`](polymorphism), [CheatSheet](ExercisesCheatSheet.md#polymorphism-directory-polymorphism))
40+
### My first classes (directory: [`classes`](classes), [CheatSheet](ExercisesCheatSheet.md#classes-directory-classes))
4141

4242
### Operator overloading (directory: [`operators`](operators), [CheatSheet](ExercisesCheatSheet.md#operator-overloading-directory-operators))
4343

44+
### Polymorphism (directory: [`polymorphism`](polymorphism), [CheatSheet](ExercisesCheatSheet.md#polymorphism-directory-polymorphism))
45+
4446
### Virtual inheritance (directory: [`virtual_inheritance`](virtual_inheritance), [CheatSheet](ExercisesCheatSheet.md#virtual-inheritance-directory-virtual_inheritance))
4547

4648

exercises/ExercisesCheatSheet.md

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,37 @@ The idea of this exercise is to play with all kinds of possible loops and contro
4545
Object-orientation Exercises
4646
----------------------------
4747

48+
### Classes (directory: [`classes`](classes))
49+
50+
This exercise is about doing a basic class, not using any operator overloading. It also paves the way for object-functions, making a first algo-like class.
51+
52+
You may discuss with the students:
53+
- pros and cons of making the constructor explicit or not,
54+
- pros and cons of having multiply() and equal() either as member functions,
55+
friend free functions or external free functions.
56+
- optionally, thed difference between equality and equivalence.
57+
58+
NOTE: I did not find a justified need to add operator=...
59+
60+
### Operator overloading (directory: [`operators`](operators))
61+
62+
Here we take the class from the previous exercise, and we try to make
63+
anything an operator that makes sense.
64+
65+
Tips and tricks:
66+
- When the argument of CHECK() has a comma not between parentheses, for example
67+
curly braces, add an additional global level of parenthesis, or CHECK() will
68+
think it has several arguments.
69+
70+
You may discuss with the students:
71+
- the chaining of operator<<
72+
- in a set of consistent operators (such as == and !=),
73+
reusing versus performance.
74+
- object-functions.
75+
- for what concerns the related operators, such as * and *=,
76+
the choice to be made consitent reuse or performant
77+
specialization of each operator.
78+
4879
### Polymorphism (directory: [`polymorphism`](polymorphism))
4980

5081
First create a Pentagon and an Hexagon and call computePerimeter. Can be used to break the ice.
@@ -79,22 +110,6 @@ See and solve the compilation issue about missing Drawable constructor. Understa
79110
See the new id being printed twice.
80111

81112

82-
### Operator overloading (directory: [`operators`](operators))
83-
84-
This exercise is about making `main` run successfully by completing the implementation of `Fraction`.
85-
Implement a constructor for `Fraction` and add two integer data members for numerator and denominator.
86-
Comment out everything in `main` except the first two LOCs.
87-
This should compile now and print nothing.
88-
89-
Then uncomment the `std::cout` statements and implement `operator<<` for `Fraction`.
90-
Compile and run.
91-
92-
Proceed this way through the entire exercise.
93-
There are multiple possibilities to implement some operators, e.g. as members, as hidden friends, or as free functions.
94-
Also when and where to normalize a fraction is up to the students.
95-
All solutions are fine, as long as the `main` function runs successfully.
96-
97-
98113
Modern C++ Exercises
99114
--------------------
100115

exercises/classes/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
classes classes_sol

exercises/classes/CMakeLists.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Set up the project.
2+
cmake_minimum_required( VERSION 3.12 )
3+
project( operators LANGUAGES CXX )
4+
5+
# Set up the compilation environment.
6+
include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" )
7+
8+
# Create the user's executable.
9+
add_executable( classes "classes.cpp" )
10+
11+
# Create the "solution executable".
12+
add_executable( classes_sol EXCLUDE_FROM_ALL "solution/classes_sol.cpp" )
13+
add_dependencies( solution classes_sol )

exercises/classes/Makefile

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
all: classes
2+
solution: classes_sol
3+
4+
clean:
5+
rm -f *o classes *~ classes_sol
6+
7+
classes : classes.cpp
8+
${CXX} -g -std=c++17 -O0 -Wall -Wextra -L. -o $@ $<
9+
10+
classes_sol : solution/classes_sol.cpp
11+
${CXX} -g -std=c++17 -O0 -Wall -Wextra -L. -o $@ $<

exercises/classes/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
2+
## Instructions
3+
4+
STEP 1
5+
- Complete the class Fraction so that a Fraction can be construct from one or two integer.
6+
Check the first two lines of main() are working (comment out the rest)
7+
- Add the function equal().
8+
Check the second section of main() works.
9+
- Add the function multiply().
10+
Check the whole main() works.
11+
12+
STEP 2
13+
- Replace the function printTestResult() by a class TestResultPrinter
14+
with a method process() that take the same arguments as before.
15+
Upgrade CHECK() and main().
16+
- Transform the WIDTH constant into a variable member of TestResultPrinter,
17+
which is initialized in its constructor.
18+
Upgrade main().
19+
20+
OPTIONAL STEP 3
21+
- Move multiply() and compare() as friend functions within the class Fraction.
22+
Check main() works.
23+
- Remove the accessors numerator() and denominator().
24+
Check main() works.
25+
26+
OPTIONAL STEP 4
27+
- Remove the systematic call to normalize().
28+
- Add a equivalent() method to Fraction.
29+
- Upgrade the tests accordingly.
30+
- Transform the private normalize() into a public const normalized() method
31+
which return the normalized fraction.
32+
- Add some tests to check normalized().
33+

exercises/classes/classes.cpp

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
#include <iomanip>
2+
#include <iostream>
3+
#include <numeric>
4+
5+
class Fraction {
6+
7+
public:
8+
9+
// ADD YOUR CODE HERE
10+
11+
std::string str() const {
12+
std::ostringstream oss;
13+
oss << m_num << '/' << m_denom;
14+
return oss.str();
15+
}
16+
17+
int numerator() const {
18+
return m_num;
19+
}
20+
int denominator() const {
21+
return m_denom;
22+
}
23+
24+
private:
25+
26+
void normalize() {
27+
const int gcd = std::gcd(m_num, m_denom);
28+
m_num /= gcd;
29+
m_denom /= gcd;
30+
}
31+
32+
int m_num, m_denom;
33+
};
34+
35+
// ADD YOUR CODE HERE
36+
37+
#define CHECK(print,what) print(#what, what)
38+
39+
unsigned int WIDTH {20};
40+
41+
void printTestResult(std::string const & what, bool passed) {
42+
std::cout << std::setw(WIDTH) << what << ": " << (passed ? "PASS" : "** FAIL **") << '\n';
43+
}
44+
45+
int main() {
46+
47+
// create a fraction with values 3 (which is 3/1) and 1/3
48+
std::cout<<std::endl;
49+
const Fraction three{3};
50+
const Fraction third{1, 3};
51+
std::cout<<three.str()<<' '<<third.str()<<'\n';
52+
53+
// equality
54+
std::cout<<std::endl;
55+
CHECK(printTestResult,equal(three,three));
56+
CHECK(printTestResult,equal(third,third));
57+
CHECK(printTestResult,equal(three,Fraction{3}));
58+
CHECK(printTestResult,equal(three,Fraction{3, 1}));
59+
CHECK(printTestResult,equal(third,Fraction{1, 3}));
60+
CHECK(printTestResult,equal(Fraction{3},three));
61+
CHECK(printTestResult,equal(Fraction{1, 3},third));
62+
CHECK(printTestResult,equal(third,Fraction{2, 6}));
63+
64+
// multiply
65+
std::cout<<std::endl;
66+
CHECK(printTestResult,equal(multiply(third,2),Fraction{2, 3}));
67+
CHECK(printTestResult,equal(multiply(2,third),Fraction{2, 3}));
68+
CHECK(printTestResult,equal(multiply(three,third),Fraction{1, 1}));
69+
CHECK(printTestResult,equal(multiply(3,third),1));
70+
71+
// end
72+
std::cout<<std::endl;
73+
74+
}
75+

exercises/classes/classes_sol

94.9 KB
Binary file not shown.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#include <iomanip>
2+
#include <iostream>
3+
#include <sstream>
4+
#include <numeric>
5+
6+
class Fraction {
7+
8+
public:
9+
10+
Fraction(int a_num, int a_denom = 1) : m_num(a_num), m_denom(a_denom) {}
11+
12+
std::string str() const {
13+
std::ostringstream oss;
14+
oss << m_num << '/' << m_denom;
15+
return oss.str();
16+
}
17+
18+
friend bool equal( Fraction const & lhs, Fraction const & rhs ) {
19+
return (lhs.m_num==rhs.m_num) && (lhs.m_denom==rhs.m_denom);
20+
}
21+
22+
friend bool equivalent( Fraction const & lhs, Fraction const & rhs ) {
23+
return (lhs.m_num*rhs.m_denom==rhs.m_num*lhs.m_denom);
24+
}
25+
26+
friend Fraction multiply( Fraction const & lhs, Fraction const & rhs ) {
27+
return {lhs.m_num*rhs.m_num, lhs.m_denom*rhs.m_denom};
28+
}
29+
30+
Fraction normalized() const {
31+
const int gcd = std::gcd(m_num, m_denom);
32+
return {m_num/gcd, m_denom/gcd};
33+
}
34+
35+
private:
36+
37+
int m_num, m_denom;
38+
};
39+
40+
class TestResultPrinter {
41+
42+
public:
43+
44+
TestResultPrinter( unsigned int a_width ) : m_width(a_width) {}
45+
46+
void process(std::string const & what, bool passed) {
47+
std::cout << std::left << std::setw(m_width) << what << ": " << (passed ? "PASS" : "** FAIL **") << '\n';
48+
}
49+
50+
private:
51+
52+
unsigned int m_width;
53+
54+
};
55+
56+
#define CHECK(printer,what) printer.process(#what, what)
57+
58+
int main() {
59+
60+
// create a fraction with values 3 (which is 3/1) and 1/3
61+
std::cout<<std::endl;
62+
const Fraction three{3};
63+
const Fraction third{1, 3};
64+
std::cout<<three.str()<<' '<<third.str()<<'\n';
65+
66+
// equality
67+
std::cout<<std::endl;
68+
TestResultPrinter p1{27};
69+
CHECK(p1,equal(three,three));
70+
CHECK(p1,equal(third,third));
71+
CHECK(p1,equal(three,Fraction{3}));
72+
CHECK(p1,equal(three,Fraction{3,1}));
73+
CHECK(p1,equal(third,Fraction{1,3}));
74+
CHECK(p1,equal(Fraction{3},three));
75+
CHECK(p1,equal(Fraction{1,3},third));
76+
77+
// equivalence
78+
std::cout<<std::endl;
79+
TestResultPrinter p2{40};
80+
CHECK(p2,!equal(third,Fraction{2,6}));
81+
CHECK(p2,equivalent(third,Fraction{2,6}));
82+
CHECK(p2,equal(third,Fraction{2,6}.normalized()));
83+
84+
// multiply
85+
std::cout<<std::endl;
86+
TestResultPrinter p3{48};
87+
CHECK(p3,equal(multiply(third,2),Fraction{2,3}));
88+
CHECK(p3,equal(multiply(2,third),Fraction{2,3}));
89+
CHECK(p3,equivalent(multiply(three,third),Fraction{1,1}));
90+
CHECK(p3,equivalent(multiply(3,third),1));
91+
92+
// end
93+
std::cout<<std::endl;
94+
95+
}

exercises/operators/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
operators
2+
operators_sol

exercises/operators/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ project( operators LANGUAGES CXX )
44

55
# Set up the compilation environment.
66
include( "${CMAKE_CURRENT_SOURCE_DIR}/../common.cmake" )
7+
set( CMAKE_CXX_STANDARD 20 )
78

89
# Create the user's executable.
910
add_executable( operators "operators.cpp" )
1011

1112
# Create the "solution executable".
12-
add_executable( operators.sol EXCLUDE_FROM_ALL "solution/operators.sol.cpp" )
13-
add_dependencies( solution operators.sol )
13+
add_executable( operators_sol EXCLUDE_FROM_ALL "solution/operators_sol.cpp" )
14+
add_dependencies( solution operators_sol )

exercises/operators/Makefile

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
all: operators
2-
solution: operators.sol
2+
solution: operators_sol
33

44
clean:
5-
rm -f *o operators *~ operators.sol
5+
rm -f *o operators *~ operators_sol
66

77
operators : operators.cpp
8-
${CXX} -g -std=c++17 -O0 -Wall -Wextra -L. -o $@ $<
8+
${CXX} -g -std=c++20 -O0 -Wall -Wextra -L. -o $@ $<
99

10-
operators.sol : solution/operators.sol.cpp
11-
${CXX} -g -std=c++17 -O0 -Wall -Wextra -L. -o $@ $<
10+
operators_sol : solution/operators_sol.cpp
11+
${CXX} -g -std=c++20 -O0 -Wall -Wextra -L. -o $@ $<

exercises/operators/README.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,27 @@
11

22
## Instructions
33

4-
* inspect main and complete the implementation of class Fraction step by step
5-
* you can comment out parts of main to test in between
6-
* when possible, prefer non-member operators and friend for operators
4+
STEP 1
5+
- Add a free operator<<, reusing str(), and simplify main() first lines.
6+
- Replace equal() with operator==(), and upgrade tests.
7+
- Add operator!=(), reusing operator==(), and upgrade tests.
8+
- Replace compare() with operator<=>(), reusing <=> between doubles,
9+
and upgrade tests.
10+
- Replace multiply() with operator*(), and upgrade tests.
11+
12+
STEP 2
13+
- Replace TestResultPrinter::process() with operator()(), and upgrade CHECK().
14+
15+
OPTIONAL STEP 3
16+
- Add an inplace multiplication operator*=(), and add tests.
17+
- Review operator*() so to reuse operator*=().
18+
- Ensure calls to operator*=() can be chained, the same as operator<<().
19+
20+
## Take aways
21+
22+
- Do not confuse equality and equivalence.
23+
- We can very often implement an arithemtic operator@ in terms of operator@=.
24+
- When implementing <=>, you get <, >, <=, >= for free.
25+
- Object-functions are very used with standard algorithms,
26+
yet tend to be often replaced by lambdas in modern C++.
27+

0 commit comments

Comments
 (0)