Jump to page: 1 2 3
Thread overview
Would love to override default ctor of struct
Jan 19
Dru
Jan 19
Alex
Jan 19
Basile B.
Jan 19
Alex
Jan 21
Alex
Jan 21
Meta
Jan 21
Alex
Jan 22
aliak
Jan 22
Meta
Jan 22
aliak
Jan 22
aliak
Jan 21
Alex
Jan 22
aliak
Jan 22
Dru
Feb 07
aliak
January 19
What is the idea behind why we can't define default ctor for structs?

In current situation I need to "@disable this()"
then define a constructor with dummy arguments  (because my constructor does not need arguments)

It is a big pain on syntax

January 19
On Saturday, 19 January 2019 at 10:05:22 UTC, Dru wrote:
> What is the idea behind why we can't define default ctor for structs?
>
> In current situation I need to "@disable this()"
> then define a constructor with dummy arguments  (because my constructor does not need arguments)
>
> It is a big pain on syntax

If your constructor doesn't need any arguments, what is the reason you can't define default values to your members?
January 19
On Saturday, 19 January 2019 at 10:18:34 UTC, Alex wrote:
> On Saturday, 19 January 2019 at 10:05:22 UTC, Dru wrote:
>> What is the idea behind why we can't define default ctor for structs?
>>
>> In current situation I need to "@disable this()"
>> then define a constructor with dummy arguments  (because my constructor does not need arguments)
>>
>> It is a big pain on syntax
>
> If your constructor doesn't need any arguments, what is the reason you can't define default values to your members?

I see your point but...

1. default member values (via initializer) are more like a shared this().
2. not everything can be set using member initializer because of CTFE limitations.
January 19
On Saturday, 19 January 2019 at 10:22:11 UTC, Basile B. wrote:
> On Saturday, 19 January 2019 at 10:18:34 UTC, Alex wrote:
>> On Saturday, 19 January 2019 at 10:05:22 UTC, Dru wrote:
>>> What is the idea behind why we can't define default ctor for structs?
>>>
>>> In current situation I need to "@disable this()"
>>> then define a constructor with dummy arguments  (because my constructor does not need arguments)
>>>
>>> It is a big pain on syntax
>>
>> If your constructor doesn't need any arguments, what is the reason you can't define default values to your members?
>
> I see your point but...
>
> 1. default member values (via initializer) are more like a shared this().

Ok. And why this is a problem? I mean, if the sharedness is not wanted, then, the thread id should go as a constructor parameter, shouldn't it?

> 2. not everything can be set using member initializer because of CTFE limitations.

Hm... ok. But even then, if something does not work in the way it should because of limitations: What prevents of setting the parameter inside a function after or on creation? Or, using a non-default constructor exactly for these params?
January 19
On Saturday, January 19, 2019 3:05:22 AM MST Dru via Digitalmars-d wrote:
> What is the idea behind why we can't define default ctor for structs?
>
> In current situation I need to "@disable this()"
> then define a constructor with dummy arguments  (because my
> constructor does not need arguments)
>
> It is a big pain on syntax

Structs in D don't actually have default constructors. Rather, they have a default value that they're initialized to - namely T.init (where T is the type name). In fact, all types in D have a default value so that they're guaranteed to not be garbage if you forget to initialize them with a specific value. This gets taken advantage of in a number of places. One obvious one is arrays. The init value can simply be blitted into all of the elements of the array. A number of features in D are built around that and really don't work without it.

In fact, if you @disable this() to disable default-initialization, it makes it so that you can't do a number of things with that type that you can normally do (e.g. put it in an array), because those language features require default-initialization. In fact, default values are so critical in D that a struct with @disable this() still has an init value (e.g. it's used to initialize the struct before the constructor is called). It's just that you can't use the struct in places where default initialization is required, because you told the compiler that you didn't want that to be allowed. However, code may still explicitly use the init value, and some functions will use it (e.g move uses it to ensure that the memory that's left behind after the move isn't in a garbage state). So, while @disable this _can_ be useful, it tends to just be begging for problems, because D is very much designed with the idea that everything can be default-initialized, and @disable this() was only really added as a hack for rare cases where you really needed to not have something be default-initialized.

If you really need something akin to a default constructor, then the normal thing to do is to use a factory function, not do something funky like have dummy arguments. And it may be appropriate in such a situation to @disable this() so that you don't end up with the object being default-initialized (theoretically forcing folks to use the factory function), but even then, there are cases where the init value may still get used, so relying on it never being used can be problematic.

And yes, sometimes, the fact that you can't have default constructors on structs in D like you would in C++ can be annoying, but it's a consequence of the fact that D insists on not having types created with garbage values (as frequently happens in C/C++), and the rest of the language was then built with the assumption that everything is default-initialized. So, while it can be annoying, it does prevent a whole class of bugs that are common in other languages. All of the various ways around it were added later and are used at your own risk.

Alternatively, unlike structs, because classes are reference types (and thus default-initialized to null), they are able to have default constructors. So, if allocating your objects is fine, then classes may be a better option for whatever you're trying to do that requires a default constructor. Or they may not - that would depend on what the code needs to do - but it's something to consider.

- Jonathan M Davis



January 21
On Saturday, 19 January 2019 at 11:01:14 UTC, Alex wrote:
>> 2. not everything can be set using member initializer because of CTFE limitations.
>
> Hm... ok. But even then, if something does not work in the way it should because of limitations: What prevents of setting the parameter inside a function after or on creation?

Performance for repeated function calls after construction, we don't want an extra branch test on each function call and an extra bool bloating/tainting fields in the struct.

A pseudo constructor function is a workaround, but is a bit ugly, adding a separate symbol you have to check for when learning a new library struct, and the function has more unnecessary boilerplate vs a constructor.

> Or, using a non-default constructor exactly for these params?

You can't have a zero argument non-default struct constructor. I've never been told why:
https://wiki.dlang.org/Language_issues#Explicit_syntax_and_.40disable_this

Only the default constructor is constrained by .init, explicit zero argument construction is unconstrained. It could be allowed.
January 21
On Monday, 21 January 2019 at 17:06:55 UTC, Nick Treleaven wrote:
> On Saturday, 19 January 2019 at 11:01:14 UTC, Alex wrote:
>>> 2. not everything can be set using member initializer because of CTFE limitations.
>>
>> Hm... ok. But even then, if something does not work in the way it should because of limitations: What prevents of setting the parameter inside a function after or on creation?
>
> Performance for repeated function calls after construction, we don't want an extra branch test on each function call and an extra bool bloating/tainting fields in the struct.
>
> A pseudo constructor function is a workaround, but is a bit ugly, adding a separate symbol you have to check for when learning a new library struct, and the function has more unnecessary boilerplate vs a constructor.
>
>> Or, using a non-default constructor exactly for these params?
>
> You can't have a zero argument non-default struct constructor. I've never been told why:
> https://wiki.dlang.org/Language_issues#Explicit_syntax_and_.40disable_this
>
> Only the default constructor is constrained by .init, explicit zero argument construction is unconstrained. It could be allowed.

Could you give an example, where a zero argument construction has to be done, which cannot be accomplished by setting the appropriate field with a default value?
January 21
On Monday, 21 January 2019 at 19:08:49 UTC, Alex wrote:
> Could you give an example, where a zero argument construction has to be done, which cannot be accomplished by setting the appropriate field with a default value?

For the following reason, although the default argument constructor hack no longer works:

import std.stdio;

struct Test(bool useFieldDefaultVal)
{
    static if (useFieldDefaultVal)
    {
        int[] arr = [1, 2, 3];
    }
    else
    {
        int[] arr;

        this(int dummy = 0)
        {
            arr = [1, 2, 3];
        }
    }

    void doTest()
    {
    	writeln("Address of arr: ", arr.ptr);
    }
}

void main()
{
    Test!true t1;
    Test!true t2;

    // Deprecation: constructor `onlineapp.Test!false.Test.this`
    // all parameters have default arguments, but structs cannot
    // have default constructors.
    auto t3 = Test!false();

    t1.doTest(); // Prints "Address of arr: 5622DAA0F010"
    t2.doTest(); // Prints "Address of arr: 5622DAA0F010"
    t3.doTest(); // Prints "Address of arr: null"
}
January 21
On Monday, January 21, 2019 12:08:49 PM MST Alex via Digitalmars-d wrote:
> On Monday, 21 January 2019 at 17:06:55 UTC, Nick Treleaven wrote:
> > On Saturday, 19 January 2019 at 11:01:14 UTC, Alex wrote:
> >>> 2. not everything can be set using member initializer because of CTFE limitations.
> >>
> >> Hm... ok. But even then, if something does not work in the way it should because of limitations: What prevents of setting the parameter inside a function after or on creation?
> >
> > Performance for repeated function calls after construction, we don't want an extra branch test on each function call and an extra bool bloating/tainting fields in the struct.
> >
> > A pseudo constructor function is a workaround, but is a bit ugly, adding a separate symbol you have to check for when learning a new library struct, and the function has more unnecessary boilerplate vs a constructor.
> >
> >> Or, using a non-default constructor exactly for these params?
> >
> > You can't have a zero argument non-default struct constructor.
> > I've never been told why:
> > https://wiki.dlang.org/Language_issues#Explicit_syntax_and_.40disable_th
> > is
> >
> > Only the default constructor is constrained by .init, explicit zero argument construction is unconstrained. It could be allowed.
>
> Could you give an example, where a zero argument construction has to be done, which cannot be accomplished by setting the appropriate field with a default value?

A classic case would be some uses of RAII such as what MFC does with its hourglass. You get code something like

Hourglass hg;

and that does everything. The constructor pops up the hourglass, and the destructor takes it down. If you wanted an object like that in D, you would have to use a factory function. e.g.

auto hg = Hourglass.create();

and you would then have to make it so that the object can deal with the fact that the init value never when through the factory function and thus must do nothing in its destructor (using @disable this() to disable default initialization reduces that problem, but since the init value can still be used explicitly, it really doesn't eliminate the problem).

Similarly, as Meta alludes to in his example, having dynamic arrays of mutable elements as member variables is problematic with structs, because all of the instances of that struct on the same thread have a dynamic array that is a slice of the same chunk of memory, meaning that mutating an element in one mutates all of them (at least all of them which haven't ended up with their array being reallocated due to appending or whatnot). In most cases, you'd really want a separate dynamic array for each instance of the struct, and D doesn't give a good way to do that with init values, forcing you to use a factory function instead of a constructor if you want to try and force it. And now that we can actually directly initialize member variables which are pointers or class references, those join the ranks of potentially having problems due to all of the instances of that struct on a particular thread having the same value for those pointers or references. So, it can become a bit error-prone for those types of member variables.

And really, any and all situations where you're looking to have a piece of code run when an instance of the struct is created but where you don't necessarily have arguments for the constructor is going to be harder to deal with cleanly in D thanks to the lack of default constructors.

In general, you just learn to live with it and use factory functions to deal with the problem in those cases where it pops up. So, it usually isn't a big deal, and it's mostly just something that folks new to D complain about, but having init values such that we cannot have default constructors for structs is an area in D that's a tradeoff, not a complete win. Many of us think that the tradeoffs involved are well worth it, but that doesn't change the fact that there are times when having a default constructor would be really nice, and the fact that you can't is then annoying.

- Jonathan M Davis



January 21
On Monday, 21 January 2019 at 20:50:45 UTC, Meta wrote:
> On Monday, 21 January 2019 at 19:08:49 UTC, Alex wrote:
>> Could you give an example, where a zero argument construction has to be done, which cannot be accomplished by setting the appropriate field with a default value?
>
> For the following reason, although the default argument constructor hack no longer works:
> [...]

Ok, and I'm wondering what of this initialization cannot be done without a constructor?

« First   ‹ Prev
1 2 3