www.digitalmars.com         C & C++   DMDScript  

digitalmars.dip.ideas - Allow default constructors for structs

reply Ogi <ogion.art gmail.com> writes:
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
next sibling parent FeepingCreature <feepingcreature gmail.com> writes:
Yes please!
Aug 29
prev sibling next sibling parent IchorDev <zxinsworld gmail.com> writes:
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
prev sibling next sibling parent Ogi <ogion.art gmail.com> writes:
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
s/implicit/explicit
Aug 29
prev sibling next sibling parent reply monkyyy <crazymonkyyy gmail.com> writes:
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
parent reply Dukc <ajieskola gmail.com> writes:
monkyyy kirjoitti 30.8.2024 klo 14.11:
 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?
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.
Aug 30
next sibling parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Friday, 30 August 2024 at 12:29:43 UTC, Dukc wrote:
 monkyyy kirjoitti 30.8.2024 klo 14.11:
 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?
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.
.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"
S v = S.init; // ok (may be a logically incorrect object)
Aug 30
next sibling parent Dukc <ajieskola gmail.com> writes:
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
prev sibling parent reply Ogi <ogion.art gmail.com> writes:
On Friday, 30 August 2024 at 12:45:27 UTC, monkyyy wrote:
 but this proposal does not maintain a .init "standard"

S v = S.init; // ok (may be a logically incorrect object)
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.
Aug 30
parent monkyyy <crazymonkyyy gmail.com> writes:
On Friday, 30 August 2024 at 16:17:08 UTC, Ogi wrote:
 On Friday, 30 August 2024 at 12:45:27 UTC, monkyyy wrote:
 but this proposal does not maintain a .init "standard"

S v = S.init; // ok (may be a logically incorrect object)
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.
... no its poorly defined
  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
prev sibling parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 30 August 2024 at 12:29:43 UTC, Dukc wrote:
 monkyyy kirjoitti 30.8.2024 klo 14.11:
 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?
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.
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.
Sep 05
parent Paul Backus <snarwin gmail.com> writes:
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
prev sibling next sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
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
prev sibling next sibling parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
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
prev sibling next sibling parent Salih Dincer <salihdb hotmail.com> writes:
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
prev sibling parent Quirin Schroll <qs.il.paperinik gmail.com> writes:
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