I for one have literally never been bitten by it anywhere. I get that you could launch missiles with it but ultimately that's true of literally any structured programming.
Allow me to introduce you to C++’s new std::filesystem::path class, which overloads the divide operators "/" and "/=" to concatenate two paths. (Not to be confused with the “+=“ operator, which also concatenates paths, but with subtly different behavior.)
Why did the division symbol get chosen for a concatenation behavior, of all things? Well, I suppose because it looks kind of like how we write paths with slashes, e.g. “dir/subdir/etc”. While I understand how this might be fun aesthetically once you already know the types involved, I find this completely opposite-than-numeric behavior here to be quite ambiguous and unintuitive.
Actually, slashes in filesystem::path is one of the operator overloading uses I really like in the C++ standard library (It’s time-saving, it’s useful, it’s easy to read). What I really don’t like is the IO operators << and >>, which I think is an incredibly horrid language design mistake. (It’s slow in performance, it’s hard to add formatting options, hard to read).
I suspect it might have bitten me if I wasn’t so lucky to encounter “/=“ first, which lead me to read the documentation and discover the different kinds of concatenation (“+=“ and “/=“) and their behavior.
To someone just skimming the code though, it might not at all be obvious that “+=“ and “/=“ both exist as concatenation operators, and their behaviors are different.
But yes it’s an aesthetic preference or opinion; I tend to prefer operators only when their only possible behavior is nearly so obvious that no documentation should be necessary. When that’s not the case, I prefer named functions/methods because they permit being explicit about subtle differences.
There are actually named member functions for those; they're append and concat. Can you guess which is which? I can't. append is actually "append with directory separator", which is weird, because I'd expect append to be a string-like append to match the append function on std::string. Instead concat is a string-like append despite the fact std::string doesn't have a concat member.
OTOH / is an instant mnemonic cue for "append with separator", and + matches the + on std::string.
Yes, I 100% agree with you that “append()” vs “concat()” are certainly no less confusing in their differences than “/=“ vs “+=“ (and the latter are arguably less confusing due to the mnemonic effect you mention).
In fact, to generalize, I think we can fairly say that operator overloads are akin to extremely short function names; in some cases they may work out great when there is not much ambiguity implicit in the underlying problem, but in other cases a longer and more explicitly descriptive name is required to disambiguate. In this case, “append” and “concat” seem quite poor names for different functions, given that they are virtually synonymous and therefore do nothing to describe or distinguish their differences of behavior.
So my claim is that we can do significantly better at resolving ambiguity with carefully named functions (or other approaches) than operators, or tersely/ambiguously named functions (like “append” and “concat”). Of course, this does come at the cost of code verbosity. Just where we should draw the line between too ambiguous vs too verbose, is of course a difficult subjective matter.
I suppose you're comparing against std::format, but the question is why overloading an operator (cout << "foo") would have been necessary vs a regular member function (cout.put("foo")).
Because it had to replace people using a comma inside printf. The member function could only accept one argument cleanly so you would've to chain them like cout.put.put.put
I was once trying to diagnose a performance issue in an algorithm written in Ocaml. Someone had overloaded ** to be (IIRC) 64-bit multiply. I had a momentary “gotcha in 2 seconds!” moment before realising what was happening with that one.
Funny you should mention OCaml, with doesn't have operator overloading! You can only have one function with a given name in scope at any point, that includes operators. Of course you can redefine with `let (+) a b = ...`, but then you have to explicitly open that module where you want to use that redefined operator. That makes it even more clear what's going on.