July 26

On Thursday, 25 July 2024 at 22:22:19 UTC, Quirin Schroll wrote:

>

On Thursday, 25 July 2024 at 13:07:03 UTC, Jonathan M Davis wrote:

>

But either way, it would cause quite a few problems if IFTI started instantiating templates in general with mutable instead of the actual type that it's given, because it's extremely common that const and immutable types cannot be converted to mutable.

It’s wild how C++ and D disagree on this one. In C++, a copy constructor must produce a mutable copy. Now, C++ and D disagree on what const means and therefore what counts as a mutable copy. But, and this is the key takeaway, C++ never infers const on a copy. Never. It does infer const on a reference, of course.

sigh.
One more point where C++ made better decisions.

>

Nothing of this is out of reach for D. A type for which const objects can’t be copied to initialize a mutable one simply isn’t copyable and therefore can’t be passed by value. A type for which const rvalues can’t be used to initialize a mutable variable aren’t movable.

+1

>

However, copying a type [with const] for which there’s a copy constructor that can give you a mutable object is a no-no.

That's my point.

July 26
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



July 29

On Friday, 26 July 2024 at 10:04:45 UTC, Jonathan M Davis wrote:

>

On Friday, July 26, 2024 2:17:21 AM MDT Dom DiSc via Digitalmars-d-learn wrote:

>

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?

Because you can't make a mutable copy?

>

Structs are [...] often pseudo-reference types. [...]
such types can often work just fine with const copies, because the data that's shared between objects is const or immutable.

Then declare the template to take a const parameter.

> >
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.

This is why you should get the same error, if the compiler cannot create a mutable parameter-by-value 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,

Why? In a template that guarantees not to modify a parameter by declaring it const, what bad can happen?

>

because many, many types do not work with it at all.

At least reading it should never be a problem. And nothing more one want to do with a const parameter.

But I don't see your problem. I'm taking about situations where a const instance of a type IS ALREADY THERE (which will never be the case with any of your problematic types), and I want to have a mutable copy of them.
So if the compiler would discard the const from parameters that are anyway never const, that's a NOP. All is fine.

On the other hand if there is a const object, that should be an object of a type that indeed CAN provide mutable copies. This is also fine.

So, where is the problem if the compiler drops "const" from the by-value copy of a parameter?

>

in situations where 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.

Sorry, but if a type is "logically const" (like a range) and you read it and thereby change it's internal state, then the template that guaranteed not to modify a parameter is very likely to destroy this internal state, and so you are much better of if this doesn't compile beforehand.

Some templates are simply not meant to work with all kinds of user defined types.

>

If you declare the parameter to be const, then there are a lot of types that won't work with the template.

Fine. If I can change the internal state via read-functions, I don't want to work with that bogous type. If my intention is not to modify an object, I'm perfectly ok that I can't use may of that fake-const types functions.

>

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).

Ranges are a bad example, because they really cannot be const. They are completely unusable in a function not modifying them, because reading them is using them up. So a function taking a range as parameter indeed cannot guarantee not to modify it (at least not if it reads that parameters data at all).

>

It's also the case that a lot of code simply avoids using const altogether with user-defined types

Fine. Then you won't run into the problem I have. And indeed, as you weren't aware of this problem is an indicator that you are not really using const.

>

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.

No, so far the C++ code I converted to D is working fine. And it is heavily using const - but it uses it correct, despite C++ doesn't enforce that. Maybe I never used C++ the way it was meant?!?

>

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.

Nope. Constraints work very good in guaranteeing that such types are NOT on the list of types the template is working with.

>

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).

Jup. If a template takes a range, it should not take it const.
But I also won't take a range by value. As whatever I do will modify the range, why should I pretend to work on a copy of it? Take it by ref!

1 2
Next ›   Last »