There's a difference between overloading `+`, `-`, `<<`, (which must obviously run custom code when applied to custom types), and overloading `,`, copying, assignment, moving, and tons of other "already works on everything" operations.
Both can cause problems when used inappropriately, but the latter is just chaos.
My answer to that is "you shouldn't want to deep copy on assignment". It's a performance trap and makes the code harder to understand. Without pervasive copy/assignment/move ctors you would be surprised to see how little you actually want to explicitly invoke them (usecases may vary, of course).
Yes, backwards compatability with C makes this a hard problem, but this is exactly what affine typing fixes: assignment is a shallow copy, but the old copy becomes inaccessible.
I don't think it's "backward compatibility with C". It's that C++ deliberately lets you go down to raw pointers. This lets you write things like memory allocators in C++. Yes, that makes it a hard problem to do only shallow copy, which gets us back to overloading assignment operators.
It sounds like you want C++ to be something other than it is. That's fine, for you. Use something else. But C++ is the way it is for a reason. A lot of people find those parts that you don't like to make it a more useful tool for things that we actually do.
Yes, C++ is that (almost). But that attribute of C++ is not the source of the problems that we're talking about here.
I mean, yes, in a sense it is, in that C had shallow copy of structs (IIRC), but... let's review, shall we?
pjmlp whined about operator overloading, about how it made code difficult to understand. Gankro specifically singled out copy and assignment overloading as being unnecessary and confusing. jawilson2 raised the question of deep vs. shallow copying (implying that you need to overload copy and assignment in order to easily do deep copy). Gankro replied that you shouldn't want to do deep copy on assignment. I pointed out the problem with his/her view, namely, possible memory corruption. Gankro relied, blaming this on backward compatibility with C. I explained that it's not backward compatibility per se that creates the problem, it's the ability to have pointers. My point was that C++ was deliberately going to have pointers - it wasn't just because of backward compatibility. You argue that C++ is in fact backward compatible. In the context of the conversation, so what? We're not questioning whether C++ is C-compatible. It is, but that's not the point.
The point is, even if C++ were deliberately not C-compatible, if it let you play with pointers (and given the intent of C++, it would) it would still have the issue of shallow vs. deep copy, and overloading assignment and copy would still be the solution.
You have completely ignored the actually interesting point of my argument, and focused on pointless details. In particular, my statement on affine types. Or, in C++ terms, move semantics by default, instead of copy semantics by default.
Raw pointers don't deep copy (what would that even mean?) so clearly you're only talking about structs that impose special semantics on raw pointers. If those structs were affine (assignment was a move which marked the old copy as unusable), then there would be no need to overload assignment for that facet of safety. They would just be in a different place, which would be fine.
The only really special case is having a pointer into yourself (really into yourself, not just into some fixed heap location you own -- basically, caring about your own location in memory). This is the only thing, as far as I can tell, that C++'s approach gains over affinity. Personally I consider it a bit of a nightmarish thing to do without GC (it's a bit nasty even with it TBH), but I know all too well that people will demand anything and everything; especially once they're used to it. Although this wouldn't completely preclude this pattern, it just means you can't wrap it up in a pseudo-safe way -- particularly if you want to pass it to arbitrary client code.
However this would be a massive break from C, which is copy semantics all the way down. I suppose C++ could have further given the `class` keyword meaning by making all classes affine. I dunno if people would have tolerated that kind of difference at the time. Sort of a soft breaking change, yaknow? Then again, maybe overloading `=` is already a breaking change in that regard.
True, I was not addressing the main point of what you said, and quibbling about your wording on a side point.
So before C++ assignment and copy operator overloading, you'd have a C struct, and assignment was a bitwise copy. If the original is destroyed after that, you're safe. If it's not, and the struct contains a pointer to allocated memory, you're going to have trouble unless you're very careful about who owns the memory.
But even then, you had situations where you wanted to truly make a (deep) copy. That took a call to a function that knew the structure, and which parts to deep copy. So the need to do that was still there.
So if we move to affine types or move semantics or whatever, the need for deep copy doesn't go away. If you don't have an overloaded assignment operator or copy operator, you're going to have a function of a different name that does the exact same operations. So the need for both operations doesn't go away. But the move/affine approach does make the double-free problem go away (as does the current C++ overloaded copy approach).
> language design is hard
Yeah. Somebody wants every possible action to be doable, and different people want different actions to be easy or the default. Nobody's going to be completely happy.
I would personally arguing that a deep copy necessitating an explicit function call is a good thing, though. It makes potentially expensive operations obvious, and improves your ability to reason about program behaviour.
Which, if we were talking about Rust here, might be relevant. But in this thread of the conversation, we're talking about C++ operator overloading and why you need it for deep copying on assignment. In the immediate context, how Rust does it is not relevant.
The discussio is about strategies to avoid the double-frees problem in a hypothetical "C++ with changes", and Rust's is one example. Seems fine and useful to give examples. (That's what Gankro's comment is too: Rust's strategy is affine types.)
Could be I missed something, but I looked all the way back to the root of this thread, and I didn't see (in any of the direct ancestors) anywhere where the topic was a hypothetical "C++ with changes".
In Rust, the = operator always represents a shallow copy (for types with move semantics, it will also statically invalidate the source) and there's no way to override this or otherwise permit user-defined code to run. Deep copying is done via a standard method, `.clone()`, which makes it obvious that user-defined code is running.
Both can cause problems when used inappropriately, but the latter is just chaos.