94

I just read one of Joel's articles in which he says:

In general, I have to admit that I’m a little bit scared of language features that hide things. When you see the code

i = j * 5;

… in C you know, at least, that j is being multiplied by five and the results stored in i.

But if you see that same snippet of code in C++, you don’t know anything. Nothing. The only way to know what’s really happening in C++ is to find out what types i and j are, something which might be declared somewhere altogether else. That’s because j might be of a type that has operator* overloaded and it does something terribly witty when you try to multiply it.

(Emphasis mine.) Scared of language features that hide things? How can you be scared of that? Isn't hiding things (also known as abstraction) one of the key ideas of object-oriented programming? Everytime you call a method a.foo(b), you don't have any idea what that might do. You have to find out what types a and b are, something which might be declared somewhere altogether else. So should we do away with object-oriented programming, because it hides too much things from the programmer?

And how is j * 5 any different from j.multiply(5), which you might have to write in a language that does not support operator overloading? Again, you would have to find out the type of j and peek inside the multiply method, because lo and behold, j might be of a type that has a multiply method that does something terribly witty.

"Muahaha, I'm an evil programmer that names a method multiply, but what it actually does is totally obscure and non-intuitive and has absolutely nothing to do whatsoever with multiplying things." Is that a scenario we must take into consideration when designing a programming language? Then we have to abandon identifiers from programming languages on the grounds that they might be misleading!

If you want to know what a method does, you can either glance at the documentation or peek inside the implementation. Operator overloading is just syntactic sugar, and I don't see how it changes the game at all.

Please enlighten me.

fredoverflow
  • 6,954

15 Answers15

35

Abstraction 'hides' code so you don't have to be concerned about the inner workings and often so you can't change them, but the intention was not to prevent you from looking at it. We just make assumptions about operators and like Joel said, it could be anywhere. Having a programming feature requiring all overloaded operators to be established in a specific location may help to find it, but I'm not sure it makes using it any easier.

I don't see making * do something that doesn't closely resemble multiplication any better than a function called Get_Some_Data that deletes data.

JeffO
  • 36,956
22

IMHO, language features such as operator overloading give the programmer more power. And, as we all know, with great power comes great responsibility. Features that give you more power also give you more ways to shoot yourself in the foot, and, obviously, should be used judiciously.

For example, it makes perfect sense to overload the + or the * operator for class Matrix or class Complex. Everyone will instantly know what it means. On the other hand, to me the fact that + means concatenation of strings is not at all obvious, even though Java does this as a part of the language, and STL does for std::string using operator overloading.

Another good example of when operator overloading makes code more clear is smart pointers in C++. You want the smart pointers to behave like regular pointers as much as possible, so it makes perfect sense to overload the unary * and -> operators.

In essence, operator overloading is nothing more than just another way to name a function. And there is a rule for naming functions: the name must be descriptive, making it immediately obvious what the function does. The same exact rule applies to operator overloading.

Dima
  • 11,852
11

In Haskell "+", "-", "*", "/" etc are just (infix) functions.

Should you name an infix function "plus" as in "4 plus 2"? Why not, if addition is what your function does. Should you name your "plus" function "+"? Why not.

I think the issue with so called "operators" are, that they mostly resemble mathematical operations and there are not many ways to interpret those and thus there are high expectations about what such a method/function/operator does.

EDIT: made my point more clear

8

Based on the other answers I've seen, I can only conclude that the real objection to operator overloading is the desire for immediately obvious code.

This is tragic for two reasons:

  1. Carried to its logical conclusion, the principle that code should be immediately obvious would have us all still coding in COBOL.
  2. You don't learn from code that is immediately obvious. You learn from code that makes sense once you take some time to think about how it works.
7

I see two problems with operator overloading.

  1. Overloading changes the semantics of the operator, even if that is not intended by the programmer. For example, when you overload &&, || or ,, you lose the sequence points that are implied by the built-in variants of these operators (as well as the short-circuiting behaviour of the logical operators). For this reason, it is better not to overload these operators, even if the language allows it.
  2. Some people see operator overloading as such a nice feature, they start to use it everywhere, even if it is not the appropriate solution. This causes other people to over-react in the other direction and warn against the use of operator overloading. I don't agree with either group, but take the middle ground: Operator overloading should be used sparingly and only when
    • the overloaded operator has the natural meaning for both the domain experts and the software experts. If those two groups do not agree on the natural meaning for the operator, don't overload it.
    • for the type(s) involved, there is no natural meaning for the operator and the immediate context (preferably same expression, but no more than a few lines) always makes it clear what the meaning is of the operator. An example of this category would be operator<< for streams.
5

I somewhat agree.

If you write multiply(j,5), j could be of a scalar or matrix type, making multiply() more or less complex, depending on what j is. However, if you abandon the idea of overloading altogether, then the function would have to be named multiply_scalar() or multiply_matrix() which would make it obvious what's happening underneath.

There's code where many of us would prefer it one way and there's code where most of us would prefer it the other way. Most of the code, however, falls into thew middle ground between those two extremes. What you prefer there depends on your background and personal preferences.

sbi
  • 10,052
3

In addition to what has already been said here, there's one more argument against operator overloading. Indeed, if you write +, this is kind of obvious that you mean addition of something to something. But this is not always the case.

C++ itself provides a great example of such a case. How is stream << 1 supposed to be read? stream shifted left by 1? It is not obvious at all unless you explicitly know that << in C++ also writes to the stream. However, if this operation were implemented as a method, no sane developer would write o.leftShift(1), it would be something like o.write(1).

The bottom line is that by making operator overloading unavailable, the language makes programmers think about the names of operations. Even if the chosen name is not perfect, it is still harder to misinterpret a name than a sign.

Malcolm
  • 379
3

Based on my personal experience, the Java way of allowing multiple methods, but not overloading operator, means that whenever you see an operator you know exactly what it does.

You do not have to see if * invokes strange code but know that it is a multiply, and it behaves exactly like in the way defined by the Java Language Specification. This means you can concentrate on the actual behaviour instead of finding out all the wicket stuff defined by the programmer.

In other words, prohibiting operator overload is a benefit to the reader, not the writer, and hence makes programs easier to maintain!

3

One difference between overloading a * b and calling multiply(a,b) is that the latter can easily be grepped for. If the multiply function isn't overloaded for different types then you can find out exactly what the function is going to do, without having to track through the types of a and b.

Linus Torvalds has an interesting argument about operator overloading. In something like linux kernel development, where most of the changes are sent via patches over email, it's important that the maintainers can understand what a patch will do with only a few lines of context around each change. If functions and operators are not overloaded then the patch can more easily be read in a context independent way, as you don't have to go through the changed file working out what all of the types are and check for overloaded operators.

2

I suspect it has something to do with breaking expectations. If you're used to C++, then you're used to operator behavior not being dictated entirely by the language, and you won't be surprised when an operator does something odd. If you're used to languages that don't have that feature, and then see C++ code, you bring along the expectations from those other languages, and may be nastily surprised when you discover that an overloaded operator does something funky.

Personally I think there's a difference. When you can change the behavior of the language's built-in syntax, it becomes more opaque to reason about. Languages that don't allow meta-programming are syntactically less powerful, but conceptually simpler to understand.

Don Hatch
  • 105
2

I think that overloading math operators is not the real issue with operator overloading in C++. I think overloading operators that should not rely on the context of the expression (i.e. type) is "evil". E.g. overloading , [ ] ( ) -> ->* new delete or even the unary *. You have a certain set of expectations from those operators that should never change.

Allon Guralnek
  • 1,085
  • 7
  • 15
2

I perfectly understand you do not like Joel's argument about hiding. Me neither. It's indeed much better to use '+' for things like built-in numerical types or for your own ones like, say, matrix. I admit this is neat and elegant to be able to multiply two matrices with the '*' instead of '.multiply( )'. And after all we've got the same kind of abstraction in both cases.

What hurts here is the readability of your code. In a real-life cases, not in the academic example of matrix multiplication. Especially if your language allows to define operators that are not initially present in the language core, for instance =:=. A lot of extra questions arise at this point. What is that damn operator about? I mean what is the precedence of that thing? What is the associativity? In which order is the a =:= b =:= c really executed?

That is already an argument against operator overloading. Still not convinced? Checking the precedence rules took you no more then 10 sec? Ok, let's go further.

If you start to use a language that allows operator overloading, for instance that popular one whose name begins with 'S', you will quickly learn that library designers love to override operators. Of course they are well educated, they follow the best practices (no cynicism here) and all their APIs make perfect sense when we look at them separately.

Now imagine you have to use a few APIs that make heavy use of operators overloading together in a one piece of code. Or even better - you have to read some legacy code like that. This is when the operator overloading really sucks. Basically if there is a lot of overloaded operators in one place they will soon start to mingle with the other non alpha-numerical characters in your program code. They will mingle with non alpha-numerical characters that are not really operators but rather some more fundamental language grammar elements that define things like blocks and scopes, shape flow control statements or denote some meta thingies. You will need to put the glasses and move your eyes 10 cm closer to the LCD display to understand that visual mess.

akosicki
  • 121
1

In comparison to spelled out methods, operators are shorter, but also they don't require parentheses. Parentheses are relatively inconvenient to type. And you must balance them. In total, any method call requires three characters of plain noise compared to an operator. This makes using operators very, very tempting.
Why else would anyone want to this: cout << "Hello world"?

The problem with overloading is, that most programmers are unbelievably lazy and most programmers cannot afford to be.

What drives C++ programmers to the abuse of operator overloading is the not its presence, but the absence of a neater way to perform method calls. And people are not just afraid of operator overloading because it's possible, but because it's done.
Note that for example in Ruby and Scala nobody is afraid of operator overloading. Apart from the fact, that the use of operators is not really shorter than methods, another reason is, that Ruby limits operator overloading to a sensible minimum, while Scala allows you to declare your own operators thus making avoiding collision trivial.

back2dos
  • 30,140
1

In general, I avoid using operator overloading in non-intuitive ways. That is, if I have a numeric class, overloading * is acceptable (and encouraged). However, if I have a class Employee, what would overloading * do? In other words, overload operators in intuitive ways that make it easy to read and understand.

Acceptable/Encouraged:

class Complex
{
public:
    double r;
    double i;

    Complex operator*(const Compex& rhs)
    {
        Complex result;
        result.r = (r * rhs.r) - (i * rhs.i);
        result.i = (r * rhs.i) + (i * rhs.r);
        return result;
    }
};

Not acceptable:

class Employee
{
public:
    std::string name;
    std::string address;
    std::string phone_number;

    Employee operator* (const Employee& e)
    {
        // what the hell do I do here??
    }
};
0

The reason Operator Overloading is scary, is because there are a large number of programmers that would never even THINK that * doesn't mean simply "multiply", whereas a method like foo.multiply(bar) at least instantly points out to that programmer that someone wrote a custom multiply method. At which point they would wonder why and go investigating.

I have worked with "good programmers" who were in high level positions that would create methods called "CompareValues" that would take 2 arguments, and apply the values from one to the other and return a boolean. Or a method called "LoadTheValues" that would go to the database for 3 other objects, get values, do calculations, modify this and save it to the database.

If I am working on a team with those types of programmers, I instantly know to investigate things they have worked on. If they overloaded an operator, I have no way at all of knowing that they did it except to assume they did and go looking.

In a perfect world, or a team with perfect programmers, operator overloading is probably a fantastic tool. I have yet to work on a team of perfect programmers though, so that's why it's scary.