immutable postblit unusable?

Jonathan M Davis newsgroup.d at jmdavisprog.com
Sun Feb 11 08:25:41 UTC 2018


On Sunday, February 11, 2018 07:50:52 Cauterite via Digitalmars-d wrote:
> https://dpaste.dzfl.pl/80d2a208519c
>
> struct A {
>   this(this) immutable {}
> }
>
> struct B {
>   A a;
> }
>
> // Error: immutable method f18.A.__postblit is not callable using
> a mutable object
>
> -------------------------
>
> I honestly don't know what to make of this.
> I guess the auto-generated postblit of B is throwing this?
>
> Does it even make sense to have an immutable postblit?
> Should such code be explicitly rejected?

Basically, as soon as you have a postblit constructor, the type cannot be
const or immutable. Unfortunately, as nice as the idea for postblit
constructors is in comparison to copy constructors, it fundamentally doesn't
work if the members aren't mutable (since for the postblit constructor to
work, you have to mutate the result). Occasionally, there has been talk of
adding copy constructors to solve the problem with const, but it's never
happened.

In the case of immutable, the postblit is actually completely unnecessary,
because there's no need to do a deep copy of an immutable object, since it's
guaranteed to never change its value (whereas with const, a deep copy can
still be necessary, since other references to that data may be mutable and
could thus change the data).

Now, as to what you've done. You've marked the postblit constructor as
immutable. That means that it's only callable when the object is immutable,
and A isn't immutable, so B's implicit postblit constructor can't work
properly. And declaring an explicit postblit constructor doesn't help. The
reason for that is that any type with an explicit postblit constructor
really has two posblit constructors. This code

struct A
{
}
pragma(msg, __traits(allMembers, A));

struct B
{
    this(this) {}
}
pragma(msg, __traits(allMembers, B));

struct C
{
    B c;
}
pragma(msg, __traits(allMembers, C));

prints this

tuple()
tuple("__postblit", "__xpostblit", "opAssign")
tuple("b", "__xpostblit", "opAssign")

A doesn't have an explicit postblit constructor, and it doesn't have any
member variables with a postblit constructor. So, it has no postblit
constructors. On the other hand, B has an explicit postblit constructor.
That's the __postblit in the members, and __xpostblit is the implicit
postblit constructor. C then has no explicit postblit constructor, so it has
no __postblit member, but because it has a member variable with a postblit
constructor, it has a __xpostblit member.

As I understand it, __xpostblit calls the postblit constructors for each
member that needs it and then calls __postblit. So, __xpostblit is
essentially the actual postblit constructor that deals correctly with the
entire type and all its members recursively, and __postblit is the
explicitly declared one that just deals with the type itself.

And with what you've done, you've declared __postblit to be immutable, but
that has no effect on __xpostblit. So, you get a compilation error about
mutable objects not working with __postblit, since __xpostblit only operates
on mutable objects.

Interestingly, changing your example so that A a; is immutable doesn't
actually help, and it could be argued that that's a bug, but it really
doesn't matter. If you're dealing with immutable, there's zero reason to
have a postblit constructor such that it arguably should be an error to
declare a postblit constructor as immutable, and the nature of a postblit
constructor fundamentally only works with mutable objects, so arguably, it
should be illegal to mark a postblit constructor with either const or
immutable.

But if you just give up on the postblit constructor, immutable will work
just fine. It's const that's screwed, and unless there's a language
improvement (most likely involving some kind of copy constructor), const and
objects with postblit constructors just fundamentally aren't going to work
together.

- Jonathan M Davis



More information about the Digitalmars-d mailing list