What are some guidelines / "rules of thumb" for overloading operators?
A: Here are a few guidelines / rules of thumb .Use common sense. If your overloaded operator makes life simpler and safer for your users, do it; or else don't. It is the most significant guideline. Actually it is, in a very real sense, the only guideline; the rest are merely special cases.
If you describe arithmetic operators, maintain usual arithmetic identities. For instance, if your class describe x + y and x - y, then x + y - y have to return an object which is behaviorally equivalent to x. The term behaviorally equivalent is described in the bullet on x == y below, although simply put, this means the two objects must ideally act like they have the similar state. It should be true even if you decide not to define an == operator for objects of your class.
You must provide arithmetic operators only while they make logical sense toward the users. Subtracting two dates makes sense, logically returning the duration among those dates, so you may want to let date1 - date2 for objects of your Date class (provided you have a reasonable class/type to signify the duration among two Date objects). Though adding two dates makes no sense: what does it mean to add July 4, 1776 with June 5, 1959? Likewise it makes no sense to multiply or divide dates, thus you should not define any of those operators.
You must provide mixed-mode arithmetic operators only while they make logical sense to users. For instance, this makes sense to add duration (e.g. 35 days) to a date (e.g. July 4, 1776), thus you might define date + duration to return a Date. Likewise date - duration could also return a Date. However duration - date does not make sense at the conceptual level (what does this mean to subtract July 4, 1776 from 35 days?) so you must not define that operator.
If you give constructive operators, they must return their result by value. For instance, x + y must return its result by value. If this returns by reference, you will possibly run into many problems figuring out who owns the referent and while the referent will get destructed. Of no importance if returning by reference is more competent; it is perhaps wrong.
If you provide constructive operators, they must not change their operands. For instance, x + y must not change x. For some crazy cause, programmers frequently define x + y to be logically the similar as x += y since the latter is faster. But keep in mind, your users expect x + y to make a copy. Actually they selected the + operator (over, say, the += operator) exactly since they wanted a copy. If they wished to modify x, they would have utilized whatever is equivalent to x += y instead. For your users don't make semantic decisions; it's their decision, not yours, whether they wish the semantics of x + y vs. x += y. Tell them that one is faster if you wish, but then step back and allow them make the last decision they know what they're trying to get and you do not.
If you provide constructive operators, they ought to let promotion of the left-hand operand. For instance, if your class Fraction supports promotion from int to Fraction (using the non-explicit ctor Fraction::Fraction(int)), and if you let x - y for two Fraction objects, you ought to also let 42- y. In practice it simply means that your operator-() must not be a member function of Fraction. You will make it a friend typically, if for no other cause than to force it into the public: part of the class, but even if it is not a friend, it must not be a member.
Generally, your operator must change its operand(s) if and only if the operands get changed while you apply the similar operator to intrinsic types. x == y and x << y must not change either operand; x *= y and x <<= y must (but only the left-hand operand).
If you define x++ & ++x, maintain the usual identities. For instance, x++ & ++x should have should have the same observable effect on x, and ought to differ only in what they return. ++x ought to return x by reference; x++ ought to either return a copy (by value) of the original state of x or must have a void return-type. Usually you're better off returning a copy of the original state of x by value, especially if your class will be utilized in generic algorithms. The easy way to do that is to implement x++ by three lines: make a local copy of *this, call ++x (that means. this- >operator++()), then return local copy. Similar comments for x-- and --x.
If you define ++x and x += 1, maintain the usual identities. For instance, these expressions should have the same observable behavior, by including the same result. Among other things, that means your += operator must return x by reference. Alike comments for --x and x -= 1.
If you define *p and p[0] for pointer like objects, maintain the usual identities. For instance, these two expressions must have the same result and neither must change p.
If you define *(p+i) and p[i] for pointer-like objects, maintain the usual identities. For instance, these two expressions must have the same result and neither must change p. Similar comments for p[-i] and *(p-i).
Generally Subscript operators come in pairs; see on const-overloading.
If you define x == y, then x == y must be true if and only if the two of objects are equivalent behaviorally. In this bullet, the term "behaviorally equivalent" refers to the observable behavior of any operation or sequence of operations applied to x will be the similar as when applied to y. The term "operation" means methods, operators, and friends or just regarding anything else you can do with these objects (except, certainly the address-of operator). You won't always be capable to achieve that aim, but you have to get close, and you should document any variances (other than the address-of operator).
If you define x == y & x = y, maintain the usual identities. For instance, after an assignment, the two objects must be equal. Even if you don't define x == y, the two objects should be behaviorally equivalent (see above for the meaning of that phrase) after an assignment.
If you define x == y and x != y, you mustg maintain the usual identities. For instance, these expressions must return something convertible to bool, neither must change its operands, and x == y must have the similar result as !(x != y), and vice versa.
If you define inequality operators such as x <= y and x < y, you must maintain the usual identities. For instance, if x < y & y < z are both true, then x < z must also be true, etc. Same comments for x >= y and x > y.
If you define inequality operators such as x < y and x >= y, you must maintain the usual identities. For instance, x < y must have the result as !(x >= y). You can't always do that, however you must get close and you must document any variances. Same comments for x > y and !(x <= y), etc. Ignore overloading short-circuiting operators: x || y or x && y. The overloaded versions of these don't short-circuit they evaluate both of the operands even if the left-hand operand "determines" the outcome, so it confuses users.
Ignore overloading the comma operator: x, y. overloaded comma operator does not contain the similar ordering properties that it has while it is not overloaded, & that confuses users.
Don't overload an operator that is non-intuitive to your users. It is called the Doctrine of Least Surprise. For instance, altough C++ uses std::cout << x for printing, & although technically printing is called inserting, and even though inserting sort of sounds like what happens while you push an element on a stack, don't overload myStack << x to push an element on a stack. It may make sense while you're really tired or otherwise mentally impaired, and a few of your people might think it's "kewl," however just say No.
Use common sense. If you don't view "your" operator listed here, you can figure it out. Just keep in mind the ultimate goals of operator overloading: to make life easier for your users, particularly to make their code cheaper to write and more obvious.
Caveat: the list is not exhaustive. This means there are other entries that you might consider
"missing."
Caveat: the list contains guidelines, not hard and fast rules. This means almost every entry has exceptions, and most of those exceptions are not explicitly stated.