5

If I want to overload operator +, which prototype is correct?

  1. D operator+(const D& lhs, const D& rhs);
    then declare it as a friend function of D.

  2. D operator+(const D& s);
    Then declare it as a member function of D.

Joachim Sauer
309k59 gold badges567 silver badges624 bronze badges
asked Aug 10, 2011 at 15:49
11
  • possible duplicate of Operator overloading Commented Aug 10, 2011 at 15:52
  • 1
    Both a valid but I guess you mean which is best practice? I would say probably declaring as a member since you're not opening up the innards of class D through a friend declaration. Commented Aug 10, 2011 at 15:52
  • Both will work fine. You should have a look at the Operator Overloading FAQ here Commented Aug 10, 2011 at 15:52
  • 1
    @Kerrek: I'm not sure if people should be doing that any more as it can interfere with move semantics. Commented Aug 10, 2011 at 16:01
  • 4
    @Dan: member operators are not usually the best idea. You should prefer free standing functions (they are symmetric with respect to the types of the arguments). The free function is part of the interface and as such it should not be too problematic to declare it as friend, but then again, that can also be avoided easily. Implement operator+= as a member function, and operator+ in terms of the previous one: D operator+( D const & lhs, D const & rhs ) { D copy(lhs); return copy+=rhs; } Commented Aug 10, 2011 at 16:41

10 Answers 10

3

The first is correct, the second is plain wrong. You can improve the second by writing this

D operator+(const D& s) const;

but it's still wrong. The reason is that the compiler will apply different rules to the left and right hand sides of your + operator in the second version. For instance given this code

class C
{
};
class D
{
public:
 D(const C&);
};
C c;
D d;
d = d + c; // legal with both versions
d = c + d; // not legal with the second version

The difference is because the compiler will create a temporary D object from a C object for a method or function argument but it won't do it to make a method call on the temporary object.

In short the first version treats the left hand side and right hand side equally and so agrees better with the coders expectations.

Cheers and hth. - Alf
146k15 gold badges218 silver badges342 bronze badges
answered Aug 10, 2011 at 16:00
Sign up to request clarification or add additional context in comments.

3 Comments

+1 for example of derived class problem. I'm deleting my own answer.
sorry for edit adding inheritance. i rolled it back. i need more coffee!
The second (with the const) is only "wrong" if there are implicit conversions to your class. Arguably, it is the presence of implicit conversions which is wrong, not the second form:-). (But a lot depends on the actual types involved.)
2

The second one should be

D operator+(const D& s) const;

Then either is good.

As to the first needing to be a friend: only if it really needs access to anything private. Normally you can implement it in terms of the public interface, commonly with the corresponding operator+=:

D operator+(D lhs, const D& rhs) { //copy left-hand side
 return lhs += rhs; //add the right-hand side and return by value
}
answered Aug 10, 2011 at 15:54

Comments

2

I would advice you to follow a third path: Implement operator+= as a member function, and then implement operator+ in terms of the previous like:

D operator+=( D lhs, D const & rhs ) {
 lhs += rhs;
 return lhs;
}

The advantage of the third way is that with basically the same code you provide both + and +=, and you get to implement operator+ as a free function which is an advantage from the point of view of symmetry, if your class has implicit conversions, it will allow d + t and t + d for any object d of type D and any other object t of type implicitly convertible to D. The member function version will only apply conversions to the right hand side, which means that d + t will be allowed, but not t + d.

[self publicity warning] You can read a longer explanation on this particular issue here

answered Aug 10, 2011 at 16:44

Comments

1

Go with the first one. However, if it needs to access private members,only then make it friend, otherwise make it non-friend function.

answered Aug 10, 2011 at 15:53

Comments

1

I think both are correct. But one thing you missed (that may or may not apply), is that if the left side value can be something other than D (say an integer or something) then option 1 works for that e.g.

D operator+(int lhs, const D& rhs);

Then you can do something like:

D d1;
D d2 = 5 + d1;
answered Aug 10, 2011 at 15:54

1 Comment

The member function needs to be const.
0

You only need to declare a function as a friend of a class if you plan to access private variables of the class through this function. In the first prototype you do not need to declare the function as a friend since you are passing in all the values you plan to use. In the second prototype you can declare the function a friend since you will need to access one more variable, but it is better practice and easier to just declare the function as a member.

answered Aug 10, 2011 at 15:54

Comments

0

Both methods are almost correct. It's just two ways of doing almost the same. But when you need to apply the binary operator to other types other than D (for example int+D) you need to use the second one.

The option 1 does not even have to be a friend if it does not need access to the private members.

The option 2 have to be fixed a little. You are missing D::.

D D::operator+(const D& s) const {
}
answered Aug 10, 2011 at 15:52

6 Comments

When it lies outside the class it does.
And option 1 has the ability to promote either lhs or rhs arguments if an appropriate constructor exists. Option 2 cannot do that.
@Nobody: Not if it uses only public members and functions.
@Zan Lynx: I don't think thats the case here but you are right.
-1 For an in-class declaration no D:: is needed. Also, the signature is wrong (for most practical designs). I.e., it's not the case as you state that "both methods are correct".
|
0

The principle of least surprise says that your overload should behave more or less like the built-in operators. The usual solution here is not to implement operator+ at all, but to implement:

D& operator+=( D const& rhs );

as a member, and then derive from something like:

template<typename T>
class ArithmeticOperators
{
 friend T operator+( T const& lhs, T const& rhs )
 {
 T result( lhs );
 result += rhs;
 return result;
 }
 // Same thing for all of the other binary operators...
};

This way, you don't have to rewrite the same thing every time you define a class which overloads the arithmetic operators, and you're guaranteed that the semantics of + and += correspond.

(The friend in the above is simply to allow you to put the function, along with its implementation, in the class itself, where ADL will find it.)

answered Aug 10, 2011 at 17:18

Comments

0

Both are correct (after fixing the const correctness problem others pointed out), but I recommend the member operator. It can be overridden with polymorphic behavior if virtual, have better cohesion with the class and as a result it is easier to maintain and to document. Try to avoid friend functions if you can.

As for the "derived = base + derived" case, I recommend not mixing value semantics operators and polymorphism, it can have unforeseen consequences due to various implicit conversion sequences and object slicing. That example might be equivalent to derived = Derived(base) + derived, but it can be derived = Derived(base + Base(derived)) as well if base has operator +.

Use only explicit conversion and casting, and you will not encounter any mysterious strange behavior. And think twice before you implement operators for a polymorphic class.

answered Aug 10, 2011 at 18:04

Comments

0

The first one has a different behavior if D has implicit constructors.

Consider

struct D
{
 D(int);
};
D operator+(const D&, const D&);

Then you can do 1 + d and d + 1, which you cannot do with the member operator+ (the lhs must be a D, and no conversion shall take place).

answered Aug 10, 2011 at 15:55

1 Comment

That might not be a desirable behavior in all cases. Optimization opportunities (for example a Complex class could provide faster methods for real parameters) or customizable behavior can come into play. It might seem good for the default case, however it might hide if the custom behavior isn't implemented properly.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.