More Effective C++ | Item 7: Never overload ||, or , Back to Item 6: Distinguish between prefix and postfix forms of increment and decrement operators Continue to Item 8: Understand the different meanings of new and delete Item 7: Never overload &&, ||, or ,. Like C, C++ employs short-circuit evaluation of boolean expressions. This means that once the truth or falsehood of an expression has been determined, evaluation of the expression ceases, even if some parts of the expression haven't yet been examined. For example, in this case, char *p; ... if ((p != 0) && (strlen(p) > 10)) ... there is no need to worry about invoking strlen on p if it's a null pointer, because if the test of p against 0 fails, strlen will never be called. Similarly, given int rangeCheck(int index) { if ((index < lowerBound) || (index > upperBound)) ... ... } index will never be compared to upperBound if it's less than lowerBound. This is the behavior that has been drummed into C and C++ programmers since time immemorial, so this is what they expect. Furthermore, they write programs whose correct behavior depends on short-circuit evaluation. In the first code fragment above, for example, it is important that strlen not be invoked if p is a null pointer, because the standard for C++ states (as does the standard for C) that the result of invoking strlen on a null pointer is undefined. C++ allows you to customize the behavior of the && and || operators for user-defined types. You do it by overloading the functions operator&& and operator||, and you can do this at the global scope or on a per-class basis. If you decide to take advantage of this opportunity, however, you must be aware that you are changing the rules of the game quite radically, because you are replacing short-circuit semantics with function call semantics. That is, if you overload operator&&, what looks to you like this, if (expression1 && expression2) ... looks to compilers like one of these: if (expression1.operator&&(expression2)) ... // when operator&& is a // member function if (operator&&(expression1, expression2)) ... // when operator&& is a // global function This may not seem like that big a deal, but function call semantics differ from short-circuit semantics in two crucial ways. First, when a function call is made, all parameters must be evaluated, so when calling the functions operator&& and operator||, both parameters are evaluated. There is, in other words, no short circuit. Second, the language specification leaves undefined the order of evaluation of parameters to a function call, so there is no way of knowing whether expression1 or expression2 will be evaluated first. This stands in stark contrast to short-circuit evaluation, which always evaluates its arguments in left-to-right order. As a result, if you overload && or ||, there is no way to offer programmers the behavior they both expect and have come to depend on. So don't overload && or ||. The situation with the comma operator is similar, but before we delve into that, I'll pause and let you catch the breath you lost when you gasped, "The comma operator? There's a comma operator?" There is indeed. The comma operator is used to form expressions, and you're most likely to run across it in the update part of a for loop. The following function, for example, is based on one in the second edition of Kernighan's and Ritchie's classic The C Programming Language (Prentice-Hall, 1988): // reverse string s in place void reverse(char s[]) { for (int i = 0, j = strlen(s)-1; i < j; ++i, --j) // aha! the comma operator! { int c = s[i]; s[i] = s[j]; s[j] = c; } } Here, i is incremented and j is decremented in the final part of the for loop. It is convenient to use the comma operator here, because only an expression is valid in the final part of a for loop; separate statements to change the values of i and j would be illegal. Just as there are rules in C++ defining how && and || behave for built-in types, there are rules defining how the comma operator behaves for such types. An expression containing a comma is evaluated by first evaluating the part of the expression to the left of the comma, then evaluating the expression to the right of the comma; the result of the overall comma expression is the value of the expression on the right. So in the final part of the loop above, compilers first evaluate ++i, then --j, and the result of the comma expression is the value returned from --j. Perhaps you're wondering why you need to know this. You need to know because you need to mimic this behavior if you're going to take it upon yourself to write your own comma operator. Unfortunately, you can't perform the requisite mimicry. If you write operator, as a non-member function, you'll never be able to guarantee that the left-hand expression is evaluated before the right-hand expression, because both expressions will be passed as arguments in a function call (to operator,). But you have no control over the order in which a function's arguments are evaluated. So the non-member approach is definitely out. That leaves only the possibility of writing operator, as a member function. Even here you can't rely on the left-hand operand to the comma operator being evaluated first, because compilers are not constrained to do things that way. Hence, you can't overload the comma operator and also guarantee it will behave the way it's supposed to. It therefore seems imprudent to overload it at all. You may be wondering if there's an end to this overloading madness. After all, if you can overload the comma operator, what can't you overload? As it turns out, there are limits. You can't overload the following operators: . .* :: ?: new delete sizeof typeid static_cast dynamic_cast const_cast reinterpret_cast You can overload these: operator new operator delete operator new[] operator delete[] + - * / % ^ & | ~ ! = < > += -= *= /= %= ^= &= |= << >> >>= <<= == != <= >= && || ++ -- , ->* -> () [] (For information on the new and delete operators, as well as operator new, operator delete, operator new[], and operator delete[], see Item 8.) Of course, just because you can overload these operators is no reason to run off and do it. The purpose of operator overloading is to make programs easier to read, write, and understand, not to dazzle others with your knowledge that comma is an operator. If you don't have a good reason for overloading an operator, don't overload it. In the case of &&, ||, and ,, it's difficult to have a good reason, because no matter how hard you try, you can't make them behave the way they're supposed to. Back to Item 6: Distinguish between prefix and postfix forms of increment and decrement operators Continue to Item 8: Understand the different meanings of new and delete