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.