We can very loosely separate operators into three groups.

Operators like the comparison operators, logical operators etc. that do not modify their operands and return a type that is unrelated to the type of the operands.

Operators that modify their lefthand operand (by convention) These include the assignment operators (including the compound ones)

Operators that conceptually return a value of the same type as the righthand operand. The traditional arithmetic operators are the prime examples of these.
For now I want to focus on ideas for implementing that last group.
The traditional, and I believe poor, design is to provide these operators as friends of the relevant class. There are times when the friendship is required for implementing some operators efficiently. The major indicator of these is that the left and right operand will be of different userdefined types. For example multiplying a matrix by a vector (in mathematical terms). However there is usually a better solution.
First note that arithmetic operators would only normally be applied to value types (ones that support public copying and so can be passed and returned by value). This is important because it means we can expect an accessible copy constructor.
Conventional Solution
Let me restrict myself to addition (+). Almost everything that I write will be equally applicable to other arithmetic operations. Note that a type can support multiple overloads of addition (i.e. add together two instances of the same type, add an instance of one type to an instance of another type).
The conventional solution is to provide an inclass operator+= and then use that to provide an out of class (i.e. namespace scope) operator+. For example:
class Rational { // lots of interface detail public: Rational & operator+=(Rational & const rhs); }; inline Rational operator+ (Rational const & lhs, Rational const & rhs) { return Rational(lhs) += rhs; }
This form (which may be new to you) creates an anonymous Rational as a copy of the lhs and then modifies it by adding the rhs. The resulting value is nominally returned by copying to the return value. However, compilers with a respectable level of optimisation will elide that copy and use the location of the return value for the anonymous temporary.
Alternative Solutions
Some programmers find the idea of creating overloads for the compound assignment operators distasteful and so are tempted to resort to using friend declarations to get around the problem. There is no reason why they should do this.
First there is the simple solution of providing an in class 'add' function. Our code now looks like this:
class Rational { // lots of interface detail public: Rational add(Rational & const rhs) const; }; inline Rational operator+ (Rational const & lhs, Rational const & rhs) { return lhs.add(rhs); }
The next issue that gets raised is that technically this results in two calls of the copy constructor, one to return from the add function by value, and the second to return that value from the operator+ wrapper. It would be a pretty poor compiler that did this (so I guess there are still a few poor compilers around). However, if you are really unhappy then notice that what an addition operation does is to create a new value from two existing ones. Does that suggest anything? What about using a constructor?
So here is a third design.
Operator Constructors
class Rational { // lots of interface detail public: Rational (Rational & const lhs, Rational const rhs); }; inline Rational operator+ (Rational const & lhs, Rational const & rhs) { return Rational(lhs, rhs); }
Conceptually this is probably the cleanest solution but it has a drawback. How am I going to provide constructor operators for the other arithmetic operations? It doesn't take long to realise that we need another parameter whose type will determine which operator is being supported. If we want a static (compile time) decision this third parameter must be of a different type for each of the operators. (You can avoid this by using an enum and having an internal switch statement, but that is clumsy and probably makes it harder for the compiler to optimise your code).
I want to be able to provide a set of constructors such as:
Rational(lhs, rhs, Add); Rational(lhs, rhs, Subtract); Rational(lhs, rhs, Multiply); Rational(lhs, rhs, Divide);
With Add, Subtract, Multiply and Divide being distinct types.
Fine, but where should these extra UDT go? Once I decide to use this mechanism to provide arithmetic operators I might as well generalise. So rather than place the definitions inside the Rational class, or inside the enclosing namespace I think it makes sense to provide:
namespace Operators { struct Add {}; struct Subtract{}; struct Multiply{}; struct Divide{}; // and any other operators that you think // you want to provide via constructors. }
(Note that C++ does not support a semicolon after the closing brace of a namespace. I hope this was just a mistake and not a deliberate decision of the Standards Committees. If it was the latter I would dearly like to hear the rational supporting the decision)
Interestingly, this is yet another example of empty, functionless classes being useful.
With the above in place we can now write:
Using namespace Operators;
as a using directive inside the namespace in which we define our class Rational. I think that is a perfectly sensible way to use a using directive. Now we can provide our overloaded arithmetic operators with:
inline Rational operator+ (Rational const & lhs, Rational const & rhs) { return Rational(lhs, rhs, Add()); }
[and similarly for the other operators]
A good compiler will generate no code for that anonymous instance of an empty class, just use its type to discriminate between the different operators.
Once you get the hang of this idea you might be tempted to write the following template function:
template<typename T> inline T operator+ (T const & lhs, T const & rhs) { return T(lhs, rhs, Add()); }
Resist the temptation. The main reason that you do not use a member function for operator+ is because you want to allow symmetric conversions to type T. Providing a template is the best way of torpedoing this intent that I know. There are many opportunities for using templates but this is not one of them.
Disambiguating Declarations
You do not have to program very long in C++ before you come across the problem exemplified by the following code:
int main() { Mytype mt1(3); Mytype mt2(); // other code };
The first declaration declares an instance of Mytype that is constructed with a constructor that takes a single parameter that can accept an argument of type int . The naïve programmer thinks the second declaration should create an instance by using the default constructor for Mytype . However there is an alternative way of viewing that line. It is also a prototype for a function mt2 that returns a Mytype by value and has no parameters.
As all experienced programmers know, this is resolved in favour of its being a prototype. If that were not the case you would have some difficulty in declaring such a prototype. C++ would then have required empty parameter lists to be specified with void as they are in C. That might have been OK had it not been that a general disambiguation rule was required anyway. For example:
enum X {zero, one, ten=10}; class Mytype { // a ctor that takes an X parameter Mytype(X); }; int main () { int i=3; Mytype mt(X(i)); // other code }
and now we have that ambiguity again. This time because the ancestral language, C, allow redundant parentheses. Mytype mt(X(i)) is the same as Mytype mt(X i) .
Rather than have a raft of special disambiguation rules C++ makes do with one: if a declaration can be a function prototype, it is a function prototype.
Unfortunately, this results in traps lying around for the unwary. However, I wonder about the following variation:
int main () { int i=3; auto Mytype mt(X(i)); // other code }
I do not believe that you can prototype a function as returning an auto (or come to that, a register ) qualified type. Therefore, I believe that the otherwise useless keyword auto should disambiguate declarations in favour of objects over functions. However, when I tried this on a couple of compilers they seemed to want to complain about the auto . Which of us is right?
I hope I am because it would be nice to provide a simple fix to force the selection you want without having to know all the special solutions that are currently required.