digitalmars.dip.ideas - Allow default constructors for structs
- Ogi (52/52) Aug 29 This has been discussed ad nauseam, so I’m skipping rationale.
- FeepingCreature (1/1) Aug 29 Yes please!
- IchorDev (6/11) Aug 29 Yes, hello. Here I am. I had to manually write C++ constructor
- Ogi (2/4) Aug 29 s/implicit/explicit
- monkyyy (3/7) Aug 30 why? I *just* got bitten by nested structs being hard to init
- Dukc (14/22) Aug 30 There is a reason. D is designed so that every type, except `noreturn`,
- monkyyy (6/31) Aug 30 .init sucks as a standard, I only ever use it as I have nothing
- Dukc (3/10) Aug 30 But if it's really going to break the standard, it needs to answer how
- Ogi (4/6) Aug 30 Using `.init` to initialize a struct with a disabled default
- monkyyy (6/15) Aug 30 It may not be c's "undefined behavior ^tm" but it is undefined;
- Quirin Schroll (10/34) Sep 05 Simple. Burn `init` with fire and give up the idea that every
- Paul Backus (5/10) Sep 05 Perhaps a more moderate approach would be to make accessing
- Steven Schveighoffer (28/75) Aug 30 I dislike the requirement to explicitly construct. I don't see
- Richard (Rikki) Andrew Cattermole (6/6) Aug 30 There is a subset of constructors we certainly can do without analyzing
- Salih Dincer (25/28) Sep 01 I think everything is consistent and works as it should. D gives
- Quirin Schroll (35/57) Sep 05 I’d prefer some modifications:
This has been discussed ad nauseam, so I’m skipping rationale. Let’s just say that many users want this feature, especially those who need C++ interop. Here’s how it can be implemented in a non-problematic way. A struct with a default constructor can only be instantiated by an implicit constructor call, following the same rules as a struct with a disabled default constructor. ```D struct S { this() { writeln("S.this()"); } } void main { S s; // error S t = S(); // ok (constructed by calling `S.this()`) S u = void; // ok (not ` safe`) S v = S.init; // ok (may be a logically incorrect object) S[3] a; // error S[3] b = [S(), S(), S()]; // ok } ``` If any fields of an aggregate type are structs with default constructors, all constructors of this type must initialize them. If a default constructor is not defined, default construction is disabled. ```D struct S { this() { writeln("S.this()"); } } struct T { S s; // implicit ` disable this();` } struct U { S s; this(int x) {} // error: `s` must be initialized this() { s = S(); } } class C { S s; this(int x) {} // error: `s` must be initialized this() { s = S(); } } void main { T t1; // error: default construction is disabled T t2 = T(); // ditto U u = U(); // ok C c = new C(); // ok } ``` Destroy.
Aug 29
On Thursday, 29 August 2024 at 14:28:09 UTC, Ogi wrote:especially those who need C++ interop.Yes, hello. Here I am. I had to manually write C++ constructor mangling to hack in default constructors for C++ interop! It SUCKED!Here’s how it can be implemented in a non-problematic way. A struct with a default constructor can only be instantiated by an implicit constructor call, following the same rules as a struct with a disabled default constructor.Genius. This shouldn’t even need to become a DIP because it’s so genius.
Aug 29
On Thursday, 29 August 2024 at 14:28:09 UTC, Ogi wrote:A struct with a default constructor can only be instantiated by an implicit constructor calls/implicit/explicit
Aug 29
On Thursday, 29 August 2024 at 14:28:09 UTC, Ogi wrote:struct T { S s; // implicit ` disable this();` }why? I *just* got bitten by nested structs being hard to init (with terrible error messages), why not just fix the problem?
Aug 30
monkyyy kirjoitti 30.8.2024 klo 14.11:On Thursday, 29 August 2024 at 14:28:09 UTC, Ogi wrote:There is a reason. D is designed so that every type, except `noreturn`, has an `.init` value. It's supposed to be a value, not a function. This is why a struct cannot have a default constructor. If we break that pattern, then we run into some pretty strange situtions. For example, what would be the `.init` value of a static array of structs with a default constructor? What if the `.init` value of such a struct is used at CTFE? If the struct is a member of another struct, should the default constructor be called only once to determine `.init` value of the parent struct, or every time the parent struct is initialised? However, I agree that structs (and unions) with context pointers are really annoying because they break that pattern of `.init` value for every type. Maybe it should be allowed to initialise nested structs with a null context.struct T { S s; // implicit ` disable this();` }why? I *just* got bitten by nested structs being hard to init (with terrible error messages), why not just fix the problem?
Aug 30
On Friday, 30 August 2024 at 12:29:43 UTC, Dukc wrote:monkyyy kirjoitti 30.8.2024 klo 14.11:.init sucks as a standard, I only ever use it as I have nothing else see my post about .zero (there is **not a standard**, int.init!=float.init, are ranges init even fixable? is nullable.init well defined? is sumtype.init?) but this proposal does not maintain a .init "standard"On Thursday, 29 August 2024 at 14:28:09 UTC, Ogi wrote:There is a reason. D is designed so that every type, except `noreturn`, has an `.init` value. It's supposed to be a value, not a function. This is why a struct cannot have a default constructor. If we break that pattern, then we run into some pretty strange situtions. For example, what would be the `.init` value of a static array of structs with a default constructor? What if the `.init` value of such a struct is used at CTFE? If the struct is a member of another struct, should the default constructor be called only once to determine `.init` value of the parent struct, or every time the parent struct is initialised? However, I agree that structs (and unions) with context pointers are really annoying because they break that pattern of `.init` value for every type. Maybe it should be allowed to initialise nested structs with a null context.struct T { S s; // implicit ` disable this();` }why? I *just* got bitten by nested structs being hard to init (with terrible error messages), why not just fix the problem?S v = S.init; // ok (may be a logically incorrect object)
Aug 30
monkyyy kirjoitti 30.8.2024 klo 15.45:.init sucks as a standard, I only ever use it as I have nothing else see my post about .zero (there is **not a standard**, int.init!=float.init, are ranges init even fixable? is nullable.init well defined? is sumtype.init?) but this proposal does not maintain a .init "standard"But if it's really going to break the standard, it needs to answer how to deal with the resulting complications.
Aug 30
On Friday, 30 August 2024 at 12:45:27 UTC, monkyyy wrote:but this proposal does not maintain a .init "standard"Using `.init` to initialize a struct with a disabled default constructor is [well defined](https://dlang.org/spec/property.html#init-vs-construction) and allowed even in ` safe` code. This proposal doesn’t change anything in this regard.S v = S.init; // ok (may be a logically incorrect object)
Aug 30
On Friday, 30 August 2024 at 16:17:08 UTC, Ogi wrote:On Friday, 30 August 2024 at 12:45:27 UTC, monkyyy wrote:... no its poorly definedbut this proposal does not maintain a .init "standard"Using `.init` to initialize a struct with a disabled default constructor is [well defined](https://dlang.org/spec/property.html#init-vs-construction) and allowed even in ` safe` code. This proposal doesn’t change anything in this regard.S v = S.init; // ok (may be a logically incorrect object)If there is a default constructor for an object, it may produce a different value.It may not be c's "undefined behavior ^tm" but it is undefined; the spec of .init may as well say ".init will exist, and heres a bunch of ways its useless" and my critism is the post above that one
Aug 30
On Friday, 30 August 2024 at 12:29:43 UTC, Dukc wrote:monkyyy kirjoitti 30.8.2024 klo 14.11:Simple. Burn `init` with fire and give up the idea that every type has a default value. It was never true due to `void`, but with `noreturn`, it’s gotten worse. Adding non-nullable types is impossible really with also requiring each type have a default value. It’s limiting the design space. We’ve gotten workarounds like `static opCall` that are frankly worse than the problem. C++ added `static operator()` and guess what, you can’t call it except non-statically.On Thursday, 29 August 2024 at 14:28:09 UTC, Ogi wrote:There is a reason. D is designed so that every type, except `noreturn`, has an `.init` value. It's supposed to be a value, not a function. This is why a struct cannot have a default constructor. If we break that pattern, then we run into some pretty strange situtions. For example, what would be the `.init` value of a static array of structs with a default constructor? What if the `.init` value of such a struct is used at CTFE? If the struct is a member of another struct, should the default constructor be called only once to determine `.init` value of the parent struct, or every time the parent struct is initialised? However, I agree that structs (and unions) with context pointers are really annoying because they break that pattern of `.init` value for every type. Maybe it should be allowed to initialise nested structs with a null context.struct T { S s; // implicit ` disable this();` }why? I *just* got bitten by nested structs being hard to init (with terrible error messages), why not just fix the problem?
Sep 05
On Thursday, 5 September 2024 at 18:32:07 UTC, Quirin Schroll wrote:Simple. Burn `init` with fire and give up the idea that every type has a default value. It was never true due to `void`, but with `noreturn`, it’s gotten worse. Adding non-nullable types is impossible really with also requiring each type have a default value.Perhaps a more moderate approach would be to make accessing `.init` a ` system` operation for types with default constructors (including ` disable`d default constructors).
Sep 05
On Thursday, 29 August 2024 at 14:28:09 UTC, Ogi wrote:This has been discussed ad nauseam, so I’m skipping rationale. Let’s just say that many users want this feature, especially those who need C++ interop. Here’s how it can be implemented in a non-problematic way. A struct with a default constructor can only be instantiated by an [explicit] constructor call, following the same rules as a struct with a disabled default constructor.I dislike the requirement to explicitly construct. I don't see the drawback of implicit construction.```D struct S { this() { writeln("S.this()"); } } void main { S s; // error S t = S(); // ok (constructed by calling `S.this()`) S u = void; // ok (not ` safe`) S v = S.init; // ok (may be a logically incorrect object) S[3] a; // error S[3] b = [S(), S(), S()]; // ok } ```The array example is so bad...If any fields of an aggregate type are structs with default constructors, all constructors of this type must initialize them. If a default constructor is not defined, default construction is disabled. ```D struct S { this() { writeln("S.this()"); } } struct T { S s; // implicit ` disable this();` } ```Why not implicit: ```d this() { s = S(); } ``````D struct U { S s; this(int x) {} // error: `s` must be initialized this() { s = S(); } } class C { S s; this(int x) {} // error: `s` must be initialized this() { s = S(); } } ```Seems fine But what happens in this case? ```d struct S { this(int) {} } S s; // ok? ``` Seems like if you want to *require* constructor calls when they are present, it should be consistent. This is a different approach than what I posted [here](https://forum.dlang.org/post/onsftzijgkpjkpdbbzec forum.dlang.org). I'm approaching it from the side of *hooking default initialization*, whereas you are approaching it from the side of *disallowing default initialization*. Note that with the advent of editions, we can do whatever we want with semantics, and given that default constructors do not currently exist, keeping existing behavior when they are not present seems reasonable to me. -Steve
Aug 30
There is a subset of constructors we certainly can do without analyzing type states and the implications thereof. Specifically, if you have a constructors with only default values, require that it must have at least one named argument into it. Anything that messes with .init, is going to need a lot more work than presented here.
Aug 30
On Thursday, 29 August 2024 at 14:28:09 UTC, Ogi wrote:A struct with a default constructor can only be instantiated by an implicit constructor call, following the same rules as a struct with a disabled default constructor.I think everything is consistent and works as it should. D gives more than C... ```d struct S(T) { T i; this(T value) { i = value; } alias i this; } alias sint = S!int; void main() { sint s1; auto s2 = sint(1); auto sarr= [sint(2)]; // i = 2 sarr ~= s2; // i = 1 sarr ~= s1; // i = 0 assert(sarr == [2, 1, 0]); } ``` SDB 79
Sep 01
On Thursday, 29 August 2024 at 14:28:09 UTC, Ogi wrote:This has been discussed ad nauseam, so I’m skipping rationale. Let’s just say that many users want this feature, especially those who need C++ interop. Here’s how it can be implemented in a non-problematic way. A struct with a default constructor can only be instantiated by an implicit constructor call, following the same rules as a struct with a disabled default constructor. ```D struct S { this() { writeln("S.this()"); } } void main { S s; // error S t = S(); // ok (constructed by calling `S.this()`) S u = void; // ok (not ` safe`) S v = S.init; // ok (may be a logically incorrect object) S[3] a; // error S[3] b = [S(), S(), S()]; // ok } ```I’d prefer some modifications: ```d S s; // same as `S s = S();` S t = S(); // calls nullary constructor S u = void; // not safe, do not use S v1 = S.init; // deprecated; use `default(S)` S v2 = default(S); // not safe; do not use S v3 = default; // same as `S v3 = default(S)` S[3] a; // same as `S[3] = [S(), S(), S()];` S[3] b = [S(), S(), S()]; // calls nullary constructor thrice ``` Essentially, a struct variable goes through the following steps: 1. Memory is allocated for it – this is `= void` initialization 2. The type’s `default` is blitted onto it – this is `= default` initialization or using the newly added `__default(x)` function for an already allocated value `x`. 3. A constructor runs on the default initialized value – this is the usual initialization, or call `x.__ctor(args)` for an already defaulted value `x`. Then the value is ready to use. For some types, might be ready to use earlier, but in general, it’s not. For example: - A `= void` initialized `int` can be used. - A `= void` initialized pointer cannot be used. - A `= default` initialized (nullable) pointer can be used (it’s just a null pointer). - A `= default` initialized non-null reference cannot be used (it’s null, but ought not to be). I really don’t like banning `S s;` for _some_ types. Either require explicit initialization of all variables (normal, `= void;` or `= default;`) or make it do the Right Thing always. Of course, if a type has a disabled nullary constructor, `S s;` is an error, as that’s the purpose of disabling the constructor. Also, folks, terminology is important: nullary constructor is a much better term than default constructor. Don’t be fooled by C++.
Sep 05