On Thursday, 31 October 2013 at 19:39:44 UTC, Jonathan M Davis
wrote:
const and postblit fundamentally don't mix, because for it to
work, you have
to violate the type system. With postblits, the struct gets
memcpied and then
the postblit constructor has the chance to mutate the resulting
object to make
the pieces different that need to be different. However, to do
that mutation
when the object is const would mean mutating a const object
which violates the
type system. What we really need is copy constructors, but they
haven't been
added yet. At one point, Andrei was saying that he and Walter
had a solution,
but he didn't elaborate on it. I assume that it involved
introducing copy
constructors, but I don't know, and this issue has not yet been
resolved.
This makes sense, but I think we could have a simpler solution
that does not involve a copy constructor addition. It seems the
problem is postblit offers a hole in the system whereby
developers could change immutable data. Because of that postblit
and const don't mix. The simple fix would be take the
implementation of postblit away from the developer. It seems the
use case for postblit is really only to provide deep copy
semantics by having developer throw in a bunch of dups where
desired.
What if the language just provided standard postblit
functionality not defined by the user. However it is specified,
it should be easy to implement.
struct S {
// blit and no postblit dup -> sharing
int[] y;
// blit with request for postblit dup -> no sharing
@postblit int[] x;
}
Ideally, [].dup would be made pure. Once that is done, generic
field by field deep copy could be done and be made pure, thus
returning optionally immutable data. While it is true the
generated postblit will be mutating the incipient instance - that
is fine as it is called in a construction context and dup is
surely idempotent. Why make the developer write a function for
simple copy?
Now, in your particular code example, you don't define postblit
constructor,
but my guess would be that RateCurve defines one, making it so
that a postblit
constructor is generated for T which uses the one for
RateCurve, and the S
gets one which uses T's. And because const doesn't work with
postblit
constructors, S becomes uncopyable, and if there's any code
that requires that
a copy be made, then it'll fail to compile (I'm not sure
whether the fact that
it's uncopyable will result in an error if no attempts to copy
it are made,
but you'll definitely get an error if an attempt to copy is
made).
Your guesses are correct.
Hopefully, this problem will be resolved, but regardless of
that, I would
advise against ever having a const member variable in a struct.
Even if you
don't have any postblit issues, such a struct can never be
assigned to, and it
becomes essentially unusuable in any situation where you would
have to assign
a value to it (e.g. if it were in an array). You can certainly
make a struct's
member variable const if you want to, but you're going to run
into stuff that
won't work as a result. It's far better to just provide a
property for it
which is const (probably returning the member by const ref if
you want to
avoid copying it when using the property) rather than making
the member
variable itself const.
- Jonathan M Davis
I am now convinced avoiding `const(T) t` as a member is wise
advice. The suggestion of leaving it non-const and providing a
const accessor is good - but it won't prevent module code from
modifying it. I really want to make sure it is not being mutated.
So I'm now leaning toward this approach:
struct S {
const(T) *rc;
}
so I won't have the copy/postblit issues. Doesn't this bypass the
problems with `const(T) t`. The new risk is that somehow that
member variable is initialized with a stack variable.
Thanks,
Dan