www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Static struct initialization syntax behavior & it being disabled upon

reply HuskyNator <HuskyNator protonmail.ch> writes:
This is a twofold question, along the example code below:
- 1: Why does `m` initialization behave as if `m[0][]=1` and 
`m[1][]=2` were used? (Shouldn't this code result in an error 
instead?)
- 2: Why does adding a constructor to a struct disable the use of 
the static initialization syntax? I only see it mentioned in the 
documentation indirectly (there are notes in the example code 
specifying as such, but the text itself does not seem to define 
their removal). I also don't see how this behavior is beneficial, 
as it now requires me to write additional constructors, as soon 
as I want to add 1.

```d
struct Mat(int n){
	int[n][n] mat;

	void write(){
		writeln(mat);
	}
// Will cause the m & n initialisations to yield errors.
//	this(int i){
//		mat[0][0] = i;
//	}
}

void main() {
	Mat!2 m = {[1,2]}; // Prints [[1, 1], [2, 2]]
	Mat!2 n = {[[1,2],[3,4]]}; // Prints [[1, 2], [3, 4]]
	m.write();
	n.write();
}
```

PS:
Are there any plans to change the behaviour of empty struct 
constructors? (eg: `this(){}`) It surprised me greatly coming 
into D, and one of the commonly suggested workarounds (using 
`opCall`) seems rather inelegant to me. Why is this possible in 
C++ in contrast?
Apr 17 2022
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 4/17/22 08:13, HuskyNator wrote:

 - 1: Why does `m` initialization behave as if `m[0][]=1` and `m[1][]=2`
 were used? (Shouldn't this code result in an error instead?)
That's pretty weird. I think it boils down to scalar assignment to an array being valid: void main() { int[3] arr; arr = 42; // Feature? (Yes.) import std.algorithm : all; assert(arr[].all!(e => e == 42)); } So, in the end, each element of your argument array gets assigned to each element of member array. Makes sense (to me :) )... I think the initialization of 'n' is more straightforward.
 - 2: Why does adding a constructor to a struct disable the use of the
 static initialization syntax?
I am not sure how to answer this question because I am about to say don't use the static initialization syntax. :/ To me, idiomatic way of constructing D objects is auto m = Mat!2([1,2]); The reason why one cannot define a default constructor for a D struct is because every type in D must have a statically known .init value. A user-defined default constructor could not be known at compile time.
 it now requires me to
 write additional constructors, as soon as I want to add 1.
I don't see it as a big problem in practice. As soon as I need to add a constructor, the default behavior of setting members to arguments seems out of place. Either the one constructor is sufficient or write at most another one at most.
 the commonly suggested workarounds (using `opCall`) seems
 rather inelegant to me.
Agreed. And static opCall() is not usable in all cases as it somehow conflicts in some cases. (Don't remember now.)
 Why is this possible in C++ in contrast?
C++ does not insist that all types have a statically known .init value. If I'm not mistaken, the part about disabling certain constructors, move or otherwise, is commonly accepted in C++ as well. Really, compared to C++, the amount of constructor, destructor, copy constructor, etc. that I do *not* write in D is very liberating to me. It feels like I just write what is needed and it mostly just works. Ali
Apr 17 2022
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Sun, Apr 17, 2022 at 05:35:13PM -0700, Ali Çehreli via Digitalmars-d-learn
wrote:
 On 4/17/22 08:13, HuskyNator wrote:
[...]
 - 2: Why does adding a constructor to a struct disable the use of
 the static initialization syntax?
I am not sure how to answer this question because I am about to say don't use the static initialization syntax. :/ To me, idiomatic way of constructing D objects is auto m = Mat!2([1,2]); The reason why one cannot define a default constructor for a D struct is because every type in D must have a statically known .init value. A user-defined default constructor could not be known at compile time.
IME, when the lack of default ctors in D starts bothering me, that's usually around the time the struct really ought to be rewritten as a class (which *does* support default ctors). Structs in D ought to be treated like "glorified ints", as Andrei puts it. If you need complex ctors and complex methods, that's a sign you should be using a class instead. [...]
 Really, compared to C++, the amount of constructor, destructor, copy
 constructor, etc. that I do *not* write in D is very liberating to me.
 It feels like I just write what is needed and it mostly just works.
[...] One thing about idiomatic D code is that it embraces the "create the object first, then kick it into shape" philosophy, vs. the "meticulously manage the initialization of every last bit in the ctor so that the object comes out of the ctor call a perfect product ready to ship" philosophy. The latter requires a lot of boilerplate and micromanagement of object state; the former, when done well, leads to streamlined code that gets its job done with a minimum of fuss. T -- Never criticize a man until you've walked a mile in his shoes. Then when you do criticize him, you'll be a mile away and he won't have his shoes.
Apr 17 2022
next sibling parent reply cc <cc nevernet.com> writes:
On Monday, 18 April 2022 at 03:21:30 UTC, H. S. Teoh wrote:
 Structs in D ought to be treated like "glorified ints", as 
 Andrei puts it. If you need complex ctors and complex methods, 
 that's a sign you should be using a class instead.
Unless you're having a nice quiet get-together with friends, and you don't want to invite the GC, the biggest loudest party animal on the block. Phobos's RefCounted seems to stretch the definition of "glorified ints"..
Apr 18 2022
parent "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Mon, Apr 18, 2022 at 08:22:26AM +0000, cc via Digitalmars-d-learn wrote:
 On Monday, 18 April 2022 at 03:21:30 UTC, H. S. Teoh wrote:
 Structs in D ought to be treated like "glorified ints", as Andrei
 puts it. If you need complex ctors and complex methods, that's a
 sign you should be using a class instead.
Unless you're having a nice quiet get-together with friends, and you don't want to invite the GC, the biggest loudest party animal on the block. Phobos's RefCounted seems to stretch the definition of "glorified ints"..
"Glorified int" includes pass-by-value types like pointers. Pointers / references wrapped in a struct is one of the more powerful D constructs that lets you do some pretty neat things. (Just don't expect it to behave like C++, lol. :-P) T -- Debian GNU/Linux: Cray on your desktop.
Apr 18 2022
prev sibling parent reply HuskyNator <HuskyNator protonmail.ch> writes:
On Monday, 18 April 2022 at 03:21:30 UTC, H. S. Teoh wrote:
 Structs in D ought to be treated like "glorified ints", as 
 Andrei puts it. If you need complex ctors and complex methods, 
 that's a sign you should be using a class instead.
I prefer not to use classes, as the code would now move towards using references, which is the exact reason I'm using structs. I ended up creating a constructor for my needs and disabling the default constructor. I'm mostly just surprised the static syntax is turned off by adding one. I still don't see the reason behind it. Why have it but disable it? I ironically almost always need the exact same constructor with the identical arguments though: Initialize the matrix to the identity matrix. Why not introduce the empty self-defined constructor as a separate thing from the .init value? On a sidenote, I'm surprised D did not choose 0 as the default floating value. Doesn't almost every language do this? I understand the thinking behind it, but when the type one uses in a template influences the behavior of the code, that seems like a pretty big red flag to me. (Any non-floating type defaults to 0, but using floats/doubles suddenly introduces NaN, surely I'm not the only one that sees a problem with this 😅) Especially when it's basically a standard 0 is used for this. Sorry for the rant.
Apr 18 2022
next sibling parent cc <cc nevernet.com> writes:
On Monday, 18 April 2022 at 10:26:16 UTC, HuskyNator wrote:
 On a sidenote, I'm surprised D did not choose 0 as the default 
 floating value. Doesn't almost every language do this? I 
 understand the thinking behind it, but when the type one uses 
 in a template influences the behavior of the code, that seems 
 like a pretty big red flag to me. (Any non-floating type 
 defaults to 0, but using floats/doubles suddenly introduces 
 NaN, surely I'm not the only one that sees a problem with this 
 😅) Especially when it's basically a standard 0 is used for 
 this. Sorry for the rant.
I agree, it's a hiccup. I have at times intentionally initialized a float as NaN so that I can identify later whether an appropriate value has been assigned, but I've never seen the need to have this be the default behavior when integer types always init to 0 (more specifically, init to a MODIFYABLE value). In game design I have tons upon tons of floats that all [should] start initialized to zero. I can add 4 to a declared but not-assigned-to int and it'll be 4, a float remains NaN. Having to manually declare appropriate init values to each one doesn't aid me in detecting "bugs". If I had an int that was supposed to default to 10 instead of 0 it would still be a bug if I forgot to specify that, tripping me up for falsely assuming floats would start at 0 doesn't aid my workflow in any way. The whole "you should pay more attention to what you're initializing, o buggy programmer you" philosophy seems like something that should be reserved for pointers and reference types, not basic numeric data. It's probably set in stone by this point though and too late to change. Ten years ago, almost to the day: https://forum.dlang.org/thread/thsjtreegdwcgbazhczd forum.dlang.org The reasoning still feels flimsy and stubborn.
Apr 18 2022
prev sibling parent user1234 <user1234 12.de> writes:
On Monday, 18 April 2022 at 10:26:16 UTC, HuskyNator wrote:
 On a sidenote, I'm surprised D did not choose 0 as the default 
 floating value. Doesn't almost every language do this? I 
 understand the thinking behind it, but when the type one uses 
 in a template influences the behavior of the code, that seems 
 like a pretty big red flag to me. (Any non-floating type 
 defaults to 0, but using floats/doubles suddenly introduces 
 NaN, surely I'm not the only one that sees a problem with this 
 😅) Especially when it's basically a standard 0 is used for 
 this. Sorry for the rant.
Let me explain the why: D default initialization is not designed to replace user-defined initialization, it's rather made to make bugs related to non-initialized variables stable, e.g not UB. The easiest way to get that is to think to references and pointers. A random garbage value pointed by an alloca may work to some point (e.g access to member), if it's set to `null` right after the alloca then you have a stable segfault that always happens at the same time and is easy to debug. Similarly, for floating point numbers the D designers considered that `NaN` was the best choice because FP operations will not wrongly appear valid when starting operations with NaN. With integral types, this system does not work as well, as `0` doesn't create bugs as easily as `null` and `NaN`. The confusion about the real role of default initialization comes from integral types I believe.
Apr 18 2022
prev sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 4/17/22 17:35, Ali Çehreli wrote:

 compared to C++, the amount of constructor, destructor, copy
 constructor, etc. that I do *not* write in D is very liberating to me.
 It feels like I just write what is needed and it mostly just works.
The following is a quick and dirty grep-based stats from a largish successful project that implements multiple libraries and binaries. The figures are numbers of times each construct appears in source code: struct: 231 interface: 3 class: 12 union: 0 this(/* ... */): 72 [1] shared static this(): 8 static this(): 1 [2] shared static ~this(): 0 static ~this(): 0 ~this(): 8 this(this): 0 [3] [1] Most operations in most constructors are trivial assignments to members. [2] It contains just an enforce expression to ensure the environment is as expected. (It is an oversight that this is not a 'shared static this' as well.) [3] There are no copy constructors either because the project started with an older compiler. It is remarkable that I did not implement a single copy or move behavior ever. Compare that to countless C++ articles on attempting to teach how to deal with fundamental operations of object. Forgotten to be called or not, there are no 'move' (which does not move in C++) or 'forward' (which does not forward in C++) expressions at all. What a price the programming community keeps on paying just because their powerful programming language was there first... Ali
Apr 18 2022
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
On 4/18/22 09:17, Ali Çehreli wrote:

 shared static ~this():   0
         static ~this():   0
                ~this():   8
Apologies for omitting 'scope' statements: scope(exit): 34 scope(success): 6 scope(failure): 8 Ali
Apr 18 2022
prev sibling parent reply zjh <fqbqrr 163.com> writes:
On Sunday, 17 April 2022 at 15:13:29 UTC, HuskyNator wrote:
 This is a twofold question, along the example code below:
The `default constructor` of `struct` is very convenient to use. I hope that d can support it like `C++`.
Apr 18 2022
parent zjh <fqbqrr 163.com> writes:
On Monday, 18 April 2022 at 13:21:44 UTC, zjh wrote:

 I hope that d can support it like `C++`.
`Struct` and `class` behavior is inconsistent. `Constructors` sometimes have initialize behavior. If D doesn't think about `C++` users, `C++` users feel too troublesome, can't understand, and the cognitive burden is too heavy.
Apr 18 2022