Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 14 additions & 17 deletions exercises/operators/README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@

## Instructions

STEP 1
- Add a free operator<<, reusing str(), and simplify main() first lines.
- Replace equal() with operator==(), and upgrade tests.
- Add operator!=(), reusing operator==(), and upgrade tests.
- Replace compare() with operator<=>(), reusing <=> between doubles,
and upgrade tests.
- Replace multiply() with operator*(), and upgrade tests.
### Main Tasks
- Write an ostream operator with the following signature: `operator<<(ostream &, Fraction const &)`.
The `str()` function of Fraction will help you to implement it. Use this operator to make the `cout`s in the first lines of `main()` look a bit more natural.
- **Note**: If you do this exercise as part of the advanced course, implement the operators as hidden friends.
- Replace the function `equal()` with `operator==()`, and upgrade tests.
Note that equality isn't the same as equivalence. The compare function returns 0
for 1/1, 2/2, etc, but these are not equal.
- Add `operator!=()`, reusing `operator==()`, and upgrade tests.
- Replace `multiply()` with `operator*()`, and upgrade tests.

STEP 2
- Replace TestResultPrinter::process() with operator()(), and upgrade CHECK().

OPTIONAL STEP 3
- Add an inplace multiplication operator*=(), and add tests.
- Review operator*() so to reuse operator*=().
- Ensure calls to operator*=() can be chained, the same as operator<<().
### Optional if you have time
- Add an inplace multiplication `operator*=()`, and add tests.
- Review `operator*()` so to reuse `operator*=()`.
- Ensure calls to `operator*=()` can be chained, the same as `operator<<()`.

## Take away

- Operators can make certain expressions much more readable.
- Do not confuse equality and equivalence.
- We can very often implement an arithmetic operator@ in terms of operator@=.
- When implementing <=>, you get <, >, <=, >= for free.
- Object-functions are very used with standard algorithms,
yet tend to be often replaced by lambdas in modern C++.
51 changes: 27 additions & 24 deletions exercises/operators/operators.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ class Fraction {
return (lhs.m_num==rhs.m_num) && (lhs.m_denom==rhs.m_denom);
}

// This function compares two fractions, and returns
// -1 if lhs < rhs
// 0 if they denote the same value (equivalence)
// 1 if lhs > rhs
friend int compare( Fraction const & lhs, Fraction const & rhs ) {
int v1 = lhs.m_num * rhs.m_denom;
int v2 = rhs.m_num * lhs.m_denom;
Expand Down Expand Up @@ -59,10 +63,10 @@ class TestResultPrinter {
};

// This is using the cpp, the C preprocessor to expand a bit of code
// (the what argument) to a pair containing a string representation
// (the arguments in '...') to a pair containing a string representation
// of it and the code itself. That way, print is given a string and a
// value where the string is the code that lead to the value
#define CHECK(printer, ...) printer.process(#__VA_ARGS__, (__VA_ARGS__))
#define CHECK(...) TestResultPrinter{50}.process(#__VA_ARGS__, (__VA_ARGS__))

int main() {

Expand All @@ -74,32 +78,31 @@ int main() {

// equality
std::cout<<std::endl;
TestResultPrinter p1{40};
CHECK(p1,equal(three,three));
CHECK(p1,equal(third,third));
CHECK(p1,equal(three,Fraction{3}));
CHECK(p1,equal(three,Fraction{3,1}));
CHECK(p1,equal(third,Fraction{1,3}));
CHECK(p1,equal(Fraction{3},three));
CHECK(p1,equal(Fraction{1,3},third));
CHECK(p1,!equal(third,Fraction{2,6}));
CHECK(p1,equal(third,Fraction{2,6}.normalized()));

// equivalence
CHECK(equal(three,three));
CHECK(equal(third,third));
CHECK(equal(three,Fraction{3}));
CHECK(equal(three,Fraction{3,1}));
CHECK(equal(third,Fraction{1,3}));
CHECK(equal(Fraction{3},three));
CHECK(equal(Fraction{1,3},third));
CHECK(!equal(third,Fraction{2,6}));
CHECK(equal(third,Fraction{2,6}.normalized()));

// equivalence & comparison
std::cout<<std::endl;
TestResultPrinter p2{32};
CHECK(p2,compare(third,Fraction{2,6})==0);
CHECK(p2,compare(third,Fraction{1,4})>0);
CHECK(p2,compare(third,Fraction{2,4})<0);
CHECK(!equal(third,Fraction{2,6}));
CHECK(compare(third,Fraction{2,6})==0);
CHECK(compare(third,Fraction{1,4})>0);
CHECK(compare(third,Fraction{2,4})<0);

// multiply
std::cout<<std::endl;
TestResultPrinter p3{48};
CHECK(p3,equal(multiply(third,2),Fraction{2,3}));
CHECK(p3,equal(multiply(2,third),Fraction{2,3}));
CHECK(p3,compare(multiply(three,third),Fraction{1,1})==0);
CHECK(p3,compare(multiply(3,third),Fraction{1,1})==0);
CHECK(p3,equal(multiply(3,third).normalized(),1));
CHECK(equal(multiply(third,2),Fraction{2,3}));
CHECK(equal(multiply(2,third),Fraction{2,3}));
CHECK(compare(multiply(three,third),Fraction{1,1})==0);
CHECK(compare(multiply(3,third),Fraction{1,1})==0);
CHECK(equal(multiply(3,third).normalized(),1));


// end
std::cout<<std::endl;
Expand Down
77 changes: 35 additions & 42 deletions exercises/operators/solution/operators_sol.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#include <iomanip>
#include <iostream>
#include <sstream>
#include <compare>
#include <numeric>

class Fraction {
Expand All @@ -24,8 +23,16 @@ class Fraction {
return !(lhs==rhs);
}

friend auto operator<=>( Fraction const & lhs, Fraction const & rhs ) {
return ((lhs.m_num*rhs.m_denom)<=>(rhs.m_num*lhs.m_denom));
// This function compares two fractions, and returns
// -1 if lhs < rhs
// 0 if they denote the same value (equivalence)
// 1 if lhs > rhs
friend int compare( Fraction const & lhs, Fraction const & rhs ) {
int v1 = lhs.m_num * rhs.m_denom;
int v2 = rhs.m_num * lhs.m_denom;
if (v1 < v2) return -1;
else if (v1 > v2) return 1;
else return 0;
}

Fraction & operator*=(Fraction const & other) {
Expand Down Expand Up @@ -59,7 +66,7 @@ class TestResultPrinter {

TestResultPrinter( unsigned int a_width ) : m_width(a_width) {}

void operator()(std::string const & what, bool passed) {
void process(std::string const & what, bool passed) {
std::cout << std::left << std::setw(m_width) << what << ": " << (passed ? "PASS" : "** FAIL **") << '\n';
}

Expand All @@ -70,10 +77,10 @@ class TestResultPrinter {
};

// This is using the cpp, the C preprocessor to expand a bit of code
// (the what argument) to a pair containing a string representation
// (the arguments in '...') to a pair containing a string representation
// of it and the code itself. That way, print is given a string and a
// value where the string is the code that lead to the value
#define CHECK(printer,what) printer(#what, what)
#define CHECK(...) TestResultPrinter{50}.process(#__VA_ARGS__, (__VA_ARGS__))

int main() {

Expand All @@ -85,53 +92,39 @@ int main() {

// equality
std::cout<<std::endl;
TestResultPrinter p1{36};
CHECK(p1,three==three);
CHECK(p1,third==third);
CHECK(p1,three==Fraction{3});
CHECK(p1,(three==Fraction{3,1}));
CHECK(p1,(third==Fraction{1,3}));
CHECK(p1,(Fraction{3}==three));
CHECK(p1,(Fraction{1,3}==third));
CHECK(p1,(third!=Fraction{2,6}));
CHECK(p1,third==(Fraction{2,6}.normalized()));
CHECK(three==three);
CHECK(third==third);
CHECK(three==Fraction{3});
CHECK(three==Fraction{3,1});
CHECK(third==Fraction{1,3});
CHECK(Fraction{3}==three);
CHECK(Fraction{1,3}==third);
CHECK(third!=Fraction{2,6});
CHECK(third==(Fraction{2,6}.normalized()));

// equivalence & comparison
std::cout<<std::endl;
TestResultPrinter p2{34};
CHECK(p2,std::is_eq(third<=>Fraction{2,6}));
CHECK(p2,std::is_gt(third<=>Fraction{1,4}));
CHECK(p2,std::is_lt(third<=>Fraction{2,4}));
CHECK(p2,(third>Fraction{1,4}));
CHECK(p2,(third<Fraction{2,4}));
CHECK(p2,!(third<=Fraction{1,4}));
CHECK(p2,!(third>=Fraction{2,4}));
CHECK(p2,(third>=Fraction{1,4}));
CHECK(p2,(third<=Fraction{2,4}));
CHECK(p2,(third>=Fraction{1,3}));
CHECK(p2,(third<=Fraction{2,3}));
CHECK(p2,!(third<Fraction{1,4}));
CHECK(p2,!(third>Fraction{2,4}));
CHECK(p2,!(third<Fraction{1,3}));
CHECK(p2,!(third>Fraction{2,3}));
CHECK(third!=Fraction{2,6});
CHECK(compare(third,Fraction{2,6})==0);
CHECK(compare(third,Fraction{1,4})>0);
CHECK(compare(third,Fraction{2,4})<0);

// multiply
std::cout<<std::endl;
TestResultPrinter p3{42};
CHECK(p3,((third*2)==Fraction{2,3}));
CHECK(p3,((2*third)==Fraction{2,3}));
CHECK(p3,std::is_eq((three*third)<=>Fraction{1,1}));
CHECK(p3,std::is_eq((3*third)<=>Fraction{1,1}));
CHECK(p3,((3*third).normalized()==1));
CHECK((third*2)==Fraction{2,3});
CHECK((2*third)==Fraction{2,3});
CHECK(compare(three*third, Fraction{1,1}) == 0);
CHECK(compare(3*third, Fraction{1,1}) == 0);
CHECK((3*third).normalized()==1);

// multiply in place
std::cout<<std::endl;
TestResultPrinter p4{20};
Fraction one {third};
((one *= 2) *= 3) *= Fraction{1,2};
CHECK(p4,std::is_eq(one<=>1));
CHECK(p4,one.normalized()==1);
CHECK(p4,one!=1);
CHECK(compare(one, 1)==0);
CHECK(one.normalized()==1);
CHECK(one!=1);


// end
std::cout<<std::endl;
Expand Down
Loading