2

Hi I have been trying to analyze the following code that uses operation overloading.

#include <iostream>
using namespace std;

#define DBG(str) cout << str << endl

class Integer {
    int n;
public:
    Integer(int _n) : n(_n) { DBG("A"); };
    Integer(const Integer& i) : n(i.n) { DBG("B"); };

    Integer& operator=(const Integer& i) { DBG("C"); n = i.n; return *this; };
    Integer& operator+=(const Integer& i) { DBG("D"); n += i.n; return *this; };

    friend Integer operator+(const Integer& a, const Integer& b);
    friend Integer operator*(const Integer& a, const Integer& b);
    friend ostream& operator<<(ostream& os, const Integer& i);

};

Integer operator+(const Integer& a, const Integer& b) {
    DBG("E"); return Integer(a.n + b.n);
}
Integer operator*(const Integer& a, const Integer& b) {
    DBG("F"); return Integer(a.n * b.n);
}
ostream& operator<<(ostream& os, const Integer& i) {
    DBG("G"); os << i.n; return os;
}

int main() {
    Integer n1(1), n2(2);
    Integer n = 5 + n1 + 2 * n2;

    cout << n << endl;
}

and the result comes out to be...

A // constructor called when n1 is created
A // constructor called when n2 is created
A // when is this called?
A // when is this called?
F // called when 2 * n2 is operated
A // called when Integer(a.n * b.n) is created in the multiplication function
E // called when 5 + n1 is operated
A // called when Integer(a.n + b.n) is created in the addition function
E // called when (5 + n1) + (2 * n2) is operated
A // called when Integer is created in the addition function
G // called when n is printed using cout
5 // value of n

Now what I'm having trouble with the most is the third and fourth print of A. In the sentence Integer n = 5 + n1 + 2 * n2; the object n is being created and the right value is assigned to n, so a copy constructor should be called? What I think should happen is the constructor should be called for making a temporary object of (5 + n1 + 2 * n2) and then by copying it to n, the copy constructor should be called. What am I understanding wrong?

Could you please explain what is happening? Thank you in advance.

  • 2
    Make your constructor print out the value of `n` to help trace what's happening. – Shawn Nov 18 '19 at 16:53
  • 2
    Can you place a break-point on DBG("A") and check the call-stack? – NTDLS Nov 18 '19 at 16:55
  • 3
    `Integer n = 5 + n1 + 2 * n2;` The `5` is converted to an `Integer` object, which prints out `A`. Same goes for the `2` – ChrisMM Nov 18 '19 at 16:55
  • 4
    You should step through the code in a debugger to see what's going on. That said, my guess is that the third and fourth "A" markers come from converting 5 and 2 to `Integer`. You're right about the copy constructor that creates `n`. – Pete Becker Nov 18 '19 at 16:55
  • Questions like this may also depend on the version of C++ that you are using, and whether you are compiling in debug vs release (i.e. guaranteed copy elision). – Mark Ingram Nov 18 '19 at 16:55
  • 3
    You could try making your `Integer(int _n)` constructor `explicit` and see where the compiler points at errors. – Fred Larson Nov 18 '19 at 16:56
  • 1
    try declaring Integer constructor as `explicit` ; you will understand the reason :) – Shrikant Nov 18 '19 at 16:56
  • *What I think should happen...* -- If what you state will give you the correct result, guess what? You're right. The compiler is also right. That's the issue -- a compiler can call the copy constructor in any way to construct the object. Of course, the compiler writers minimize the calls as best as possible (and in some cases, must be copy elision). – PaulMcKenzie Nov 18 '19 at 16:57
  • Note that there are better ways to create a macro to trace the function calls. See https://stackoverflow.com/questions/4384765/whats-the-difference-between-pretty-function-function-func With GCC, you can have `#define DBG() std::cout << __PRETTY_FUNCTION__ << '\n';` – Bktero Nov 18 '19 at 17:03
  • Thank you. All of these suggestions helped a lot. –  Nov 19 '19 at 08:51

3 Answers3

5

Problem is in this line:

Integer n = 5 + n1 + 2 * n2;

There is no operator+ for int and Integer, but compiler silently did a implicit conversion from int to Integer, since by default all single argument constructors can be used as conversion method. Here is your live code (improved a bit).

This is one of dangerous features of C++ so there is explicit keyword to be used before constructor. If you add it:

explicit Integer(const Integer& i) : n(i.n) { DBG("B"); };

Compiler will report an error since implicit conversion now can't be performed and there is no operator+(int, const Integer&) and operator*(int, const Integer&).

Marek R
  • 32,568
  • 6
  • 55
  • 140
  • Thank you for a clear answer. So if int is implicitly converted to Integer, what does i.n really mean? Can 5 have member variables? –  Nov 19 '19 at 08:52
  • 1
    Defining constructor which can be called with single argument by default compiler can use as conversion method. In such situation compiler creates temporary unnamed object, which will be auto destroyed after line was executed. – Marek R Nov 19 '19 at 10:05
  • There is tool to decompose implicit code: https://cppinsights.io/s/c11fe56f – Marek R Feb 24 '21 at 20:31
2

Your operator+ and operator* functions receive an const Integer& as parameter.

When this line is calculated (5 + n1 + 2 * n2) 5 and 2 will be auto-converted to Integer.

If you want them to not be converted, you should consider creating operators for int as parameter.

EDIT: You can also use explicit constructor.

For example: explicit Integer(int _n){...}

//thanks for the comment @formerlyknownas_463035818

Roy Avidan
  • 769
  • 8
  • 25
0

Friend function is used in operator overloading only when the first parameter of the operator function is not an object of the target class for which the operator function is defined. In your case, Integer:

Integer operator+(const Integer& a, const Integer& b) {
    DBG("E"); return Integer(a.n + b.n);
}
Integer operator*(const Integer& a, const Integer& b) {
    DBG("F"); return Integer(a.n * b.n);
}

, the first parameter is indeed an object of type Integer which is your target class. What you probably want is to be able to use your Integer object as operands along with primitive integer types. Change your friend function signatures to:

Integer operator+(const int& a, const Integer& b) {
    DBG("E"); return Integer(a + b.n);
}
Integer operator*(const int& a, const Integer& b) {
    DBG("F"); return Integer(a * b.n);
}

Note that a is used instead of a.n because when an operator function is defined as friend, both operands are passed as parameters.(unlike non-friend operator functions where the first operand is a calling object). So the a contains a value of type int and not a value of type Integer.

The anonymous calls to the constructor containing DBG("A") is a result of the implicit conversions happening when 5 and 2 are being converted from int to Integer.

This is unreliable although your code may work. Hope this helps.

Amal K
  • 4,359
  • 2
  • 22
  • 44