When D feels unfinished: union initialization and NRVO

Mathias Lang pro.mathias.lang at gmail.com
Thu Mar 19 10:17:20 UTC 2020


On Wednesday, 18 March 2020 at 19:09:12 UTC, Jacob Carlborg wrote:
> On 2020-03-18 07:55, Mathias Lang wrote:
>
>> This set of requirement led me to a few simple observations:
>> - I cannot use a temporary and `cast`. Aside from the fact 
>> that most casts are an admission that the type system is 
>> insufficient, it would force me to pass the type by `ref` when 
>> composing, which would expose the `cast` to user code, hence 
>> not `@safe`;
>
> How would the `cast` be exposed to the user code?

Since there is a way to hook into the deserialization, if I 
create a temporary variable which contains an elaborate type 
(e.g. which defines `opAssign` / postblit, etc..), it would get 
called at least once, while users usually expect construction / 
deserialization to be "in one go".
In order to avoid it being called, I did explore making 
`deserialize` a method of the aggregate / take a `ref` to the 
place it should write into, but then we run into other problems. 
If it's a method contracts / invariants are called, and if it 
takes a `ref`, you don't know what the hook will do with the 
already-deserialized data that you just aliased to mutable.

>> But is it really a problem ? Can't we just do the following:
>> ```
>> QT ret = { type: type, _confirm_t: deserialize!_confirm_t(dg, 
>> opts) };
>> return ret;
>> ```
>> Well no, because then, NRVO is not performed anymore.
>
> I haven't looked at the generated code, but this compiles at 
> least:
>
> struct QT
> {
>     int type;
>     int _confirm_t;
>
>     @disable this(this);
>     @disable ref QT opAssign () (auto ref QT other);
> }
>
> QT foo()
> {
>     QT ret = { type: 1, _confirm_t: 2 };
>     ret.type = 4;
>     return ret;
> }
>
> void main()
> {
>     auto qt = foo();
> }
>
> As long as you return the same variable in all branches it 
> compiles at least. If you start to return a literal in one 
> branch and a variable in a different branch it will fail to 
> compile.

Ah, thanks! So this is consistent with what C++ does as well. DMD 
is just being a bit conservative here, but as often, the solution 
is to turn a runtime parameter into a compile time one and to add 
another level of indirection.

This is how I solved the problem: 
https://gist.github.com/Geod24/61ef0d8c57c3916cd3dd7611eac8234e#file-nrvo_switch-d

If you turn the `version(none)` into `version(all)` you'll see:
```
nrvo.d(54): Error: struct nrvo.Foo is not copyable because it is 
annotated with @disable
nrvo.d(57): Error: struct nrvo.Foo is not copyable because it is 
annotated with @disable
nrvo.d(60): Error: struct nrvo.Foo is not copyable because it is 
annotated with @disable
nrvo.d(63): Error: struct nrvo.Foo is not copyable because it is 
annotated with @disable
```

I guess I could raise an issue for this (it's a frontend issue so 
LDC and GDC also suffer from it).


More information about the Digitalmars-d mailing list