From d5536d32ae79192980ce44adc9a5e2d827eab040 Mon Sep 17 00:00:00 2001 From: Stephan Hageboeck Date: Mon, 27 Oct 2025 18:32:02 +0100 Subject: [PATCH 1/2] Simplify the CHECK() macro of the operator exercise. The first argument of the check macro is unnecessary if all checks are printed using the same width. This makes the code easier to read. Using the variadic macro approach from the previous commit, the solution could also be simplified. --- exercises/operators/operators.cpp | 45 ++++++------ .../operators/solution/operators_sol.cpp | 72 +++++++++---------- 2 files changed, 55 insertions(+), 62 deletions(-) diff --git a/exercises/operators/operators.cpp b/exercises/operators/operators.cpp index fa69514f..45fa53ba 100644 --- a/exercises/operators/operators.cpp +++ b/exercises/operators/operators.cpp @@ -59,10 +59,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() { @@ -74,32 +74,29 @@ int main() { // equality std::cout<0); - CHECK(p2,compare(third,Fraction{2,4})<0); + CHECK(compare(third,Fraction{2,6})==0); + CHECK(compare(third,Fraction{1,4})>0); + CHECK(compare(third,Fraction{2,4})<0); // multiply std::cout<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,3})); - CHECK(p2,(third<=Fraction{2,3})); - CHECK(p2,!(thirdFraction{2,4})); - CHECK(p2,!(thirdFraction{2,3})); + CHECK(std::is_eq(third<=>Fraction{2,6})); + CHECK(std::is_gt(third<=>Fraction{1,4})); + CHECK(std::is_lt(third<=>Fraction{2,4})); + CHECK(third>Fraction{1,4}); + CHECK(third=Fraction{2,4})); + CHECK(third>=Fraction{1,4}); + CHECK(third<=Fraction{2,4}); + CHECK(third>=Fraction{1,3}); + CHECK(third<=Fraction{2,3}); + CHECK(!(thirdFraction{2,4})); + CHECK(!(thirdFraction{2,3})); // multiply std::cout<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(std::is_eq((three*third)<=>Fraction{1,1})); + CHECK(std::is_eq((3*third)<=>Fraction{1,1})); + CHECK((3*third).normalized()==1); // multiply in place std::cout<1)); - CHECK(p4,one.normalized()==1); - CHECK(p4,one!=1); + CHECK(std::is_eq(one<=>1)); + CHECK(one.normalized()==1); + CHECK(one!=1); // end std::cout< Date: Wed, 29 Oct 2025 17:11:43 +0100 Subject: [PATCH 2/2] Remove <=> and hidden friends from operators exercise. This exercise is used both in the essentials and the advanced course. We can therefore not require students to work with hidden friends and <=>. Here, all mentions of <=> are removed (there is a dedicated exercise on the third day of the advanced course), and the tasks are rephrased such that students can work on this exercise also in the essentials course. The diff between the original file and the solution is minimised, so viewing the solution with a diff program hopefully helps the students. --- exercises/operators/README.md | 31 ++++++------- exercises/operators/operators.cpp | 6 +++ .../operators/solution/operators_sol.cpp | 43 +++++++++---------- 3 files changed, 40 insertions(+), 40 deletions(-) diff --git a/exercises/operators/README.md b/exercises/operators/README.md index a1a644bd..17ae3552 100644 --- a/exercises/operators/README.md +++ b/exercises/operators/README.md @@ -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++. diff --git a/exercises/operators/operators.cpp b/exercises/operators/operators.cpp index 45fa53ba..f39218a9 100644 --- a/exercises/operators/operators.cpp +++ b/exercises/operators/operators.cpp @@ -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; @@ -86,6 +90,7 @@ int main() { // equivalence & comparison std::cout<0); CHECK(compare(third,Fraction{2,4})<0); @@ -98,6 +103,7 @@ int main() { CHECK(compare(multiply(3,third),Fraction{1,1})==0); CHECK(equal(multiply(3,third).normalized(),1)); + // end std::cout< #include #include -#include #include class Fraction { @@ -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) { @@ -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'; } @@ -73,7 +80,7 @@ class TestResultPrinter { // (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(...) TestResultPrinter{50}(#__VA_ARGS__, (__VA_ARGS__)) +#define CHECK(...) TestResultPrinter{50}.process(#__VA_ARGS__, (__VA_ARGS__)) int main() { @@ -97,38 +104,28 @@ int main() { // equivalence & comparison std::cout<Fraction{2,6})); - CHECK(std::is_gt(third<=>Fraction{1,4})); - CHECK(std::is_lt(third<=>Fraction{2,4})); - CHECK(third>Fraction{1,4}); - CHECK(third=Fraction{2,4})); - CHECK(third>=Fraction{1,4}); - CHECK(third<=Fraction{2,4}); - CHECK(third>=Fraction{1,3}); - CHECK(third<=Fraction{2,3}); - CHECK(!(thirdFraction{2,4})); - CHECK(!(thirdFraction{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<Fraction{1,1})); - CHECK(std::is_eq((3*third)<=>Fraction{1,1})); + 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<1)); + CHECK(compare(one, 1)==0); CHECK(one.normalized()==1); CHECK(one!=1); + // end std::cout<