Jump to page: 1 2
Thread overview
Default struct constructor
Jan 30, 2019
Victor Porton
Jan 30, 2019
Stefan Koch
Jan 31, 2019
Jonathan M Davis
Feb 06, 2019
bitwise
Feb 06, 2019
Jonathan M Davis
Feb 07, 2019
bitwise
Feb 08, 2019
Jonathan M Davis
Feb 08, 2019
bitwise
Feb 11, 2019
aliak
Feb 08, 2019
psycha0s
Feb 08, 2019
Victor Porton
Feb 08, 2019
psycha0s
Feb 12, 2019
Timon Gehr
Feb 16, 2019
Dru
Jan 27, 2021
Paul
Jan 27, 2021
Ali Çehreli
Jan 27, 2021
Paul
Jan 28, 2021
Ali Çehreli
Feb 10, 2019
Cym13
Feb 12, 2019
bitwise
January 30, 2019
Why the language specification disables default constructors for structs?

It can be easily worked around:

---
struct Dummy { }

struct C {
    this(Dummy dummy) { }
}
---

But this workaround (using Dummy) is silly.

Isn't it better to allow default constructor and call it for both of the following?

C x;
C y = C();
January 30, 2019
On Wednesday, 30 January 2019 at 12:29:46 UTC, Victor Porton wrote:
> Why the language specification disables default constructors for structs?
>
> It can be easily worked around:
>
> ---
> struct Dummy { }
>
> struct C {
>     this(Dummy dummy) { }
> }
> ---
>
> But this workaround (using Dummy) is silly.
>
> Isn't it better to allow default constructor and call it for both of the following?
>
> C x;
> C y = C();


the reason is that a struct should have a static initialization state.
In Practice this does not always work, but it is a nice enough conecpt.
January 31, 2019
On Wednesday, January 30, 2019 5:29:46 AM MST Victor Porton via Digitalmars- d wrote:
> Why the language specification disables default constructors for structs?
>
> It can be easily worked around:
>
> ---
> struct Dummy { }
>
> struct C {
>      this(Dummy dummy) { }
> }
> ---
>
> But this workaround (using Dummy) is silly.
>
> Isn't it better to allow default constructor and call it for both of the following?
>
> C x;
> C y = C();

One of the core concepts in D is that all types have a default value which is known at compile time, and a number of places in the language rely on that fact (e.g. the way that arrays are initialized depends on blitting the init value of the type into the array). Because structs live directly wherever they're placed rather than requiring references like classes do, default construction for structs wouldn't play well with the semantics of the language. It wouldn't necessarily be impossible to have it, but the language was designed around default initialization and not default construction, and adding default construction into the mix would likely cause some confusing corner cases and generally not work very well.

The fact that D has default initialization is extremely useful (e.g. it makes it impossible to have issues like variables being initialized with garbage unless you specifically tell the compiler to do it with something like initializing with = void;), but the fact that you can't have default constructors for structs is at times quite annoying. So, it comes at a cost, but the tradeoff is arguably well worth it.

In general, it's advised to simply not declar structs that require that they be default constructed rather than default initialized, but you can declare

@disable this();

to disable default construction and then use a factory method to construct the type instead of using a constructor directly. However, this does have the downside that you can't use the struct anywhere that default initialization is required (e.g. in an array), and an init value still exists for the type and can be assigned to it (which functions like std.algarothim's move will do). So, if the type can't handle being assigned its init value, you may still have problems in some cases. As such, disabling default initialization really isn't something that should be done unless you really need to, but like = void;, the language does provide it for cases where it really is needed, and the programmer is careful.

- Jonathan M Davis



February 06, 2019
On Thursday, 31 January 2019 at 13:57:41 UTC, Jonathan M Davis wrote:
>
> It wouldn't necessarily be impossible to have it, but the language was designed around default initialization and not default construction, and adding default construction into the mix would likely cause some confusing corner cases and generally not work very well.

If I can do this:

struct A {
    int n;
}

A a = void;

Maybe I should be able to do this too:

struct S {
    this(void) { ... }   // <--- make more explicit by adding void
}

S s = S();
February 06, 2019
On Wednesday, February 6, 2019 7:49:28 AM MST bitwise via Digitalmars-d wrote:
> On Thursday, 31 January 2019 at 13:57:41 UTC, Jonathan M Davis
>
> wrote:
> > It wouldn't necessarily be impossible to have it, but the language was designed around default initialization and not default construction, and adding default construction into the mix would likely cause some confusing corner cases and generally not work very well.
>
> If I can do this:
>
> struct A {
>      int n;
> }
>
> A a = void;
>
> Maybe I should be able to do this too:
>
> struct S {
>      this(void) { ... }   // <--- make more explicit by adding void
> }
>
> S s = S();

If that's what you want, then you can just use a static opCall. e.g.

struct S
{
    static S opCall()
    {
        ...
    }
}

auto s = S();

The only real restriction is that you can't declare any constructors if you have any static opCall functions declared.

- Jonathan M Davis



February 07, 2019
On Wednesday, 6 February 2019 at 23:17:31 UTC, Jonathan M Davis wrote:
> On Wednesday, February 6, 2019 7:49:28 AM MST bitwise via Digitalmars-d wrote:
>> [...]
>
> If that's what you want, then you can just use a static opCall. e.g.
>
> struct S {
>     static S opCall() { ... }
> }
> auto s = S();
>
> The only real restriction is that you can't declare any constructors if you have any static opCall functions declared.
>
> - Jonathan M Davis

Sorry - my example was pretty bad.

I'm suggesting that this(void) be a dynamic alternative to T.init.

Current:

struct S { int n; }
S s = void;
s.n = 1;

Suggested:

struct S { int n; this(void){ n = 1; } }
S s;   // this(void) invoked here and T.init not used
February 08, 2019
On Thursday, February 7, 2019 8:02:09 AM MST bitwise via Digitalmars-d wrote:
> On Wednesday, 6 February 2019 at 23:17:31 UTC, Jonathan M Davis
>
> wrote:
> > On Wednesday, February 6, 2019 7:49:28 AM MST bitwise via
> >
> > Digitalmars-d wrote:
> >> [...]
> >
> > If that's what you want, then you can just use a static opCall. e.g.
> >
> > struct S {
> >
> >     static S opCall() { ... }
> >
> > }
> > auto s = S();
> >
> > The only real restriction is that you can't declare any constructors if you have any static opCall functions declared.
> >
> > - Jonathan M Davis
>
> Sorry - my example was pretty bad.
>
> I'm suggesting that this(void) be a dynamic alternative to T.init.
>
> Current:
>
> struct S { int n; }
> S s = void;
> s.n = 1;
>
> Suggested:
>
> struct S { int n; this(void){ n = 1; } }
> S s;   // this(void) invoked here and T.init not used

So, basically, you want default constructors but are suggesting that there be an explicit void parameter in the declaration to indicate that that's what you're doing rather than just having it not have any parameters. Well, that runs into all of the problems related to the fact that D was designed around the idea that everything is default-initialized. It's not an impossible hurdle, but it would be a significant shift in how the language works, and it would mean that stuff like how arrays are initialized would change drastically depending on the types involved, whereas right now, you need default initialization, or you simply can't use the type in an array, and the way that arrays are initialized is the same for all types. It also would be a problem from the standpoint of code clarity. With something like

S s = void;

you can clearly see that the programmer has explicitly requested that the variable not be properly initialized, whereas with your suggested default constructors, suddenly you would no longer know what

S s;

did unless you looked at the type. In the vast majority of cases, it would be default-iniatialized, but occasionally, it would be default constructed, and that could cause quite a bit of confusion.

It also still doesn't get around the fact that _all_ types in D have an init value, and someone could use it (e.g. std.algorithm's move function uses it to give the old variable a valid value rather than garbage after the move). Even types which have disabled default initialization have init values. And it would be very disruptive to the language to try to change that if it's really possible it all (e.g. objects are always initialized to their init value before any constructors are run, so the way that constructors work in D relies on there being an init value for the type). So, with how D is designed, you really can't rely on a constructor having been run for a struct object. It can be made much harder to not have called a constructor (e.g. by using @disable this();), but as long as the init value exists, there's a way around the constructor. So, fundamentally, I really don't see how default constructors for structs can fully work in D. At best, it could be made to work in the common cases and even that's only possible by complicating how a number of core things work so that they do different things depending on whether a struct is default constructed or default initialized, which would definitely complicate the language.

Honestly, I really think that trying to make default constructors work in D is a mistake. Default initialization and init are just too baked into the language. Even trying to create a type with @disable this(); is pretty questionable. It's useful in rare situations, but it has to be handled with care - particularly since the type's init value still exists, and some code will use it.

It's true that there are use cases where the lack of a default constructor for structs is annoying, but in general, all you have to do is use a factory method instead and then make sure that the type still works if its init value is used (e.g. make sure that the destructor is aware that the factory method can be bypassed). And if you absolutely must have default construction, you can always use a class instead.

- Jonathan M Davis



February 08, 2019
On Friday, 8 February 2019 at 08:25:18 UTC, Jonathan M Davis wrote:
> [...]
>
> So, basically, you want default constructors but are suggesting that there be an explicit void parameter in the declaration to indicate that that's what you're doing rather than just having it not have any parameters.

Yeah, that *was* the general idea.

> Well, that runs into all of the problems related to the fact that D was designed around the idea that everything is default-initialized. It's not an impossible hurdle, but it would be a significant shift in how the language works, and it would mean that stuff like how arrays are initialized would change drastically depending on the types involved whereas right now, you need default initialization, or you simply can't use the type in an array,
> and the way that arrays are initialized is the same for all types. It also would be a problem from the standpoint of code clarity. With something like
>
> S s = void;
>
> you can clearly see that the programmer has explicitly requested that the variable not be properly initialized, whereas with your suggested default constructors, suddenly you would no longer know what
>
> S s;
>
> did unless you looked at the type. In the vast majority of cases, it would be default-iniatialized, but occasionally, it would be default constructed, and that could cause quite a bit of confusion.
>
> It also still doesn't get around the fact that _all_ types in D have an init value, and someone could use it (e.g. std.algorithm's move function uses it to give the old variable a valid value rather than garbage after the move). Even types which have disabled default initialization have init values.
>
> And it would be very disruptive to the language to try to change that if it's really possible at all (e.g. objects are always initialized to their init value before any constructors are run, so the way that constructors work in D relies on there being an init value for the type). So, with how D is designed, you really can't rely on a constructor having been run for a struct object. It can be made much harder to not have called a constructor (e.g. by using @disable this();), but as long as the init value exists, there's a way around the constructor.

This is a good point, but I think it's a workable problem. I actually wouldn't be opposed to having T.init blitted over my struct before my dynamic constructor was called. My concern here is more functional than performance-based. As long as my struct had the correct values by the beginning of it's lifetime, that would be fine.

So maybe this(auto) could work ;)

> So, fundamentally, I really don't see how default constructors for structs can fully work in D. At best, it could be made to work in the common cases and even that's only possible by complicating how a number of core things work so that they do different things depending on whether a struct is default constructed or default initialized, which would definitely complicate the language.

My (revised) concern is only that my struct have the opportunity to change it's values right before the start of it's lifetime, but without adding boiler-plate code all over the place. Essentially, I guess I'm asking for a  post-init constructor, similar to how a post-blit would work.

It has also been very annoying not to be able to have dynamic class/struct field initializers, and this would also be fixed by some kind of post-init constructor method.

> It's useful in rare situations, but it has to be handled with care - particularly since the type's init value still exists, and some code will use it.

I have to strongly disagree with this. I haven't touched D in nearly a year because of these types of problems. I'm still waiting to see how the memory/resource situation shapes up with the addition of scope and possible RC classes.

> It's true that there are use cases where the lack of a default constructor for structs is annoying, but in general, all you have to do is use a factory method instead and then make sure that the type still works if its init value is used (e.g. make sure that the destructor is aware that the factory method can be bypassed). And if you absolutely must have default construction, you can always use a class instead.

I think my comment above about wanting structs to have the appropriate values _before_ their lifetime starts applies here too.

A factory method requires explicit runtime invocation and is no different than a static opCall, unary dummy constructor, or having to add initialization boilerplate.


Thanks for the detailed response,
   Bit

February 08, 2019
What kind of problems a default struct constructor could solve? If you want to use RAII, you need to pass the resource as an argument. If you want to set some initial state, you can do it by assigning values to the struct members directly.
February 08, 2019
On Friday, 8 February 2019 at 19:20:59 UTC, psycha0s wrote:
> What kind of problems a default struct constructor could solve? If you want to use RAII, you need to pass the resource as an argument. If you want to set some initial state, you can do it by assigning values to the struct members directly.

In my particular case I need RAII initializing a C library object. Some of the C constructor functions don't have arguments. So the only way to do it is my silly hack with Dummy object. I would probably vote for a change in D spec.
« First   ‹ Prev
1 2