Jonathan M Davis
Posted in reply to Dom DiSc
| On Friday, July 26, 2024 2:17:21 AM MDT Dom DiSc via Digitalmars-d-learn wrote:
> On Thursday, 25 July 2024 at 13:07:03 UTC, Jonathan M Davis wrote:
> > On Thursday, July 25, 2024 6:00:58 AM MDT Dom DiSc via
> >
> >> But a parameter given by value is ALWAYS a copy.
> >
> > It has to be a _full_, independent copy. If you're talking about integer types, that's a non-issue, but if you're talking about types with any indirections, it becomes a big issue. If the type contains any pointers, dynamic arrays, class references, etc. then the copy can't be mutable unless you're dealing with a struct with a copy constructor that does a deep copy of all of the member variables. So, in the general case, you cannot copy a value and expect to get a mutable result.
>
> If you are not able to construct a mutable copy of a type, why on earth are you handing it over by value?!?
Why not? Structs are almost never reference types (since that would mean only holding a single pointer or class reference), but they're often pseudo-reference types. Something as simple as having a dynamic array or string in your struct means that you can't get a mutable copy without duplicating memory (and depending on the types involved, you can't even do that), but such types can often work just fine with const copies, because the data that's shared between objects is const or immutable.
> And what do you do with normal assignment with such a type?
You can't assign to variables which are const or immutable, so assignment won't work. But plenty of generic code is written which never assigns to a variable and works just fine with const objects being copied.
> ```d
> immutable MyNonCopyableType x;
> MyNonCopyableType y = x; // compile error?
> ```
If you can't get a mutable copy of MyNonCopyableType, then yes, you'll get a compiler error.
> Also, if you need no writeable copy in your template, why don't
> you declare it with const parameter?
> Maybe you need this behaviour often and so don't want to type the
> 7 extra characters. But I think, it is a bad default to create
> const copies of const objects, and to make it worse the language
> does not allow you to change this default (would need a new
> keyword "mutable" to explicitly request to get a mutable copy).
In D, const is pretty much cancer, honestly. You do _not_ want to use it for generic code if you can possible avoid it, because many, many types do not work with it at all. Unlike C++'s const, it's transitive and has no backdoors (at least not without violating the type system), making it very easy to end up in situations where even when a type can be logically const (or which could be const in C++), it cannot be const in D, because D's const is simply too restrictive. When something is const in D, it means it. There are types when those guarantees are useful, but often, the end result is that you're better off avoding const with any user-defined types, because const is simply too restrictive. And if a type has any indirections in it, the odds are extremely low that it's going to be possible to get a mutable copy from a const object.
As such, unless you're dealing with a specific subset of types where you know that const will work (e.g. if you're only dealing with integer types), then you usually don't want to use const at all with templates. Templates will often work with const types just fine when those types are designed to work with const, but if the template require const, then you're going to end up with a lot of types that won't work with that template, because they won't work with const (and in many cases, cannot be made to work with const).
> > It's most definitely not a bug that IFTI (Implicit Function Template Instantiation) instantiates the template with the exact type that it's given. In the general case, that's what you want - particularly since many, many types (probably the vast majority of types) cannot be const or immutable and then result in a mutable copy when they're copied.
>
> If you need this, declare the parameter const.
> The default should be a mutable copy (and a compile error if
> creating a mutable copy is not possible).
If you declare the parameter to be const, then there a lot of types that won't work with the template. D's const is just too restrictive for it to make any sense to use it unless you're specifically restricting your code to a subset of types which are designed to work with it. With generic code, it's usually the case that the decision on whether const should be used needs to be left up to the caller if you want any chance of the code working with a large range of types.
Using const parameters with function templates can work quite well with the primitive types, but with user-defined types, it tends to become untenable quite quickly - especially if you're writing library code that will be used by a whole bunch of people writing types that you don't control.
> > That really only works with very simple types with no indirections.
>
> Sorry, but no. It is possible to write copy constructors that create mutable copies for pretty much any type. It may for bad designed types be not very fast, but if you are able to create a mutable instance of a type, it should also be possible to create a mutable copy.
Given how D's const works, that really isn't true - especially when you're dealing with any types that you don't control the definition of. For there to be any hope of it working in the general case to get a mutable copy of a const object, D structs in general would have to be written in a way that that was true, and copying structs would become far more expensive, because it would require copying memory all over the place. That's simply not how typical D code is written.
And copy constructors are actually fairly rare in D code. They certainly exist, but since structs are passed around by value, and the way that dynamic arrays work makes it _extremely_ common to share data between objects, it's simply not typical to write copy constructors that do stuff like duplicate memory - which is what would often be required to get a mutable copy from a const object.
On top of that, in many cases, getting a mutable copy would give you completely incorrect behavior - e.g. if a range is a range over a container, you need that range to point to the elements in the container, and if the range refers to those elements as const, then getting a mutable copy of those elements would mean that they're no longer necessarily the same elements which are in the container (they might be at the point that the copy is made, but that won't necessarily stay true, whereas it would if the elements aren't copied).
It's also the case that a lot of code simply avoids using const altogether with user-defined types because of how restrictive D's const is. So, even when types have copy constructors, they're often not written with the idea of getting a mutable copy from a const object. It's something that will work with some types to be sure, but for many types, it won't. And while you can certainly consider that a deficiency in common D programming practices, it's in large part the result of how restrictive D's const is. Most anyone who tries to use D's const like you would in C++ eventually stops, because D's const is simply too different from C++'s const for that to work.
And that's especially true with templated code, since if it has to work with a large range of types, types which don't work with const are going to be on the list. On top of that, some common D idioms (e.g. ranges) can't work with const, because they require mutation (a range can have const elements, but the range itself cannot be const). And because there is no requirement that a const object of any of these types be able to be copied to get a mutable copy, and that would often be expensive, most types aren't written that way. So, in general, it just works better to not use const with templated code and leave that decision up to the caller.
While you might find the current situation annoying with the use case that you're dealing with at the moment, ultimately, D templates work with _far_ more types without mucking around with the mutabality of the arguments than they would if the template changed the mutability by default, and most of the time, you really don't want to use const with them anyway, because it's simply not going to work with a lot user-defined types.
- Jonathan M Davis
|