Summary:
Try a+(b+c)
and watch your member version fail.
C++ has named values (aka lvalues, named because they are values that make sense on the LEFT hand side of an =
assignment) and temporaries (aka rvalues, which go on the RIGHT hand side of an =
assignment). (this paragraph contains simplifications).
lvalues will bind to non-const references, while rvalues will only bind to const references. So Int&
won't bind to a temporary, while Int const&
or const Int&
(same thing) will.
The error you are getting is that a+b+c
is parsed as (a+b)+c
by the order of operations in C++. And the result of a+b
is a temporary (an rvalue). So it won't bind to Int&
.
A method like Int operator+(Int& rhs)
takes *this
as the left-hand argument to +
, and rhs
is the right-hand argument. So (a+b)+c
, a+b
is *this
and c
is rhs
. Member operator+
works on temporaries, so all is good.
A friend like Int operator+(Int&, Int const&)
-- you'll note the Int&
is on the left. The left in (a+b)+c
is a temporary when you add +c. So it fails, because a=b
won't bind to Int&
Finally, there are quirks in how members bind to temporaries. Because members where added to C++ 20+ years before rvalue/lvalue references where, they'll bind to temporaries far more liberally than you might expect (by default).
In more detail:
Your operator+
member takes a non-const reference on the right hand side. Your friend takes a non-const reference on the left hand side.
a+b+c
in C++ is parsed as (a+b)+c
. In every case, the right hand side of a +
has a named value -- an lvalue -- and named mutable values can bind to non-const references.
However, the +c
has a temporary on the left hand side -- an rvalue -- and temporaries cannot bind to non-const lvalue references.
The final quirk has to do with the fact that a member like this:
Int Int::operator + (Int & v)
{
return Int(a + v.a);
}
can be called on an rvalue (a temporary), even though it is non-const. l/r value qualification of objects was added after initial member syntax was.
There are a bunch of ways you are permitted to declare "what kind of object, const or non const, temporary or not, will this method work on". Their signatures look like:
Int operator + (Int const& v);
Int operator + (Int const& v) const;
Int operator + (Int const& v) &;
Int operator + (Int const& v) const&;
Int operator + (Int const& v) &&;
Int operator + (Int const& v) const&&;
if you don't include a trailing &
or a &&
, your methods bind to both rvalues (temporaries) and lvalues (named values with identity). This is because l/r value references where added 20+ years after methods where added to C++, so the default method declaration works with both (for backward compatibility reasons).
friend Int operator + (Int &, const Int &);
corresponds to
Int operator + (const Int &) &;
which, as a method, will fail with basically the same error message as the friend does.
You can also induce the
friend Int operator + (Int &, Int const&);
method to work by typing:
a+(b+c)
as now the left-hand side of +c
is a temporary, and refuses to bind to Int&
.
This will also make the
Int operator+(Int&)
version fail, as now the right hand side of a+
is (b+c)
, which is a temporary, and won't bind to Int&
.
General Advice:
When working on operators for a class, do this.
Int operator+=(Int const& rhs)&;
this is the increment by operator. It should be a member, and it should have the &
qualification at the end -- incrementing a temporary makes no sense.
Then write:
friend Int operator+(Int lhs, Int const& rhs) {
lhs += rhs;
return lhs;
}
here we use the increment operator to implement +
. If our class has efficient move constructors (as it should), this will also be very close to optimal.
The left hand side argument we take by value. We then increment it. Then we return it (and this implicitly moves).
When you type:
a+b+c
this becomes
(a+b)+c
we copy a
and get:
copy of a += b
we then move the copy of a into the result. This is then elided (zero-cost moved) into the left hand argument of +c
.
We then increment that, and move it to the result.
So, eliminating moves (which should be near zero cost even on most complex objects), we get:
Int temporary = a;
temporary += b;
temporary += c;
return temporary;
as the result of a+b+c
.
This pattern works with simple things like fake integers, more complex things like strings, and a myriad of other situations. And it gives you +=
and +
for about the same amount of coding work as typically writing +
does.