86

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,854
  • 8
  • 39
  • 46
  • 21
    +1: Well written, well argued, interesting topic and highly debatable. A shining example of a p.se question. – Allon Guralnek Dec 10 '10 at 12:37
  • 19
    +1: People listen to Joel Spolsky because he writes well and is well-known. But that doesnt make him right 100% of the time. I agree with your argument. If we all followed Joel's logic here, we'd never get anywhere. – Nobody Dec 10 '10 at 12:59
  • 5
    I'd argue that either i and j are declared locally so you can see their type quickly, or they're sucky variable names and should be renamed appropriately. – Cameron MacFarland Dec 10 '10 at 13:40
  • 5
    +1, but don't forget the best part of Joel's article: after running a marathon toward the correct answer, he for no apparent reason stops 50 feet short of it. Wrong code shouldn't just look wrong; it shouldn't compile. – Larry Coleman Dec 10 '10 at 15:57
  • 3
    @Larry: You can make wrong code fail to compile by defining classes appropriately, so in his example you could have SafeString and UnsafeString in C++, or RowIndex and ColumnIndex, but you'd then have to use operator overloading to make them behave intuitively. – David Thornley Dec 10 '10 at 16:26
  • @David: That's exactly my point: Joel could have suggested doing just what you describe in his article. – Larry Coleman Dec 10 '10 at 16:31
  • Joel's article becomes less important if you can assume that there is a very good debugger which can tell you the EXACT overloaded operator that is being used. Also, if you code in .Net, you can use an `IDisposable` interface together with a `using` statement (if that applies). – Job Dec 10 '10 at 22:46
  • This is as much a problem with C++ as with overloading per se. C++ has silent type conversions and a legacy of "really smart" hacks that turned out not to be such a good idea. – Móż Jan 31 '14 at 02:52
  • On top of a language like C where a builtin language construct translates pretty much to at most a very small handful of instructions, I see the point in not allowing operator overloading. The possibility of allowing function calls to hide under operators destroys that nice predictability property of C. I can't think of any good arguments against function overloading though (and I'd really appreciate some because otherwise it's going to get included in the language I'm making :D). – Petr Skocik Jul 05 '18 at 21:36

15 Answers15

34

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,816
  • 2
  • 57
  • 124
  • 14
    +1 For the 'I don't see' bit. Language features are there for use, not abuse. – Michael K Dec 10 '10 at 13:37
  • 5
    Yet we have a `<<` operator defined on streams which has nothing to do with the bitwise shift, right in the standard library of C++. – Malcolm Jul 25 '13 at 16:32
  • the 'bitwise shift' operator is only called that for historical reasons. When applied to standard types, it does a bitwise shift (in the same way that the + operator adds numbers together when applied to numeric types), however when applied to a complex type, it can do what it likes, as long as it makes sense for that type. – gbjbaanb Apr 07 '14 at 09:33
  • 1
    * is also used for dereferencing (as done by smart pointers and iterators); it's not clear where to put the boundary between good and bad overloading – martinkunev Feb 09 '15 at 12:57
  • It wouldn't be just anywhere, it'd be in the type definition of j. – Andy Oct 29 '15 at 01:07
  • @Malcolm and yet there's no ambiguity in that because streams obviously can't be left-shifted. (I also saw a matrix library that allowed `m << 1,2,3, 4,5,6, 7,8,9;` to fill a matrix, and matrices can't be left-shifted either, and if they could, it would make no sense to do that on the left side of a comma operator) – user253751 Jan 23 '18 at 04:36
  • @immibis There is no ambiguity. There is unintuitiveness. – Malcolm Jan 23 '18 at 13:39
  • @Andy - won't most devs go looking at a lot of other places to fix a particular strange occurrence/bug before checking type definitions for potentially over-loaded operators? If there are strong cases when this is the best solution, then it will be easy to find because you'll know were to look. – JeffO Jan 23 '18 at 15:23
  • @Malcolm I'd say that it's also unintuitive for `<<` to mean left shift. Surely it should be the much-less-than operator? – user253751 Jan 23 '18 at 21:36
  • @JeffO Same as they would look for a potentially wrongly overridden `.equals` – user253751 Jan 23 '18 at 21:41
  • If you're debugging, and everything is fine until `i = j * k` executes, and immediately after that code is executed things are not fine, it seems like there's not a lot of places left to look. – Andy Jan 24 '18 at 01:27
  • @immibis What is unintuitive about `<<` itself? It is an operator defined by the language, you either know what it means or not. It had existed at least since BCPL and had a well-established meaning by the time C++ appeared. And then it suddenly starts to mean some completely unrelated operation, also depending on what it is called on - that's what is unintuitive. – Malcolm Jan 24 '18 at 09:46
  • @Malcolm `<<` means "is much less than" the same way `<` means "is less than". The "left shift" meaning is specific to C-derived languages. That's the unintuitiveness. There is no difference between the reasoning of "`<<` normally means 'is much less than', but in C++, it means 'left shift', so that's unintuitive" and "`<<` normally means 'left shift', but with streams it means 'output', so that's unintuitive". The only reason you find `<<` as output unintuitive is that you didn't grow up with streams but you did grow up with left shifts. – user253751 Jan 24 '18 at 21:54
  • @immibis By your logic nothing can be unintuitive because if it is, you just "didn't grow up with this". That's clearly false. To address the specific examples, `≪` means "much less than". And in mathematics. `<<` in programming didn't mean anything until it became a language operator. What is more related, C syntax and mathematical notation or C syntax and C++ syntax? And if it's OK to start using `<<` for stream output, then would it be fine to use `=` for range creation or something? – Malcolm Jan 25 '18 at 09:39
  • By the unintuitiveness argument above, `this->m_var` is unintuitive because `->` means "minus greater than". Even though that makes absolutely no sense. (Nor does "much less than", in programming, because without specifying a range/magnitude how do you define "much"?) – FeRD May 15 '21 at 09:40
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,822
  • 3
  • 46
  • 49
  • 1
    Your last two sentences get to the heart of the objection to operator overloading: the desire for all code to be immediately obvious. – Larry Coleman Dec 10 '10 at 16:00
  • 2
    Isn't it obvious what M * N means, where M and N are of type Matrix? – Dima Dec 10 '10 at 16:14
  • @Dima: I'm not an expert on matrix algebra, but aren't there multiple kinds of matrix multiplications, like one yielding a matrix and another yielding a scalar? – fredoverflow Dec 10 '10 at 17:46
  • 2
    @Fred: Nope. There is one kind of matrix multiplication. You can multiply an m x n matrix by an n x k matrix and get an m x k matrix. – Dima Dec 10 '10 at 18:35
  • 1
    @FredOverflow: There are different ways to multiply a three-dimensional vector, one giving you a scalar and one giving you another three-dimensional vector, and so overloading `*` for those can cause confusion. Arguably you could use `operator*()` for the dot product and `operator%()` for the cross product, but I wouldn't do that for a general-use library. – David Thornley Dec 10 '10 at 18:48
  • @David: Dot product is a special case of matrix multiplication, e. g. if you multiply a 3x1 matrix by a 1x3 matrix you get a 1x1 matrix, i. e. a scalar. So if you have a class Matrix, and you represent a vector as a 1 x n or an n x 1 matrix, it makes perfect sense to overload * to mean matrix multiplication. However, as you have pointed out there is more than one kind of vector multiplication. So if you have a class Vector, then you should probably have non-member functions dotProduct(v1, v2) and crossProduct(v1, v2) rather than overload operators. – Dima Dec 10 '10 at 18:56
  • @david Thornley, @dima - is C++ allowed to reorder * operations? with a matrix M * N isn't necessarily the same as N * M if they are different shapes – Martin Beckett Apr 10 '11 at 06:27
  • 2
    @Martin Beckett: No. C++ isn't allowed to reorder `A-B` as `B-A` either, and all operators follow that pattern. Although there is always one exception: when the compiler can *prove* it doesn't matter , it's allowed to rearrange everything. – Sjoerd Apr 10 '11 at 07:13
  • 1
    @Martin Beckett: No, it's not allowed to reorder operations unless it can prove it makes no difference (`a + (b + c)` and `(a + b) + c)`, all variables unsigned and of the same size, would be an example). Moreover, `*` for matrices would in reality be `operator*(Matrix const & m)` or even `operator*(Matrix const & m1, Matrix const & m2)`, and reordering function arguments would be a real bad idea. – David Thornley Apr 11 '11 at 14:26
9

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

LennyProgrammers
  • 5,649
  • 24
  • 37
  • Erm, except for what's inherited from C, C++ (and that's what Fred was asking about) does pretty much the same thing. Now what's your take on whether this is good or bad? – sbi Dec 10 '10 at 12:39
  • @sbi I love operator overloading... Actually even C has overloaded operators... You can use them for `int`, `float`, `long long` and whatever. So what's that all about? – FUZxxl Oct 08 '11 at 14:04
  • @FUZxxl: This is all about _user-defined_ operators overloading the built-in ones. – sbi Oct 08 '11 at 14:12
  • 1
    @sbi Haskell has no distinction between *builtin* and *user-defined*. All operators are equal. You can even turn some extensions on that remove all predefined stuff and let you write anything from scratch, including any operators. – FUZxxl Oct 08 '11 at 14:18
  • @FUZxxl: That might well be, but those opposing overloaded operators usually do not oppose using built-in `+` for different built-in number types, but creating user-defined overloads. hence my comment. – sbi Oct 08 '11 at 14:23
  • @sbi Ah.. I get it. – FUZxxl Oct 08 '11 at 14:51
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.
Larry Coleman
  • 6,101
  • 2
  • 25
  • 34
  • Learning from code is not always the primary objective though. In a case like "feature X is glitching, the person who wrote it left the company, and you need to fix it ASAP", I'd much rather have code that's immediately obvious. – Errorsatz May 24 '19 at 19:01
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
  • 9,992
  • 6
  • 37
  • 56
  • Good point. However, abandoning overloading altogether does not play nice with generic programming... – fredoverflow Dec 10 '10 at 12:20
  • @FredO: Of course not. But generic programming is all about using the same algorithm for very different types, so those preferring `multiply_matrix()` won't like generic programming either. – sbi Dec 10 '10 at 13:04
  • 2
    You're rather optimistic about names aren't you? Based on some places I've worked, I'd expect names like 'multiply()` and 'multiplym()` or maybe `real_multiply()` or so. Developers often aren't good with names, and `operator*()` at least is going to be consistent. – David Thornley Dec 10 '10 at 18:56
  • @David: Yeah, I skipped over the fact that the names might be bad. But then we might just as well assume that `operator*()` might do something stupid, `j` is a macro evaluating to an expressions involving five function calls, and whatnot. You then can't compare the two approaches anymore then. __But, yes, naming things well is hard, although well worth whatever time it takes.__ – sbi Dec 10 '10 at 19:21
  • 5
    @David: And since naming things is hard, names should be banished from programming languages, right? It's just too easy to get them wrong! ;-) – fredoverflow Dec 11 '10 at 08:04
  • This is just an argument for having more operators, allowing more flexible overloading, such as . for dot product and x for cross product or even the words "dot" and "cross" (see Perl's eq, lt, gt, for examples of letters as operators). – Anonymous Sep 13 '11 at 01:26
  • @Doug: But in the end that means the meaning of those operators is overloaded even more. If you use `.` for dot products, then is `x.y` such a beast or is it simply an access to `y`, member of the type of `x`? And don't get me even started on `xxy`... To me, this idea seems a clear violation of #1 and #2 of [the three basic rules of operator overloading](http://stackoverflow.com/questions/4421708#4421708). No, the opposite is true, I wish we had _less_ operators to overload. (Unary prefix `&` anyone?) I'd vote for well-chosen names, with very few exceptions. – sbi Sep 13 '11 at 08:36
  • @sbi Then make an operator "cross*", "dot*", etc. However, in cases like this I'm all for using functions like dot_product(x, y). If there's some reasonable and common interpretation of an operator though, why not use it? For example, multiplying complex numbers, indefinite length integers, decimals, etc. should all be the same operator. Concatenation of string-like things should use the same operator. Indexing into any collection should use the same operator, etc. – Anonymous Sep 15 '11 at 22:10
  • @Doug: In that we agree: If in doubt, use a function. (See [this](http://stackoverflow.com/q/4421706/140719).) The same operation should be implemented by a function/operator with the same name no matter which type. The latter is achieved through overloading. (`std::complex` has indeed the operators overloaded one would expect to be available with it.) – sbi Sep 16 '11 at 04:13
5

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.
Bart van Ingen Schenau
  • 71,712
  • 20
  • 110
  • 179
  • 2
    +1 from me, but the second argument can equally well be applied to inheritance. Many people don't have a clue about inheritance and try to apply it to everything. I think most programmers would agree that it is possible to misuse inheritance. Does that mean inheritance is "evil" and should be abandoned from programming languages? Or should we leave it in because it can also be useful? – fredoverflow Dec 10 '10 at 17:42
  • @FredOverflow The second argument can be applied to anything that is "new and hot". I am not giving it as an argument to remove operator overloading from a language, but as a reason why people argue against it. As far as I am concerned, operator overloading is useful but should be used with care. – Bart van Ingen Schenau Dec 14 '10 at 10:50
  • IMHO, allowing overloads of `&&` and `||` in a way which does not imply sequencing was a big mistake (IMHO, if C++ was going to allow overloading of those, it should have used a special "two-function" format, with the first function being required to return a type which was implicitly convertible to an integer; the second function could take two or three arguments, with the "extra" argument of the second function being the return type of the first. The compiler would call the first function and then, if it returned non-zero, evaluate the second operand and call the second function upon it.) – supercat Jul 19 '12 at 15:54
  • Of course, that's not nearly as bizarre as allowing the comma operator to be overloaded. Incidentally, one overloading-ish thing I've not really seen, but would like to, would be a means of tight-binding member access, allowing an expression like `foo.bar[3].X` to be handled by `foo`'s class, rather than requiring `foo` to expose a member which could support subscripting and then expose a member `X`. If one wanted to force evaluation via actual member access, one would write `((foo.bar)[3]).X`. – supercat Jul 19 '12 at 16:02
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!

  • +1, with a caveat: C++ gives you enough rope to hang yourself. But if I want to implement a linked list in C++, I'd want the ability to use [] to access the nth element. It makes sense to use the operators for data that (mathematically speaking) they are valid for. – Michael K Dec 10 '10 at 13:41
  • @Michael, you cannot live with the `list.get(n)` syntax? –  Dec 10 '10 at 13:44
  • @Thorbjørn: It's actually fine as well, perhaps a poor example. Time may be better - overloading +,- would make sense rather than time.add(anotherTime). – Michael K Dec 10 '10 at 13:50
  • @Michael, and how would you figure out if `+` was overloaded? –  Dec 10 '10 at 13:59
  • You'd see a time object, know that operators would not apply normally for objects, and know that it had to be overloaded. But it would be transparent anyway, because as long as the library programmer implemented the add operation correctly, 3:00 + 1:30 = 4:30. – Michael K Dec 10 '10 at 14:46
  • @Michael, "as long as the library programmer implemented the add operation correctly"... But in order to be _certain_ you have to check :) –  Dec 10 '10 at 15:37
  • @Thorbjørn: Got me there. – Michael K Dec 10 '10 at 16:16
  • 4
    @Michael: About linked lists, `std::list` does not overload `operator[]` (or give any other means of indexing into the list), because such an operation would be O(n), and a list interface should not expose such a function if you care about efficiency. Clients might be tempted to iterate over linked lists with indexes, making O(n) algorithms needlessly O(n^2). You see that quite often in Java code, especially if people work with the `List` interface which aims to abstract complexity away completely. – fredoverflow Dec 10 '10 at 17:23
  • 5
    @Thor: "But in order to be certain you have to check :)"... Again, **this is not tied to operator overloading**. If you see `time.add(anotherTime)`, you will also have to check if the library programmer implemented the add operation "correctly" (whatever that means). – fredoverflow Dec 10 '10 at 17:35
  • @Fred: That is a very good reason not to use the overloading. Again, perhaps that was not the best example to use. – Michael K Dec 10 '10 at 18:09
  • @Michael: `std::map<>` is a better example of overloading `operator[]` than `std::list<>`. Of course, I'm eagerly awaiting the ability to write `for (auto i = foo.begin(); i != foo.end(); ++i)`, which will make writing proper iterator loops a lot simpler. – David Thornley Dec 10 '10 at 18:54
  • @David: The new for loop allows an even better solution: `for (auto& x: foo)` :) – fredoverflow Dec 10 '10 at 19:17
  • @michael, perhaps you could give a better example, then? –  Dec 11 '10 at 10:29
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.

Scott Wales
  • 271
  • 1
  • 7
  • Isn't the linux kernel developed in pure C? Why discuss (operator) overloading at all in this context? – fredoverflow Dec 10 '10 at 13:26
  • The concerns are the same for any project with a similar development process, regardless of language. Excessive overloading can make it difficult to understand the impact of changes if all you have to go on are a few lines from a patch file. – Scott Wales Dec 10 '10 at 13:34
  • @FredOverflow: The Linux kernel is in GCC C really. It uses all sorts of extensions that give its C an almost C++ feel at some times. I'm thinking of some of the fancy type manipulations. – Zan Lynx Dec 10 '10 at 17:39
  • 2
    @Scott: There is no point in discussing the "evilness" of overloading with respect to projects programmed in C, because C does not have the ability to overload functions. – fredoverflow Dec 10 '10 at 17:47
  • 4
    Linus Torvalds seems to me to have a narrow viewpoint. He sometimes criticizes things that aren't really useful for Linux kernel programming as if that makes them unsuitable for general use. Subversion is one example. It's a nice VCS, but Linux kernel development really needs a distributed VCS, so Linus criticized SVN in general. – David Thornley Dec 10 '10 at 18:42
  • @David Thornley: A good point. I am always a little weary when people quote divine oracles, they have their agendas and bias too. – Orbling Dec 11 '10 at 00:53
  • @David, have you worked with a DVCS? Extensively? –  Dec 11 '10 at 10:34
  • @Thorbjørn: Not extensively. They're nice, particularly the ability to do de facto easy branching by working with my own local repository, but I don't see that we'd gain tremendously by using one instead of SVN where I work. – David Thornley Dec 11 '10 at 19:02
  • @David Thornley: Well, the fact that you can commit while offline and have you own local branches that no one will see. – radekg Dec 15 '10 at 20:56
2

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
  • 369
  • 4
  • 14
2

I suspect it has something to do with breaking expectations. I've you're used to C++, 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.

Joeri Sebrechts
  • 12,922
  • 3
  • 29
  • 39
  • Overloaded operators should never do "something odd". It's fine if it does something complex, of course. But only overload when it has one, obvious meaning. – Sjoerd Apr 10 '11 at 07:18
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,075
  • 7
  • 15
  • +1 Don't make [] the equivalent of ++. – Michael K Dec 10 '10 at 13:38
  • 3
    Are you saying we shouldn't be able to overload the operators you mentioned *at all* ? Or are you just saying that we should overload them for sane purposes only? Because I would hate to see containers without `operator[]`, functors without `operator()`, smart pointers without `operator->` and so on. – fredoverflow Dec 10 '10 at 17:39
  • I'm saying that the potential problem of operator overloading with math operations is small compared to those operators. Doing something clever or crazy with math operators might troublesome, but the operators I listed, which people usually don't think of as operators but rather basic language elements, should *always* meet the expectation defined by the language. `[]` should always be an array-like accessor, and `->` should always mean accessing a member. It doesn't matter if it's actually an array or a different container, or if it's a smart pointer or not. – Allon Guralnek Dec 10 '10 at 21:44
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
  • 2
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
  • 29,980
  • 3
  • 73
  • 114
  • or, in C#, for using += to tie an event to a delegate. I don't think that blaming language features for programmer stupidity is a constructive way forward. – gbjbaanb Oct 08 '11 at 23:50
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??
    }
};
Zac Howland
  • 111
  • 1
  • 2
    Multiplying employees? Surely that's a sackable offence, if they do it on the boardroom table, that is. – gbjbaanb Oct 08 '11 at 23:48
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.

James P. Wright
  • 2,135
  • 4
  • 18
  • 25