www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Getting the default value of a class member field

reply WebFreak001 <d.forum webfreak.org> writes:
I've got this class definition:

```d
class X
{
     this()
     {
         assert(false);
     }

     int x = 3;
}
```

due to internal reasons the constructor would fail at compile 
time, so I put in an assert(false) here, and I can't add or 
change any methods in X.

How do I get `3` if I have `X` and field name `"x"` at compile 
time?

For structs `X.init.x` / `__traits(getMember, X.init, "x")` would 
work, however for classes it complains about null dereference.

I saw there is __traits(initSymbol), however that one doesn't 
work at compile time.
Dec 01 2022
parent reply kinke <noone nowhere.com> writes:
On Thursday, 1 December 2022 at 08:09:05 UTC, WebFreak001 wrote:
 I've got this class definition:

 ```d
 class X
 {
     this()
     {
         assert(false);
     }

     int x = 3;
 }
 ```

 due to internal reasons the constructor would fail at compile 
 time, so I put in an assert(false) here, and I can't add or 
 change any methods in X.

 How do I get `3` if I have `X` and field name `"x"` at compile 
 time?

 For structs `X.init.x` / `__traits(getMember, X.init, "x")` 
 would work, however for classes it complains about null 
 dereference.

 I saw there is __traits(initSymbol), however that one doesn't 
 work at compile time.
AFAIK, there is no way. Unlike a struct's init symbol, a class' one doesn't necessarily represent a valid instance state - it's just the raw payload before invoking a ctor (which constructs a valid instance), and the state 'dead' memory is reset to after finalizing an object instance (to prevent dead pointers keeping other GC refs alive). If the ctor worked at CTFE, one could use: ```d int get() { scope x = new X; return x.x; } enum bla = get(); ``` to get the `x` value of a *valid* instance, which might be different than the static initializer (if modified in the ctor). I guess the main question is why do you require the static initializers of these fields at compile-time. `__traits(initSymbol)` was added to aid in manual blitting at runtime.
Dec 01 2022
parent reply WebFreak001 <d.forum webfreak.org> writes:
On Thursday, 1 December 2022 at 23:02:31 UTC, kinke wrote:
 On Thursday, 1 December 2022 at 08:09:05 UTC, WebFreak001 wrote:
 [...]

 AFAIK, there is no way. Unlike a struct's init symbol, a class' 
 one doesn't necessarily represent a valid instance state - it's 
 just the raw payload before invoking a ctor (which constructs a 
 valid instance), and the state 'dead' memory is reset to after 
 finalizing an object instance (to prevent dead pointers keeping 
 other GC refs alive).

 If the ctor worked at CTFE, one could use:
 ```d
 int get() {
     scope x = new X;
     return x.x;
 }
 enum bla = get();
 ```
 to get the `x` value of a *valid* instance, which might be 
 different than the static initializer (if modified in the ctor).

 I guess the main question is why do you require the static 
 initializers of these fields at compile-time. 
 `__traits(initSymbol)` was added to aid in manual blitting at 
 runtime.
I want to use the static initializers (when used with an UDA) as default values inside my SQL database. See https://github.com/rorm-orm/dorm/blob/a86c7856e71bbc18cd50a7a6f701c325a4746518/source/dorm/declarative/conversion.d#L959 With my current design it's not really possible to move it out of compile time to runtime because the type description I create there gets serialized and output for use in another program (the migrator). Right now it's simply taking the compile time struct I generate and just dumping it without modification into a JSON serializer. I might be abusing classes a little bit here, but they provide the easiest way to do a variety of things: - `is(T : Model)` and especially type specialization like `void foo(T : Model)(T x)` is much easier to write and use - it basically allows me to inject methods into my type (using template parameter `this This` I can even get my real type) - it's the most easy and pretty to type for the user. A struct with `mixin Model` would be quite verbose imo and doesn't allow Model to define custom fields easily, because they would break the implicitly generated constructor
Dec 01 2022
parent reply kinke <noone nowhere.com> writes:
On Friday, 2 December 2022 at 00:24:44 UTC, WebFreak001 wrote:
 I want to use the static initializers (when used with an UDA) 
 as default values inside my SQL database.

 See 
 https://github.com/rorm-orm/dorm/blob/a86c7856e71bbc18cd50a7a6f701c325a4746518/source/dorm/declarative/conversion.d#L959

 With my current design it's not really possible to move it out 
 of compile time to runtime because the type description I 
 create there gets serialized and output for use in another 
 program (the migrator). Right now it's simply taking the 
 compile time struct I generate and just dumping it without 
 modification into a JSON serializer.

 [...]
Okay, so what's blocking CTFE construction of these models? AFAICT, you have a templated base constructor in `Model`, which runs an optional ` constructValue!(() => Clock.currTime + 4.hours)` lambda UDA for all fields of the derived type. Can't you replace all of that with a default ctor in the derived type? ``` class MyModel : Model { int x = 123; // statically initialized SysTime validUntil; // dynamically initialized in ctor this() { validUntil = Clock.currTime + 4.hours; } } ``` Such an instance should be CTFE-constructible, and the valid instance would feature the expected value for the `validUntil` field. If you need to know about such dynamically generated fields (as e.g. here in this time-critical example), an option would be a ` dynamicallyInitialized` UDA. Then if you additionally need to be able to re-run these current ` constructValue` lambdas for an already constructed instance, you could probably go with creating a fresh new instance and copying over the fresh new field values.
Dec 01 2022
next sibling parent reply kinke <noone nowhere.com> writes:
On Friday, 2 December 2022 at 04:14:37 UTC, kinke wrote:
 [...]
 Such an instance should be CTFE-constructible, and the valid 
 instance would feature the expected value for the `validUntil` 
 field. [...]
Oh well, that time-sensitive example clearly isn't CTFE-able. :D - If that's the primary use case for these ` constructValue` UDAs, then yeah, this makes things more complicated.
Dec 01 2022
parent kinke <noone nowhere.com> writes:
You could potentially skip running the ` constructValue` lambdas 
in the ctor via `if (__ctfe)`, if skipping this extra init is 
acceptable for CTFE instances.
Dec 01 2022
prev sibling parent WebFreak001 <d.forum webfreak.org> writes:
On Friday, 2 December 2022 at 04:14:37 UTC, kinke wrote:
 On Friday, 2 December 2022 at 00:24:44 UTC, WebFreak001 wrote:
 I want to use the static initializers (when used with an UDA) 
 as default values inside my SQL database.

 See 
 https://github.com/rorm-orm/dorm/blob/a86c7856e71bbc18cd50a7a6f701c325a4746518/source/dorm/declarative/conversion.d#L959

 With my current design it's not really possible to move it out 
 of compile time to runtime because the type description I 
 create there gets serialized and output for use in another 
 program (the migrator). Right now it's simply taking the 
 compile time struct I generate and just dumping it without 
 modification into a JSON serializer.

 [...]
Okay, so what's blocking CTFE construction of these models? AFAICT, you have a templated base constructor in `Model`, which runs an optional ` constructValue!(() => Clock.currTime + 4.hours)` lambda UDA for all fields of the derived type. Can't you replace all of that with a default ctor in the derived type? ``` class MyModel : Model { int x = 123; // statically initialized SysTime validUntil; // dynamically initialized in ctor this() { validUntil = Clock.currTime + 4.hours; } } ``` Such an instance should be CTFE-constructible, and the valid instance would feature the expected value for the `validUntil` field. If you need to know about such dynamically generated fields (as e.g. here in this time-critical example), an option would be a ` dynamicallyInitialized` UDA. Then if you additionally need to be able to re-run these current ` constructValue` lambdas for an already constructed instance, you could probably go with creating a fresh new instance and copying over the fresh new field values.
constructValue is entirely different than this default value. It's not being put into the database, it's just for the library to send it when it's missing. (so other apps accessing the database can't use the same info) - It's also still an open question if it even gives any value because it isn't part of the DB. To support constructValues I iterate over all DB fields and run their constructors. I implemented listing the fields with a ListFields!T template. However now when I want to generate the DB field information I also use this same template to list all columns to generate attributes, such as what default value to put into SQL. Problem here is that that tries to call the constructor, which wants to iterate over the fields, while the fields are still being iterated. (or something similar to this) Basically in the end the compiler complained about forward reference / the size of the fields not being known when I put in a field of a template type that would try to use the same ListFields template on the class I put that value in. Right now I hack around this by adding an `int cacheHack` template parameter to ListFields, which simply does nothing. However this fixes that the compiler thinks the template isn't usable and everything seems to work with this. Anyway this is all completely different from the default value thing, because I already found workarounds and changed some internals a bit to support things like cyclic data structures. I would still like a way to access the initializer from class fields, and it would be especially cool would be to know if they are explicitly set. Right now I have this weird and heavy ` defaultValue(...)` annotation that's basically the same as `= ...;`, that I just needed to add to make it possible to use T.init as default value in the DB as well, but not force it. My code uses ` defaultFromInit` to make it use the initializer, but it would be great if I didn't need this at all. (although because of my cyclic template issues it might break again and be unusable for me)
Dec 02 2022