August 30

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.

>
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.

struct S {
    this() { writeln("S.this()"); }
}

struct T {
    S s;
    // implicit `@disable this();`
}

Why not implicit:

this() {
   s = S();
}
>
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?

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. 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

August 31
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.
September 01

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...

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

September 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 implicit constructor call, following the same rules as a struct with a disabled default constructor.

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:

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++.

September 05

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.

September 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 @disabled default constructors).

1 2
Next ›   Last »