Initializing an Immutable Field with Magic: The "Fake Placement New" Technique
FeepingCreature
feepingcreature at gmail.com
Fri Jul 26 10:11:10 UTC 2019
How would you initialize an immutable field outside the
constructor?
For instance, assume you're trying to implement a tagged union,
and you want to switch it to a new type - but it so happens that
the type you're trying to switch to is an immutable struct.
...
immutable struct S { int i; }
union
{
...
S s;
}
For instance, you might, like me, decide that std.conv.emplace
does what you want:
...
emplace(&s, S(5));
...
You would then get a strange compiler error that the return type
of a "side effect free" function cannot be silently thrown out;
and if you changed the call to, as DMD recommends, `cast(void)
emplace(&s, S(5));`, you would discover with some astonishment
that the call is silently removed.
Emplace does not emplace!
What's happening here? From the perspective of the compiler, it
makes perfect sense.
Emplace is defined as a pure function, meaning that it cannot
have any effects other than on its parameters. However, its
parameters are, in order, an immutable struct ("can't" change the
caller - it's immutable) and a value parameter, S, which also
can't change the caller. And we told DMD to throw away the return
value.
So DMD ends up, rather reasonably, convinced that this emplace is
a no-op. We would need to convince DMD of something like, "it
looks like I'm giving it an S*, but it's actually a ~magical
type~ with the same layout as S but not immutable". This is not
possible.
Instead, we have to use the same magic hack that emplace also
uses internally: fake placement new!
See, there is *exactly one* construct in the language that is
allowed to assign a new value to an immutable field, and it's the
constructor. So we have to make DMD believe that our variable is
a field in a type we control (hack 1), and then explicitly call
that type's constructor with our new value (hack 2).
struct Wrapper
{
S s;
this(S s)
{
this.s = s; // the one operation allowed to assign immutable
fields
}
}
`Wrapper` has the same layout as `S`, because it basically *is*
`S`.
Then we call the constructor as if we were currently constructing
Wrapper at a location that "just happens" to overlap with our
field.
Wrapper* wrapper = cast(Wrapper*) &s;
wrapper.__ctor(S(5)); // fake placement new
What a mess. Works though.
Demo: https://run.dlang.io/is/kg7j3f
More information about the Digitalmars-d
mailing list