copy must be const?!?

Quirin Schroll qs.il.paperinik at gmail.com
Thu Jul 25 22:22:19 UTC 2024


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 
> Digitalmars-d-learn wrote:
>> > And no, in general, you don't want to be casting away const 
>> > or immutable. There are cases where it can work (e.g. if the 
>> > cast does a copy, which it would with an integer type)
>>
>> But a parameter given by value is ALWAYS a copy.
>> So if a copy is done, the copy should be mutable. I can't see 
>> why
>> that should ever be a problem. The compiler should never create
>> new values const or immutable unless explicitly advised to do 
>> so.
>
> 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.
>
>> E.g.
>> ```d
>> void fun(myType x) { }
>> ```
>>
>> called with immutable object should create a mutable x, because
>>
>> ```d
>>    myType x = immutableObject;
>> ```
>>
>> will also create a mutable x. If I want an immutable x, i can 
>> do
>>
>> ```d
>>    immutable myType = immutableObject;
>> ```
>>
>> ```d
>> void fun(T)(T x) if (is(Unqual!T==myType)) { }
>> ```
>>
>> should also create a mutable x, for the same reason, because 
>> if I want an immutable x, I can do
>>
>>
>> ```d
>> void fun(T)(immutable(T) x) if (is(Unqual!T==myType)) { }
>> ```
>>
>> Therefore I consider the current behaviour a bug.
>
> 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. That really only 
> works with very simple types with no indirections. In the 
> general case, there is no expectation whatsoever that it's 
> going to be possible to get a mutable copy from a const or 
> immutable variable, and generic code that expects that to work 
> is very likely to run into problems very quickly.
>
> Now, the fact that code such as
> ```d
> void fun(T : const U, U)(U x)
>     if(is(immutable T == immutable U))
> {
>     ...
> }
> ```
> or code like
> ```d
> void fun(T)(Unqual!T x)
> {
> }
> ```
> doesn't work with IFTI and requires explicit instantation is 
> definitely a deficiency in IFTI's current capabilities, but in 
> the general case, we very much want
> ```d
> void fun(T)(T x) {}
> ```
> to be instantiated as `fun!(const Foo)` if it's passed a `const 
> Foo`.
>
> For some types, we _would_ like the ability to tell the 
> compiler to implicitly instantiate function templates with a 
> tail-const version similar to what we get with dynamic arrays, 
> but that really only makes sense for certain types such as 
> ranges.
>
> 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.

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.

C++ solves this by binding values by universal reference:
```cpp
template<typename T>
void f(T&&);
```
This `f` eats anything. For rvalues of type `X`, `T` is inferred 
`X`, the rvalue is materialized in the caller and passed by 
(rvalue) reference to `f`. For lvalues, `T` is inferred `X&` so 
that the parameter becomes of type `(T&)&&` which is the same as 
`T&`, thus `f` ends up binding the value by (lvalue) reference.

D doesn’t have that. In that regard, D is approximately where C++ 
was before C++11. D’s `ref` can’t bind lvalues, which is probably 
a good thing. In one of my recent DIP Ideas posts, I outlined 
`@universal ref` and `@rvalue ref`.

The best D can do today is this:
```d
void f(T)(auto ref T);
```
For lvalues, that’s great. Nothing to complain. Rvalues should be 
moved into the parameter. For that, the type must support moves, 
which most types do, but the compiler doesn’t check if the move 
could give you a mutable object. Even if it can, the compiler 
will give you a qualified parameter.

Because of D’s transitive qualifiers, a mutable copy may not be 
possible. In that case, we’d have two options: Say the type isn’t 
copyable, or say “here’s your ‘copy,’ but it’s const.” However, 
copying a type for which there’s a copy constructor that can give 
you a mutable object is a no-no.


More information about the Digitalmars-d-learn mailing list