Search

9.2 — Overloading the arithmetic operators using friend functions

Some of the most commonly used operators in C++ are the arithmetic operators -- that is, the plus operator (+), minus operator (-), multiplication operator (*), and division operator (/). Note that all of the arithmetic operators are binary operators -- meaning they take two operands -- one on each side of the operator. All four of these operators are overloaded in the exact same way.

It turns out that there are three different ways to overload operators: the member function way, the friend function way, and the normal function way. In this lesson, we’ll cover the friend function way (because it’s more intuitive for most binary operators). Next lesson, we’ll discuss the normal function way. Finally, in a later lesson in this chapter, we’ll cover the member function way. And, of course, we’ll also summarize when to use each in more detail.

Overloading operators using friend functions

Consider the following trivial class:

The following example shows how to overload operator plus (+) in order to add two “Cents” objects together:

This produces the result:

I have 14 cents.

Overloading the plus operator (+) is as simple as declaring a function named operator+, giving it two parameters of the type of the operands we want to add, picking an appropriate return type, and then writing the function.

In the case of our Cents object, implementing our operator+() function is very simple. First, the parameter types: in this version of operator+, we are going to add two Cents objects together, so our function will take two objects of type Cents. Second, the return type: our operator+ is going to return a result of type Cents, so that’s our return type.

Finally, implementation: to add two Cents objects together, we really need to add the m_cents member from each Cents object. Because our overloaded operator+() function is a friend of the class, we can access the m_cents member of our parameters directly. Also, because m_cents is an integer, and C++ knows how to add integers together using the built-in version of the plus operator that works with integer operands, we can simply use the + operator to do the adding.

Overloading the subtraction operator (-) is simple as well:

Overloading the multiplication operator (*) and the division operator (/) is as easy as defining functions for operator* and operator/ respectively.

Friend functions can be defined inside the class

Even though friend functions are not members of the class, they can still be defined inside the class if desired:

We generally don’t recommend this, as non-trivial function definitions are better kept in a separate .cpp file, outside of the class definition. However, we will use this pattern in future tutorials to keep the examples concise.

Overloading operators for operands of different types

Often it is the case that you want your overloaded operators to work with operands that are different types. For example, if we have Cents(4), we may want to add the integer 6 to this to produce the result Cents(10).

When C++ evaluates the expression x + y, x becomes the first parameter, and y becomes the second parameter. When x and y have the same type, it does not matter if you add x + y or y + x -- either way, the same version of operator+ gets called. However, when the operands have different types, x + y does not call the same function as y + x.

For example, Cents(4) + 6 would call operator+(Cents, int), and 6 + Cents(4) would call operator+(int, Cents). Consequently, whenever we overload binary operators for operands of different types, we actually need to write two functions -- one for each case. Here is an example of that:

Note that both overloaded functions have the same implementation -- that’s because they do the same thing, they just take their parameters in a different order.

Another example

Let’s take a look at another example:

The MinMax class keeps track of the minimum and maximum values that it has seen so far. We have overloaded the + operator 3 times, so that we can add two MinMax objects together, or add integers to MinMax objects.

This example produces the result:

Result: (3, 16)

which you will note is the minimum and maximum values that we added to mFinal.

Let’s talk a little bit more about how “MinMax mFinal = m1 + m2 + 5 + 8 + m3 + 16” evaluates. Remember that operator+ has higher precedence than operator=, and operator+ evaluates from left to right, so m1 + m2 evaluate first. This becomes a call to operator+(m1, m2), which produces the return value MinMax(8, 15). Then MinMax(8, 15) + 5 evaluates next. This becomes a call to operator+(MinMax(8, 15), 5), which produces return value MinMax(5, 15). Then MinMax(5, 15) + 8 evaluates in the same way to produce MinMax(5, 15). Then MinMax(5, 15) + m3 evaluates to produce MinMax(3, 15). And finally, MinMax(3, 15) + 16 evaluates to MinMax(3, 16). This final result is then assigned to mFinal.

In other words, this expression evaluates as “MinMax mFinal = (((((m1 + m2) + 5) + 8) + m3) + 16)”, with each successive operation returning a MinMax object that becomes the left-hand operand for the following operator.

Implementing operators using other operators

In the above example, note that we defined operator+(int, MinMax) by calling operator+(MinMax, int) (which produces the same result). This allows us to reduce the implementation of operator+(int, MinMax) to a single line, making our code easier to maintain by minimizing redundancy and making the function simpler to understand.

It is often possible to define overloaded operators by calling other overloaded operators. You should do so if and when doing so produces simpler code. In cases where the implementation is trivial (e.g. a single line) it’s often not worth doing this, as the added indirection of an additional function call is more complicated than just implementing the function directly.

Quiz time

1a) Write a class named Fraction that has a integer numerator and denominator member. Write a print() function that prints out the fraction.

The following code should compile:

This should print:

1/4
1/2

Show Solution

1b) Add overloaded multiplication operators to handle multiplication between a Fraction and integer, and between two Fractions. Use the friend function method.

Hint: To multiply two fractions, first multiply the two numerators together, and then multiply the two denominators together. To multiply a fraction and an integer, multiply the numerator of the fraction by the integer and leave the denominator alone.

The following code should compile:

This should print:

2/5
3/8
6/40
4/5
6/8
6/24

Show Solution

1c) Extra credit: the fraction 2/4 is the same as 1/2, but 2/4 is not reduced to the lowest terms. We can reduce any given fraction to lowest terms by finding the greatest common divisor (GCD) between the numerator and denominator, and then dividing both the numerator and denominator by the GCD.

The following is a function to find the GCD:

Add this function to your class, and write a member function named reduce() that reduces your fraction. Make sure all fractions are properly reduced.

The following should compile:

And produce the result:

2/5
3/8
3/20
4/5
3/4
1/4

Show Solution

9.2a -- Overloading operators using normal functions
Index
9.1 -- Introduction to operator overloading

256 comments to 9.2 — Overloading the arithmetic operators using friend functions

  • Zuhail

    Why is this not working if i dont do this :
    friend Fraction operator *(const Fraction &f1,const Fraction &f2);

    And
    then why is compiler saying that :
    invalid initialization of non-const reference of type 'Fraction &' from r-value of type 'Fraction'
    //This works fine if i put const in parameters

    Hey is this the reason for this i just revised the section of anonymous objects
    //However, it is worth noting that anonymous objects are treated as rvalues (not lvalues, which have an address) -- therefore, all rules about passing and returning rvalues apply.
    am i right?

  • i have problem in this code please help for removing errors to many...........

    #include<iostream>
    #include<conio.h>
    using namespace std;
    class complex
    {
        private:
            int img,real;
            public:
                complex()
                {
                    real=img=0;
                }
                void in()
                {
                    cout<<"Enter a number";
                    cin>>real,img;
                }
                void show()
                {
                    cout<<"rael= :"<<real<<endl;
                    cout<<"imagniray is ="<<img<<endl;
                    
                }
                complex operator *(const complex lhs,const double rhs)
                {
                complex t;
                t.real=lhs.real*real;
                t.img=rhs.img*img;
                return t;
                }
                
    };
    int main()
    {
        complex s1,s2,s3;
        s1.in();
        s2.in();
        s1.show();
        s2.show();
        s3=s1*s2;
        cout<<"number after division";
        s3.show();
        getch();
        return 0;
        
    }

  • Anastasia

    I think I've found where the problem was. Somehow a `.h.gch` file (https://stackoverflow.com/questions/1241399/what-is-a-h-gch-file) was created in my Fraction class' directory and it seems it was used (almost) every time I tried to compile with `std=c++17` flag (no idea why). It probably got corrupted and was containing not only outdated(?) information, but some (apparently) nonsensical too (the last time I've encountered this issue the errors it produced took 742 lines!). Deleting `h.gch` fixed it (I hope!).

    I really really apologize for spamming the comment section with this, but it was so confusing to me.
    Just thought I should mention how I fixed it (in case somebody else would have a similar problem).

    • Thanks for posting the solution!
      You should give clang a shot. It uses the same cli as gcc, so it's a drop-in-replacement. clang has fewer bugs and is better maintained in my experience.

      • Anastasia

        I must have compiled my `Fraction.h` accidentally with -std=c++17 flag (maybe other flags too, but since -std=c++17 was the main suspect from the very beginning I was mostly testing only it) and then this precompiled header file was used every time the compiler binary was corresponding with the current compilation (when I tried to compile with that flag(s) again (maybe some other conditions were involved too)). Because I didn't re-compile it every time I changed my actual header file it got corrupted and was outputting nonsense. If this is correct then it's not really gcc's fault (just my unattentiveness and ignorance), but I'll give clang a try if you recommend it (I already installed the latest version available for my distro), thank you :)

  • Anastasia

    I have a problem with my code and I have no idea where it can be :/ My solutuion was perfectly working at first, but then my compiler started complaining that the operator* functions are declared twice (Lines 28, 34, 39 in Fraction.cpp -- error: ambiguating new declaration of ‘Fraction operator*( ... ). I have no clue what got messed up.

    Fraction.h

    Fraction.cpp

    gcd.cpp (I don't like recursion much, so I've written an iterative one)

    main.cpp

    • Can't reproduce. Compiler and settings please.

      main.cpp:21 Don't put multiplication dots on the variable unless it touches the left variable as well. This looks like an indirection.

      • Anastasia

        gcc 9.1.0

        Compiled with flags: "-std=c++17 -Wall -Weffc++ -Wextra -Wsign-conversion -pedantic-errors"

        Everything was working yesterday, can it be a bug or something? Maybe I need a new `Fraction.cpp`?

        > Don't put multiplication dots
        Auto-format. I'll turn this option off, thanks.

        • I tried a single-file version in gcc 9.1 and your multi-file version in gcc 9.2, both compile.
          Does your compiler react to changes in the files? (Purposefully cause an error to see if the compiler catches it to make sure it's compiling the correct files).

          > Auto-format. I'll turn this option off, thanks.
          I guess it's confused by brace initialization. clang-format works fine here.

          • Anastasia

            Creating and compiling a new `Fraction.cpp`(called: `Fraction2.cpp` with all the content of the original one copied into it) didn't help. But I managed to make it compile using g++ without any flags.

            > Does your compiler react to changes in the files?
            Yep, I made line 15 "int Fraction::print() const" and it errored: Fraction.cpp:15:5: error: no declaration matches ‘int Fraction::print() const’

            • Can you try to pinpoint the flag? It must be -std=c++17 or -pedantic-errors as the others are just warnings. Full compilation output would also be appreciated.

              • Anastasia

                The output (sorry, it's huge):

                Fraction.cpp:15:5: error: no declaration matches ‘int Fraction::print() const’
                   15 | int Fraction::print() const
                      |     ^~~~~~~~
                Fraction.h:11:10: note: candidate is: ‘void Fraction::print() const’
                   11 |     void print() const;
                      |          ^~~~~
                Fraction.h:4:7: note: ‘class Fraction’ defined here
                    4 | class Fraction {
                      |       ^~~~~~~~
                Fraction.cpp:28:10: error: ambiguating new declaration of ‘Fraction operator*(const Fraction&, const Fraction&)’
                   28 | Fraction operator*(const Fraction &f1, const Fraction &f2)
                      |          ^~~~~~~~
                Fraction.h:15:22: note: old declaration ‘Fraction& operator*(const Fraction&, const Fraction&)’
                   15 |     friend Fraction operator*(const Fraction &f1, const Fraction &f2);
                      |                      ^~~~~~~~
                Fraction.cpp: In function ‘Fraction operator*(const Fraction&, const Fraction&)’:
                Fraction.cpp:30:17: error: ‘int Fraction::m_numerator’ is private within this context
                   30 |     return { f1.m_numerator * f2.m_numerator,
                      |                 ^~~~~~~~~~~
                Fraction.h:5:9: note: declared private here
                    5 |     int m_numerator;
                      |         ^~~~~~~~~~~
                Fraction.cpp:30:34: error: ‘int Fraction::m_numerator’ is private within this context
                   30 |     return { f1.m_numerator * f2.m_numerator,
                      |                                  ^~~~~~~~~~~
                Fraction.h:5:9: note: declared private here
                    5 |     int m_numerator;
                      |         ^~~~~~~~~~~
                Fraction.cpp:31:24: error: ‘int Fraction::m_denominator’ is private within this context
                   31 |                     f1.m_denominator * f2.m_denominator };
                      |                        ^~~~~~~~~~~~~
                Fraction.h:6:9: note: declared private here
                    6 |     int m_denominator;
                      |         ^~~~~~~~~~~~~
                Fraction.cpp:31:43: error: ‘int Fraction::m_denominator’ is private within this context
                   31 |                     f1.m_denominator * f2.m_denominator };
                      |                                           ^~~~~~~~~~~~~
                Fraction.h:6:9: note: declared private here
                    6 |     int m_denominator;
                      |         ^~~~~~~~~~~~~
                Fraction.cpp: At global scope:
                Fraction.cpp:34:10: error: ambiguating new declaration of ‘Fraction operator*(const Fraction&, int)’
                   34 | Fraction operator*(const Fraction &f, int value)
                      |          ^~~~~~~~
                Fraction.h:16:22: note: old declaration ‘Fraction& operator*(const Fraction&, int)’
                   16 |     friend Fraction operator*(const Fraction &f, int value);
                      |                      ^~~~~~~~
                Fraction.cpp: In function ‘Fraction operator*(const Fraction&, int)’:
                Fraction.cpp:36:16: error: ‘int Fraction::m_numerator’ is private within this context
                   36 |     return { f.m_numerator * value, f.m_denominator };
                      |                ^~~~~~~~~~~
                Fraction.h:5:9: note: declared private here
                    5 |     int m_numerator;
                      |         ^~~~~~~~~~~
                Fraction.cpp:36:39: error: ‘int Fraction::m_denominator’ is private within this context
                   36 |     return { f.m_numerator * value, f.m_denominator };
                      |                                       ^~~~~~~~~~~~~
                Fraction.h:6:9: note: declared private here
                    6 |     int m_denominator;
                      |         ^~~~~~~~~~~~~
                Fraction.cpp: At global scope:
                Fraction.cpp:39:10: error: ambiguating new declaration of ‘Fraction operator*(int, const Fraction&)’
                   39 | Fraction operator*(int value, const Fraction &f)
                      |          ^~~~~~~~
                Fraction.h:17:22: note: old declaration ‘Fraction& operator*(int, const Fraction&)’
                   17 |     friend Fraction operator*(int value, const Fraction &f);

                > Can you try to pinpoint the flag?
                It seems that it's `-std=c++17` that was causing that! Without it all compiles fine! But why??

                edit: it is displaying arguments as named in `Fraction.h` because I initially thought that maybe it's absence of the variables names that was causing that and I've named them in `Fraction.h` as well.

                • Try the same purposeful error game in the header. It looks like you're compiling an old header in which your operators returned a reference.

                  • Anastasia

                    But I have only one header and I'm not returning anything by reference there. I added `-std=c++17` flag back to my bash alias, but I can't reproduce this error anymore... All compiles fine with the flag and without it.

                    To be absolutely sure I caused the same error in `Fraction.h` (made the `print()` return int instead of void):

                    Fraction.cpp:15:6: error: no declaration matches ‘void Fraction::print() const’
                       15 | void Fraction::print() const
                          |      ^~~~~~~~
                    In file included from Fraction.cpp:1:
                    Fraction.h:11:9: note: candidate is: ‘int Fraction::print() const’
                       11 |     int print() const;
                          |         ^~~~~
                    Fraction.h:4:7: note: ‘class Fraction’ defined here
                        4 | class Fraction {
                          |       ^~~~~~~~

                    edit: maybe it was some sort of typo which got saved somewhere in the session? I often test my code directly in vim, I don't know if it makes any difference...

                    edit2: anyway, I'm so happy it's resolved now and I can continue using this code in the next lessons (I wanted to use it for the lesson's 9.3 quiz, that's why I decided to test it again). I'd never figured it out by myself, thank you so much for the help!

                    • Anastasia

                      I did a bit of testing, trying to reproduce the error I've been having and it seems that it's really the fault of `-std=c++17` flag. If I save my `.h` file with the declarations that don't match the definitions in the `.cpp` file I keep getting this error even after I fix the error (in the header) save the file and try to compile again. Opening a new bash session doesn't help as long as I try to compile with `-std=c++17` flag. Compiling with just `g++` is absolutely fine. I'm not sure whether it's an issue with my bash, my alias (I haven't tried to reproduce the error typing all the flags in the command line) or my compiler. Maybe there's some incompatibility with this flag in my version of gcc(9.1)? Do I even need that? I'm just using my old alias for compiling I had since I've been using an older compiler version which didn't have the full c++17 support.

                    • This must be a problem of your environment.
                      The error means that there are 2 or more declarations of a function with the same name and parameters, but different return type. The error message also shows the functions

                      You're trying to compile an old version of the header, which shouldn't even exist. I have no idea why this only occurs with the -std=c++17 flag.

  • Anastasia

    Also, in the quiz 1a and 1b in the snippets provided for checking the code for correctness `main()` lacks the return statement.

  • Anastasia

    The last sencence of the first section (Overloading operators using friend functions):
    "Overloading the multiplication operator (*) and division operator (/) are as easy as defining functions for operator* and operator/."

    Shouldn't it be:
    "Overloading the multiplication operator (*) and division operator (/) IS as easy as defining functions for operator* and operator/."

    • Alex

      I believe "are" is correct here, as is/are is determined by the preceding nouns, of which there are several in this case.

    • Anastasia

      "Overloading the operators are easy" sounds weird to me, since the sentence is supposed to mean (imo) that the overloading is easy, not the operators.

      English is not my first language, so I may very likely be wrong here.

      edit: since I don't trust myself much with that, I checked the sentence here: http://www.grammarcheckforsentence.com
      It indeed shows using "are" in this case as an error. Not meaning to be annoying, just for the precision sake.

      I must say though that the intended meaning stays clear either way, so it's probably not so important.

  • DecSco

    Hey Alex,

    I just came across friend functions, namespaces and Argument-dependent name lookup (https://en.wikipedia.org/wiki/Argument-dependent_name_lookup).
    It strikes me as rather important to know. Maybe you might want to include a note on that?

    • Alex

      Yup. I'll add a subsection about this somewhere -- I'll have to figure out where the most appropriate place is. Maybe in the upcoming rewrite of the lesson on namespaces.

  • mmp52

    Hi there!

    Why do we have to make overloaded operators' input Fractions const?

    • Do you mean the function parameters? That's so you can use the operator with const objects.

  • cdecde57

    Hello! I am having a little trouble in general. I am having a hard time understanding exactly what is going on when you overload them? Correct me if I am wrong but essentially it allows us to add things like 2 class values together? Like adding or whatever with things that normally would not add up?

    Please help me understand what they are used for and how to better understand how they work.

    Thanks!

    • Operators are functions with special syntax, that's all there is to it.

      You can use operators to do whatever you want, it doesn't have to be an arithmetic operation. eg. `std::string` uses `operator+` to concatenate strings.

      • cdecde57

        Thank you! I think I better understand essentially what's going on. I practiced some and I made this program.

        FYI
        cls(); is a function I have in another file that just cout's a ton of \n's to clear the screen.

        I don't know why I need to convert the paths to strings when theoretically they are already strings but
        If you know why that would be great. Are they like already c-style strings?

        Also if you know anywhere I can learn more about fstream and how to make and use buffers that would be great.

        • - Initialize your variables with brace initializers.
          - Limit your lines to 80 characters in length for better readability on small displays.
          - Pass non-fundamental types by const reference.
          - Line 23-27: `int size{ argc };`. You're not using `size` though, so remove it.
          - Don't use `std::exit`. You're in `main`, just return.
          - Line 40-47: The only conditional part is "st", "nd", "rd", "th".
          - Line 40-47: Use `else if`.
          - Use `std::filesystem::remove` and `std::filesystem::delete`, they have C++-style error handling.

          > If you know why that would be great
          You didn't post your full code, there are no prints after your comment. You should be able to

  • Dimbo1911

    Hello guys, how does this code seem? Thank you a lot for your input

    • Hello Dimbo!

      - `print` should be `const`.
      - `(a > 0 ? a : -a )` can be replaced by `std::abs(a)`.
      - Use single quotation marks for characters.
      - Nothing is preventing the used of `Fraction` from setting `a` to `0`.
      - You're using the same name style for variables and functions. This can lead to confusion.

  • lucieon

    So here's my code:

    If there are any improvements, please let me know.
    Also One thing I noticed is in the function definition of reduce(), both these codes were working:
    (i)

    (ii)

    My question is how does the compiler resolve this?
    (I am using Visual Studio 2017 Community Edition)

    • Snippet 1
      * Line 6, 7, 33, 34, 40, 41, 52, 63, 65, 67, 69: Initialize your variables with uniform initialization. You used copy initialization.
      * Line 8: Initialize your variables with uniform initialization. You used direct initialization.
      * Line 63: Initialize your variables with uniform initialization.
      * Line 10: Limit your lines to 80 characters in length for better readability on small displays.
      * Line 42: Doesn't need "Fraction"

      > how does the compiler resolve this?
      @reduce and @gcd both are members of @Fraction. @reduce has access to all members of @Fraction without using "Fraction::" or "this->".

      • lucieon

        Thank you for replying.
        Couple more questions;
        You said use uniform initialisation on line 8 but how do I do that in the function declaration?
        And in line 42 do you mean just write

        If this works then can you pls elaborate thanks again!

        • Sorry about line 8, my replies about uniform initialization are automated and there seems to be a bug.
          Line 42, yes. When you're returning from a function whose return type is not auto, it's already clear which type will be returned. You don't need to state the type again when using uniform initialization.

  • Codename 47

    Hi Alex!

    I think, the line -- "This allows us to reduce the implementation of operator+(MinMax, int) to a single line", should be -- "operator+(int, MinMax)" instead.

    • Alex

      Agreed and updated. Thanks!

      • how is it possible to concatenate the operators like in next line. when you are returning objects by value and not by reference?
            MinMax mFinal = m1 + m2 + 5 + 8 + m3 + 16;

        • Alex

          Consider 1 + 2 + 4 -- this would evaluate as (1 + 2) + 4, which would evaluate as 3 + 4. That 3 is a temporary value that is kept in memory only during the expression and then discarded.

          Similarly, in this case, each operation returns a temporary object that is consumed during the next operation. So (m1 + m2) returns a temporary object that becomes the left operand for that + 5, and so on.

          All the temporary objects are discarded at the end of the expression.

  • magaji::hussaini

    Hi colleagues.
    I have modified Point3d and Vector3d from chapter 8. Here is Point3d.h any suggestion/correction/optimisation will help.

  • magaji::hussaini

    My soln...

    • * Line 11: Initialize your variables with uniform initialization. You used copy initialization.
      * Line 17, 31, 36: Initialize your variables with uniform initialization. You used direct initialization.
      * Line 4: Initialize your variables.
      * Line 4, 12, 17: Don't try to save on lines, you're making your code hard to read.

      • magaji::hussaini

        Thanks.
        By the way I have a question about uniform initialisation.
        What if I REALLY know what I am doing?
        May be I explicitly want narrow conversion or I know that I avoided it at all?
        Because (I think may be it will increase compilation time) bcos of the type checking.
        Thnks by the way.

        • > May be I explicitly want narrow conversion
          Then do it explicitly. Use casts.

          > I know that I avoided it at all
          You can omit initializations, but doing so can lead to hard-to-debug bugs.

          > increase compilation time
          I doubt initializations can make a noticeable difference in compilation time.

  • Abdelrahman

    Hi Alex,

    I've a question regarding the first example line 28 [/Cents centsSum = cents1 + cents2;]
    How centSum object is generated without passing an argument to Cents constructor ?

    Thank you in advance

    • calls @operator+, which constructs a new @Cents object in line 22 and returns it. This is then used to initialize @centsSum. This happens either through @Cent's automatically generate copy constructor or return value optimization (ie. @operator+ initializes @centsSum directly).

  • Faruk

    Hi i have made the Fraction class project from the example i took me about 1 day i also implemented more math functions and operators(+,-,/,*) and also i would really appreciate if someone could give me some tips or code improvements.

    Fraction.hpp

    Fraction.cpp

    Main.cpp

    • Hi Faruk!

      General
      * You're using the same name style for functions and types. This can lead to confusion.

      Fraction.hpp
      * Line 8: Initialize your variables with uniform initialization.
      * Line 8: Declare 1 variable per line.
      * Line 22-29: Those parameters should all be const.

      Fraction.cpp
      * Line 6, 31, 48, 70: Initialize your variables with uniform initialization. You used copy initialization.
      * Line 43, 67, 71, 85, 97: Initialize your variables with uniform initialization. You used direct initialization.
      * Unused destructor
      * Line 23: The inner conditional statement doesn't do anything.

      Main.cpp
      * Line 8, 9, 10, 11: Initialize your variables with uniform initialization. You used copy initialization.
      * Line 6, 7: Initialize your variables with uniform initialization. You used direct initialization.
      * Inconsistent use of @std::endl and '\n' with no apparent reason.

  • Jason guo

    My own function for simplifying fractions

  • david

    Hi Alex, you wrote:
    "For example, Cents(4) + 6 would call operator+(Cents, int), and 6 + Cents(4) would call operator+(int, Cents). Consequently, whenever we overload binary operators for operands of different types, we actually need to write two functions"

    why does the following could works without adding :

    but doesn't work when

    is removed?

    • Hi David!

      Line 33 uses @operator+(const Cents&, const Cents&) by calling @Cents::Cents(int) with 10 as an argument first.
      If @operator+(const Cents&, int) is removed, the same can be done in line 32.

      • david

        Hi nascardriver!
        according to the lesson, we need to make two functions one to handle Cents + int,
        and another one to handle int + Cents.
        I don't get how operator+(Cents,Cents) can handle (int, Cents) while operator+(Cents, int) can't do that.

        • operator+(Cents, int) required the second element to be an in
          When you write int + Cents, there is no way to convert the second element to an int.
          You can use int + Cents with operator+(Cents,Cents), because int can be converted to Cents

  • I did 1(c) for the extra kudos:

  • Here's my solution to 1(b):

    As both the 2nd and 3rd overload functions have the same return type would it possible to reference one to the other to save code?

  • Synex

    I assumed that for the 1c) extra credit part it is forbidden to change main() and include an extra function call.

    //this part is not very important
    First off, that task made me understand why putting a function in the constructor that calls the same constructor is a bad idea, hello infinite loop.

    Now in my first try I implemented reduce() in a way that it takes a Fraction Object AND returns one too, however, in the comments I read that doing so is inefficent because it creates a new Object just to pass the reduced fraction.
    This is where I got the loop in the constructor too, so I put it into print(), which worked but it was kind of an awkward place to put it.
    Also, when I tried to put it into the constructor or print() function I did not know how to access the object that was passed in main() because it was not passed as an argument within the bracktes, instead the function was used on the Object if I understand correctly (Class::object.Class::method).
    //this part is not very important

    In the end I rewrote the reduce() function in a way that it just modifies the passed in Object (if I understand my own code correctly, that is) which I pass with (*this).

    The code works, but if anyone has any tips on how I could implement this in a different/better way, I'm open to all suggestions.

    • Hi Synex!

      > I rewrote the reduce() function in a way that it just modifies the passed in Object
      No it doesn't. It modifies @this, @a is unused. When you're accessing member variables without specifying an object you're accessing the local variables.

      * @Fraction::print should be const
      * @getGcd doesn't need to be a non-static member function
      * Initialize your variables with uniform initialization to prevent accidental implicit casts
      * Include <cassert> instead of "assert.h". <*.h> are mostly C headers, <c*> are the C++ versions.

      Rest looks fine

      • Synex

        Hey man,

        I fixed those mistakes now. Thanks for helping me out!

        One thing I am not entirely sure about is turning getGcd() into a static member function.
        I re-read the 8.12 Chapter on static member functions and it seems the advantage here is that the function only gets instantiated once and is then used by all Objects of Fractions(f1,f2,f3...) without going out of scope and being destroyed inbetween?

        • * Line 14: You got it the wrong way around

          @reduce can not be const, because it modifies members. (Lesson 8.10)

          > the function only gets instantiated once and is then used by all Objects of Fractions
          That's the case for all functions. static functions can be used without an object

          Since @Fraction::gcd doesn't need a @Fraction object it can be declared static. This doesn't change anything, but classes should only declare functions as members if the functions need an object. You might as well move @gcd outside of @Fraction and into a "Math" class or namespace.

          > without [...] being destroyed
          Functions are never destroyed. They live as long as your program does.

          * Line 34, 42, 47, 52, 58, 61, 64, 67, 70, 73: Uniform initialization

          • Synex

            Ah, so "const void fnc()" makes "void" constant, which is nonsense and does nothing in this case.
            Explains why it still compiled and worked after I mistakenly changed it to "const void reduce()", reduce() was still allowed to change stuff afterall. ^^

            Regarding the static functions, I guess it will come to me more naturally once I use more classes (put into their respective files) and reuse a lot of math heavy "helper functions".

            I totally did not see the wrong initialization at lines 34,42,47,52, I'm not used to returning stuff that I initialize in the same line. Previously we would always initialize some variables and then return a "sum" variable for example.

            Thank you very much for your help!

            • Line 73 is still using copy initialization

              When calling a constructor while returning the to-be-constructed object, you don't need to explicitly name the constructor, as it can be deduced from the return type.

              Same for the other functions.

              • Synex

                > Line 73 is still using copy initialization

                Oha, got it!

                > you don't need to explicitly name the constructor, as it can be deduced from the return type.

                Fixed the others, makes sense, I think this was also mentioned in a previous chapter, gotta go a little slower I guess.

                You clarified some important things for me, gonna try hard and get used to using uniform initialization whenever I can.

  • gad

    Hi Alex,,
    I apologize if I am asking a question that has probably been answered before, but I am struggling to understand why you use & before object in many positions as you mentioned before in the quiz part

    • Hi Gad!

      He's passing the arguments by reference. Give lessons 6.11, 6.11a, 7.3 and 7.4a another read.

      • gad

        thanks nascardriver for illustration
        but in the same point he used const keyword i know that const objects cant access non const functions, he didnt use any function here so what is his purpose here .

        • Whenever you write a function that doesn't modify a reference parameter, you should mark the parameter const. That way the function works with both, const, and non-const arguments. If you don't mark the parameter const, you can only pass non-const parameters.
          Alex might not have used const object in this case, but you should always think ahead to increase forward compatibility. Otherwise you're going to spent a lot of boring time updating old code once you get there.

  • Nicholas

    I apologize if I am asking a question that has probably been answered before, but I am struggling to understand why

    Fraction operator*(const Fraction &f1, const Fraction &f2)

    uses a pointer.

  • Winston Lai

    Hi, based on the code below, what is the purpose of returning Cents(c1.m_cents + c2.m_cents)? Why do we need to cast cents in front of it? Is it because that c1.m_cents+c2.m_cents are integer values so we need to cast them back to Cents type? Also, in main(), how did the "centsum =" update it's own member m_cents by the return value of "Cents(c1.m_cents + c2.m_cents)" when the function operator+ returns it? Thanks so much for your help!

  • Louis Cloete

    Regarding Q1a:

    Shouldn't Fraction::print() be const?

  • Jack

    In the MinMax example, you say:

    'When C++ evaluates the expression x + y, x becomes the first parameter, and y becomes the second parameter. When x and y have the same type, it does not matter if you add x + y or y + x -- either way, the same version of operator+ gets called. However, when the operands have different types, x + y is not the same as y + x.'

    and:

    'One other interesting thing to note is that we defined operator+(int, MinMax) by calling operator+(MinMax, int). This may be slightly less efficient than implementing it directly (due to the extra function call, assuming your compiler can’t optimize the function call away), but keeps our code shorter and easier to maintain because it reduces duplicate code. It is often possible to define overloaded operators by calling other overloaded operators -- when possible, do so!'

    From the first statement, we know that:

    To overload the '*' operator to allow for multiplying a fraction by a fraction, and a fraction by a value, we need 3 different functions:

    Now, from the second statement, we get told to overload the operators by calling other overloaded operators when possible. Therefore isn't:

    the correct way to solve the problem? In the solution, the entire last function is rewritten?

    • Alex

      For ease of discussion:
      A = Fraction operator*(const Fraction &f1, int value)
      B = Fraction operator*(int value, const Fraction &f1)

      If the implementation of A is complex, then it definitely makes sense to implement B using A. However, if A is trivial, then it can be simpler to just implement B directly (as it should also be trivial -- otherwise there's an extra indirection being made). I'll make this caveat clearer in the lesson text.

  • Donlod

    Quick question:
    1. Why doesnt this work?

    Either i have to add this->gcd(...) or i have to make the variable a different name.

    2. What is in this case best practice with the gcd function regarding static/non-static and private/public?

    • nascardriver

      Hi Donlod!

      1. That's the result of a poor naming convention. "gcd" occurs twice but naming different entities. Your compiler can't differentiate between the two.

      2. Ideally @gcd shouldn't be a part of @Fraction but rather a part of a more general mathematical class or namespace. If it's in @Fraction, I'd go for private static.

  • radu f

    Regarding the extra credit problem, shouldn't be gcd(int, int) and reduce() be private members of the class Fraction? I think, normally, the user don't need access to them in these circumstances, right?
    Well, I manage to solve the quizz, but in a not so efficient manner:
    - it didn't cross my mind to put reduce() in the constructor, I used instead the following:

    paired with:

    Not sure how it looks concerning the memory usage [the anonymous object returned by reduce() looks like an unwelcome guest to a party to me now ...].

    Fraction.cpp:

    Fraction.h

    • nascardriver

      Hi radu!

      > shouldn't be gcd(int, int) and reduce() be private members of the class Fraction?
      If you don't intend them being used outside the class you should declare them private (or protected).

      > Not sure how it looks concerning the memory usage
      Bad. You're creating two @Fraction objects although you only need one in the end.
      This problem can be solved by having @reduce modify the object it's called on rather than creating a new @Fraction and returning itself by reference or pointer.

Leave a Comment

Put all code inside code tags: [code]your code here[/code]