copy must be const?!?

Jonathan M Davis newsgroup.d at jmdavisprog.com
Fri Jul 26 10:04:45 UTC 2024


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





More information about the Digitalmars-d-learn mailing list