www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Default struct constructor

reply Victor Porton <porton narod.ru> writes:
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();
Jan 30 2019
next sibling parent Stefan Koch <uplink.coder googlemail.com> writes:
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.
Jan 30 2019
prev sibling next sibling parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Jan 31 2019
parent reply bitwise <bitwise.pvt gmail.com> writes:
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();
Feb 06 2019
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Feb 06 2019
parent reply bitwise <bitwise.pvt gmail.com> writes:
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
Feb 07 2019
parent reply Jonathan M Davis <newsgroup.d jmdavisprog.com> writes:
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
Feb 08 2019
next sibling parent bitwise <bitwise.pvt gmail.com> writes:
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
Feb 08 2019
prev sibling parent aliak <something something.com> writes:
On Friday, 8 February 2019 at 08:25:18 UTC, Jonathan M Davis 
wrote:
 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
Just found if you need a type that can be parameterized by an alias to a function that accesses some local frame data, and you want the factory to be static on the type, you can't do it. Seems like a bug maybe? as I'm not sure what the difference is between the static construct function below or a global one that is parameterized on "alias fun" ? struct S(alias fun) { static auto construct() { auto a = S!fun(); return a; } void call() { fun(); } } void main() { int count; auto s = S!(() => count++).construct; } onlineapp.d(5): Error: cannot access frame pointer of onlineapp.main.S!(delegate () => count++).S
Feb 11 2019
prev sibling parent reply psycha0s <box mail.com> writes:
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.
Feb 08 2019
parent reply Victor Porton <porton narod.ru> writes:
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.
Feb 08 2019
next sibling parent reply psycha0s <box mail.com> writes:
 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.
I see. Yeah, I remember I encountered with the need to have a default struct constructor myself earlier, but I couldn't remember the reason. Some of the current D "features" are very inconvenient. Especially the inability to pass an rvalue by a const reference and the forbidden default constructors for structs.
Feb 08 2019
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 08.02.19 20:53, psycha0s wrote:
 Some of the current D "features" are very inconvenient.
 [...] inability to pass an rvalue by a const reference
Missing by-reference passing of rvalues is a nuisance, but it has nothing to do with const.
Feb 12 2019
prev sibling next sibling parent Dru <dru notreal.com> writes:
 pass an rvalue by a const reference and the forbidden default 
 constructors for structs.
Yes please
Feb 15 2019
prev sibling parent reply Paul <paultjeadriaanse gmail.com> writes:
On Friday, 8 February 2019 at 19:53:16 UTC, psycha0s wrote:
 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.
I see. Yeah, I remember I encountered with the need to have a default struct constructor myself earlier, but I couldn't remember the reason. Some of the current D "features" are very inconvenient. Especially the inability to pass an rvalue by a const reference and the forbidden default constructors for structs.
I'd also like to see the possibility for overriding the default struct constructor. I wouldn't say it's absolutely necessary in my case, as I _could_ use a class instead, however I believe a struct is more proper in my scenario.
Jan 27 2021
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/27/21 7:00 AM, Paul wrote:

 I'd also like to see the possibility for overriding the default struct
 constructor.
I don't have that feeling anymore. :)
 I wouldn't say it's absolutely necessary in my case
Same here: Everytime I thought I needed a struct destructor I solved in one way or another and never thought about it anymore.
 I _could_ use a class instead
Classes are so costly though: At least 2 pointers added; 16 bytes on a 64 bit system. And class variables are references. I would use something like the following and be very happy with it: struct S { int i; static S opCall() { S s; import std.random : uniform; s.i = uniform(1, 42); return s; } static S defaulted() { return S(); } } void main() { auto a = S(); assert(a.i != 0); auto b = S.defaulted; assert(b.i != 0); } Although, I've heard static opCall may have its own set of problems, which I haven't encountered yet; other than making the mistake of writing 'auto s = S();' as its first line, which causes a segmentation fault due to infinite recursion. ;)
 however I believe a struct is more proper in my scenario.
structs boulder! :) Ali
Jan 27 2021
parent reply Paul <paultjeadriaanse gmail.com> writes:
On Wednesday, 27 January 2021 at 18:00:02 UTC, Ali Çehreli wrote:
 struct S {
   int i;

   static S opCall() {
     S s;

     import std.random : uniform;
     s.i = uniform(1, 42);

     return s;
   }

   static S defaulted() {
     return S();
   }
 }
Does this mean opCall takes priority over default constructors (or struct literals, I have no clue what the difference is, semanthically or implementation wise), whilst it does not when arguments are included? (As https://dlang.org/spec/operatoroverloading.html states selfdefined constructors take priority) Thanks for the suggestion by the way, it seems Jonathan M Davis although I hadn't noticed. This seems like a simple solution By the way, doesnt this mean a this(){} syntax could be implemented by treating it as syntactic sugar for opCall? I noticed C++ also has non-argument struct constructors, so its a bit curious to me.
Jan 27 2021
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 1/27/21 2:05 PM, Paul wrote:

 Does this mean opCall takes priority over default constructors (or
 struct literals
Assuming static opCall is defined, there are two syntaxes: S a; // Equals S.init; no constuctor run S b = S(); // static opCall; no constuctor run foo(S()); // static opCall foo(S.init); // S.init
 I have no clue what the difference is, semanthically or
 implementation wise), whilst it does not when arguments are included?
I don't know; let's try. :) import std.stdio; struct S { this(int) { writeln("ctor"); } static opCall(int) { writeln("opCall"); S s; return s; } } void foo(S) { } void main() { auto a = S(1); foo(S(2)); } Both of the calls in main go to this(int); opCall is ignored.
 By the way, doesnt this mean a this(){} syntax could be implemented by
 treating it as syntactic sugar for opCall?
Being able to define a default constructor and then saying we don't have default constructors would be confusing I think.
 I noticed C++ also has
 non-argument struct constructors, so its a bit curious to me.
Those are called default constructors. D thinks it differently: there are no default constructors for strtucts. opCall is a strange thing where the name of the type is callable and it looks like a default constructor in usage. Ali
Jan 27 2021
prev sibling next sibling parent Cym13 <cpicard openmailbox.org> writes:
On Friday, 8 February 2019 at 19:24:00 UTC, Victor Porton wrote:
 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.
You might like this article https://w0rp.com/blog/post/an-raii-constructor-by-another-name-is-just-as-sweet/
Feb 10 2019
prev sibling parent bitwise <bitwise.pvt gmail.com> writes:
On Friday, 8 February 2019 at 19:24:00 UTC, Victor Porton wrote:
 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.
I've boiled my requirement down to structs having a "lifetime starting" method of some kind. I don't mind if T.init is blitted over my object before it's called, as long as I can initialize any struct fields that couldn't be initialized inline due to their value not being CTFE'able. I suppose that for this to be worth doing though, it would need to work extern(D) as well. So creating the object in D would blit T.init to the struct first, while creating the same object in C would not. So I'm wondering if this difference would be a deal breaker (I don't believe it should be), or if what I'm suggesting creates other technical problems (I can't think of any).
Feb 12 2019