digitalmars.D - no-arg constructor for structs (again)
- monarch_dodra (119/119) Sep 19 2012 About two month ago, I started a thread about the possibility of
- Maxim Fomin (14/35) Sep 19 2012 This means that you still have a class object. What is design
- monarch_dodra (13/36) Sep 19 2012 That's not the point at all. For starters, the "Payload" is
- deadalnix (15/50) Sep 20 2012 My solution was to include code analysis in the compiler in order to
- Timon Gehr (2/2) Sep 19 2012 I don't think making the use of optional parens affect semantics is an
- deadalnix (9/11) Sep 19 2012 I have to agree with that.
- Dmitry Olshansky (16/21) Sep 19 2012 I do not feel that there is a lot of reference-like types that take 0
- monarch_dodra (52/74) Sep 19 2012 There are not a lot currently. RefCounted is. PRNG should
- deadalnix (4/10) Sep 19 2012 A use case I encountered more than once is interfacing with C++.
- Felix Hufnagel (13/13) Sep 19 2012 isn't it even worse?
- Jonathan M Davis (4/18) Sep 19 2012 Of course that generates a linker error. You just declared a function wi...
- Felix Hufnagel (8/27) Sep 20 2012 sure, but it's a bit unexpected. do we need to be able to declare
- Jonathan M Davis (8/39) Sep 20 2012 It can be useful at module scope, and it would complicate the grammar to...
- Don Clugston (2/41) Sep 20 2012 Bug 3438
- Timon Gehr (8/13) Sep 20 2012 struct S{
- deadalnix (2/15) Sep 20 2012 Last time I checked it, it was not working. No constructor was called.
- bearophile (21/43) Sep 19 2012 Tangential to your discussion: this needs to be allowed, because
- David (9/9) Sep 20 2012 The only thing I really miss is:
- monarch_dodra (6/15) Sep 20 2012 That probably won't _ever_ work, because that is a default
About two month ago, I started a thread about the possibility of having a "no-arg" constructor (not to be confused with a "default" constructor). The thread was: http://forum.dlang.org/thread/icjwbtlxsaekksyoljfp forum.dlang.org Back then, the language was still new to me (I have a better grip on it now), and I got extra information during the thread, which threw me off course. One of the better arguments throw at me from another thread (by Andrei), was that a no-arg constructor with interfere with the "auto a = S();" syntax. I had no rebuke at the time. I'd like to restart this conversation. First, by showing a no-arg constructor is needed, and then, by showing how we should be able to plug it into the language. **************************** The biggest issue with not having a no-arg constructor can easilly be seen if you have ever worked with a "Reference Semantic" semantic struct: A struct that has a pointer to a payload. Basically, a class, but without the inherited Object polymorphism. These are hard to work with, both for the user and the implementer: They either use auto-intialization, making EVERY CALL start with "ensure initialied" (costly for ranges). Either that, or they need to be explicitly initialized. Or a mix of both, and a source of bugs and frustration in phobos. Anyways, let's start with an example. For the sake of simplicity, I defined two structs to avoid the "structs with a constructor can't be default newed" bug; ---- struct S { int* p; } struct S2 { int* p; this(int){}; } void main() { S a; S* pa; //auto b = S; auto pb = new S; auto c = S.init; //auto pc = ??? auto d = S(); auto pd = new S(); auto e = S2(5); auto pe = new S2(5); } ---- In the above code, a-c/pa-pc are not initialized, as expected. e/pe are initialized, as expected. HOWEVER, and in contrast to classes, it is surprising that "auto d = S();" and "auto pd = new S();" does not create an initialized reference semantic struct. It is a bare minimum to give a user the ability to allocate & initialize in a single call... This is a problem for both user *and* implementer: The user will have trouble initializing his struct, whereas the implementer will have trouble on his end properlly implementing his struct. I was trying to develop one such struct. One solution was to use the opCall "hack". Andrei suggested I migrate to a class. On one end, I think his suggestion is the better approach here. On the other hand I also think that if a developer is forced into migrating from a struct to a class because of implementation detail, or using a hack, it is a tell-tale sign of something stinky. **************************** What is very interesting to note above (IMO), is that the language provides no less than THREE syntaxes to allocate a non-constructed S, two of which can be used with auto: *Explicit typing (a) *For stack: S.init (c), parenthesis (d) *for new: without parenthesis (pb), with parenthesis (pd) If we have such extra ways, then surely, one of the two can be used to call the no arg constructor, while the other is just an S.init memcopy, right? Here is my proposal: ---- struct S { int* p; this(){}; //no arg constructor this(int){}; //arg constructor } void main() { S a; //Not initialized S* pa; //Not initialized auto b1 = S; //Not initialized (new semantic) auto b2 = S.init; //Not initialized (verbose semantic) auto pb = new S; //Not initialized auto e = S2(5); //Initialized, calls this(int) auto pe = new S2(5); //Initialized, calls this(int) auto d = S(); //Initialized, calls this() (migrating semantic) auto pd = new S(); //Initialized, calls this() (migrating semantic) } ---- As is shown in this example, the language semantics should be perfectly capable of handling this. The "issues" we may encounter are more due to the ambiguities with the "old" semantics: *Regarding the "migrating semantic": This form is currently available in D. I propose it remains useable for now, but later becomes deprecated if the struct does not have a no-arg constructor. *Regarding "b1": I propose this semantic become legal. It is legal with "auto pb = bew S;", so "auto b = S;" should also be accepted. The alternative would be to use "S.init", but this is more verbose, and more "explicit". ************ I realize this would be a big change to the *core* language, yet the no no-arg constructor has felt like a (breaking) limitation from day one, and it would be really nice if we could support it. I realize *why* the "default" constructor had to go, but "no-arg" didn't have to go with it. I think it was an accident to let it go, and we should be trying to fix this. Could I get some feedback so I could make a formal and thorough enhancement request?
Sep 19 2012
On Wednesday, 19 September 2012 at 11:51:13 UTC, monarch_dodra wrote:The biggest issue with not having a no-arg constructor can easilly be seen if you have ever worked with a "Reference Semantic" semantic struct: A struct that has a pointer to a payload. Basically, a class, but without the inherited Object polymorphism.This means that you still have a class object. What is design behind inserting class into the structure for the sake of escaping from classes?These are hard to work with, both for the user and the implementer: They either use auto-intialization, making EVERY CALL start with "ensure initialied" (costly for ranges). Either that, or they need to be explicitly initialized. Or a mix of both, and a source of bugs and frustration in phobos.If you know initialize values at compile time, you can use them. If not, you can overload opCall to make custom initialization at runtime. Yes, it doesn't help to initialize structures which are created like "S s;" - but that how structures work: they are lightweight objects in some matter of speaking and if somebody wants to call some functions even in such cases, he probably needs to rethink the design.HOWEVER, and in contrast to classes, it is surprising that "auto d = S();" and "auto pd = new S();" does not create an initialized reference semantic struct. It is a bare minimum to give a user the ability to allocate & initialize in a single call...Indeed, they are initialized.What is very interesting to note above (IMO), is that the language provides no less than THREE syntaxes to allocate a non-constructed S, two of which can be used with auto: *Explicit typing (a) *For stack: S.init (c), parenthesis (d) *for new: without parenthesis (pb), with parenthesis (pd)Which "construction" do you refer?
Sep 19 2012
On Wednesday, 19 September 2012 at 12:31:08 UTC, Maxim Fomin wrote:On Wednesday, 19 September 2012 at 11:51:13 UTC, monarch_dodra wrote:That's not the point at all. For starters, the "Payload" is another struct, NOT a class wrapped in a struct. As for why we aren't using a class to begin with? First, because a class wraps much more than we want: polymorphism, adherence to a base "Object Type", virtual opEquals, RTTI... But mostly, because the object we manipulate is a struct and has always been a struct. It uses reference semantics, but is in dire need of a an initialization to default. On Wednesday, 19 September 2012 at 14:09:10 UTC, deadalnix wrote:The biggest issue with not having a no-arg constructor can easilly be seen if you have ever worked with a "Reference Semantic" semantic struct: A struct that has a pointer to a payload. Basically, a class, but without the inherited Object polymorphism.This means that you still have a class object. What is design behind inserting class into the structure for the sake of escaping from classes?Le 19/09/2012 15:24, Timon Gehr a écrit :Would you happen to have some links to those proposed solutions, or reword them here for us?I don't think making the use of optional parens affect semantics is an idea worth following.I have to agree with that. However, argument-less constructor is something required for struct. The problem is raised on a regular basis on this newsgroup, and some solution already have been proposed. As discussed earlier in the reference thread, the compiler will have to track down initialization at some point. A struct with an argument-less constructor which isn't initialized must be an error. This will avoid the () semantic dichotomy while solving that problem.
Sep 19 2012
Le 20/09/2012 08:26, monarch_dodra a écrit :On Wednesday, 19 September 2012 at 12:31:08 UTC, Maxim Fomin wrote:My solution was to include code analysis in the compiler in order to ensure that a struct with an argument-less constructor is assigned before being used. struct S { this() {} } S s; foo(s); // Error, s may not have been initialized s = S(); foo(s); // OK S.init contains the struct memory layout as it is before any constructor run on them. It is not safe to use in on a struct with a default argument. Note that the code analysis required for such a task is planed to be included in dmd, to support disable this(); anyway.On Wednesday, 19 September 2012 at 11:51:13 UTC, monarch_dodra wrote:That's not the point at all. For starters, the "Payload" is another struct, NOT a class wrapped in a struct. As for why we aren't using a class to begin with? First, because a class wraps much more than we want: polymorphism, adherence to a base "Object Type", virtual opEquals, RTTI... But mostly, because the object we manipulate is a struct and has always been a struct. It uses reference semantics, but is in dire need of a an initialization to default. On Wednesday, 19 September 2012 at 14:09:10 UTC, deadalnix wrote:The biggest issue with not having a no-arg constructor can easilly be seen if you have ever worked with a "Reference Semantic" semantic struct: A struct that has a pointer to a payload. Basically, a class, but without the inherited Object polymorphism.This means that you still have a class object. What is design behind inserting class into the structure for the sake of escaping from classes?Le 19/09/2012 15:24, Timon Gehr a écrit :Would you happen to have some links to those proposed solutions, or reword them here for us?I don't think making the use of optional parens affect semantics is an idea worth following.I have to agree with that. However, argument-less constructor is something required for struct. The problem is raised on a regular basis on this newsgroup, and some solution already have been proposed. As discussed earlier in the reference thread, the compiler will have to track down initialization at some point. A struct with an argument-less constructor which isn't initialized must be an error. This will avoid the () semantic dichotomy while solving that problem.
Sep 20 2012
I don't think making the use of optional parens affect semantics is an idea worth following.
Sep 19 2012
Le 19/09/2012 15:24, Timon Gehr a écrit :I don't think making the use of optional parens affect semantics is an idea worth following.I have to agree with that. However, argument-less constructor is something required for struct. The problem is raised on a regular basis on this newsgroup, and some solution already have been proposed. As discussed earlier in the reference thread, the compiler will have to track down initialization at some point. A struct with an argument-less constructor which isn't initialized must be an error. This will avoid the () semantic dichotomy while solving that problem.
Sep 19 2012
On 19-Sep-12 15:52, monarch_dodra wrote:I realize *why* the "default" constructor had to go, but "no-arg" didn't have to go with it. I think it was an accident to let it go, and we should be trying to fix this.I do not feel that there is a lot of reference-like types that take 0 arguments at construction. Any meaningful examples? About checking for "was initialized" it is indeed painful, yet I believe even C++ is in the same boat (empty shared_ptr?). Also I do suspect that in the majority of cases these are just asserts since they aim to catch logic errors in code. Having a way to ensure initialization statically would be nice. Currently only disabling this() would achieve that but I suspect it's somewhat bogus ATM. In fact having disable this() undermines the argument for "always have T.init" strategy as generic code now have to deal with both cases.Could I get some feedback so I could make a formal and thorough enhancement request?I'd rather see static opCall go and be replaced with no-arg constructor. But this alone doesn't bring much benefit (if any). -- Dmitry Olshansky
Sep 19 2012
On Wednesday, 19 September 2012 at 18:08:15 UTC, Dmitry Olshansky wrote:On 19-Sep-12 15:52, monarch_dodra wrote:There are not a lot currently. RefCounted is. PRNG should arguably be migrated to them. Containers are kind of a hybrid (but this seems to be more of a implementation detail than by concept).I realize *why* the "default" constructor had to go, but "no-arg" didn't have to go with it. I think it was an accident to let it go, and we should be trying to fix this.I do not feel that there is a lot of reference-like types that take 0 arguments at construction. Any meaningful examples?About checking for "was initialized" it is indeed painful, yet I believe even C++ is in the same boat (empty shared_ptr?). Also I do suspect that in the majority of cases these are just asserts since they aim to catch logic errors in code.No, because C++ has default constructor. Regarding D's logic error, the problem is that the lack of "no-arg" underminds the definition of "logic error": More below!Having a way to ensure initialization statically would be nice. Currently only disabling this() would achieve that but I suspect it's somewhat bogus ATM. In fact having disable this() undermines the argument for "always have T.init" strategy as generic code now have to deal with both cases.Here is a concrete example: import std.random, std.typeconv; void main() { alias RefCounted!(int, RefCountedAutoInitialize.no) RCI; //RefCountedInt RCI a; //NOT initialized RCI b = RefCounted!int(15); //Initialized RCI c = RefCounted!int(); //Initialized to int.init ... ? int i; i = a; //Logic error i = b; //Ok i = c; //Logic error? } The issue here is with "c". Arguably, it was not initialized. Arguably, the intent was, as opposed to "a", to initialize it to it's default value. At that point, is trying to read c a logic error? And if it is, can you really blame the user? Same example with PRNG: void main() { alias ... PRNG; PRNG a; //NOT Seeded PRNG b = RefCounted!int(15); //Seeded PRNG c = RefCounted!int(); //Seeded with default seed...? a.front(); //Logic error b.front(); //Ok c.front(); //Logic error? } Ditto. Will the user really understand that b was seeded, yet c wasn't. This is even more ambiguous that PRNG *does* have a "seed()" method with a default seed I know for a FACT that _I_ would have expected c to be default seeded. This makes the prng *VERY* ambiguous about whether or not it was seeded :/ Basically: To seed with 15: PRNG b = RefCounted!int(15); //Seeded, yay To default seed: PRNG c = RefCounted!int(); c.seed(); // What...? The opCall hack would fix the issue in the above example, but as stated in the previous thread, it is just that, a hack: You can't use it to construct inplace with emplace!(T, Args...), nor can you take it's address, nor can you use it to new it (should you ever want to do that.)Could I get some feedback so I could make a formal and thorough enhancement request?I'd rather see static opCall go and be replaced with no-arg constructor. But this alone doesn't bring much benefit (if any).
Sep 19 2012
Le 19/09/2012 20:09, Dmitry Olshansky a écrit :On 19-Sep-12 15:52, monarch_dodra wrote:A use case I encountered more than once is interfacing with C++. Another is to create an argument-less initializer that forward to another one with default arguments.I realize *why* the "default" constructor had to go, but "no-arg" didn't have to go with it. I think it was an accident to let it go, and we should be trying to fix this.I do not feel that there is a lot of reference-like types that take 0 arguments at construction. Any meaningful examples?
Sep 19 2012
isn't it even worse? import std.stdio; struct S { int i; this(void* p = null){this.i = 5;} } void main() { //S l(); //gives a linker error auto k = S(); writeln(k.i); //prints 0 }
Sep 19 2012
On Thursday, September 20, 2012 00:12:04 Felix Hufnagel wrote:isn't it even worse? import std.stdio; struct S { int i; this(void* p = null){this.i = 5;} } void main() { //S l(); //gives a linker error auto k = S(); writeln(k.i); //prints 0 }Of course that generates a linker error. You just declared a function without a body. - Jonathan M Davis
Sep 19 2012
On Thursday, 20 September 2012 at 00:14:04 UTC, Jonathan M Davis wrote:On Thursday, September 20, 2012 00:12:04 Felix Hufnagel wrote:sure, but it's a bit unexpected. do we need to be able to declare empty functions? but whats even more confusing: you are not allowed to declare an no_arg constructor. but you are allowed to declare one where all parameters have default parameters. but then, how to call it without args? auto k = S(); doesn't work?isn't it even worse? import std.stdio; struct S { int i; this(void* p = null){this.i = 5;} } void main() { //S l(); //gives a linker error auto k = S(); writeln(k.i); //prints 0 }Of course that generates a linker error. You just declared a function without a body. - Jonathan M Davis
Sep 20 2012
On Thursday, September 20, 2012 10:11:41 Felix Hufnagel wrote:On Thursday, 20 September 2012 at 00:14:04 UTC, Jonathan M Davis wrote:It can be useful at module scope, and it would complicate the grammar to make it anything else at function scope, even if there's no practical reason to use it that way there. C/C++ (which doesn't have nested functions) also treats that declaration as a function declaration.On Thursday, September 20, 2012 00:12:04 Felix Hufnagel wrote:sure, but it's a bit unexpected. do we need to be able to declare empty functions?isn't it even worse? import std.stdio; struct S { int i; this(void* p = null){this.i = 5;} } void main() { //S l(); //gives a linker error auto k = S(); writeln(k.i); //prints 0 }Of course that generates a linker error. You just declared a function without a body. - Jonathan M Davisbut whats even more confusing: you are not allowed to declare an no_arg constructor. but you are allowed to declare one where all parameters have default parameters. but then, how to call it without args? auto k = S(); doesn't work?It's a bug. I'm pretty sure that there's a bug report for it already, but I'd have to go digging for it to know which one it is. - Jonathan M Davis
Sep 20 2012
On 20/09/12 11:09, Jonathan M Davis wrote:On Thursday, September 20, 2012 10:11:41 Felix Hufnagel wrote:Bug 3438On Thursday, 20 September 2012 at 00:14:04 UTC, Jonathan M Davis wrote:It can be useful at module scope, and it would complicate the grammar to make it anything else at function scope, even if there's no practical reason to use it that way there. C/C++ (which doesn't have nested functions) also treats that declaration as a function declaration.On Thursday, September 20, 2012 00:12:04 Felix Hufnagel wrote:sure, but it's a bit unexpected. do we need to be able to declare empty functions?isn't it even worse? import std.stdio; struct S { int i; this(void* p = null){this.i = 5;} } void main() { //S l(); //gives a linker error auto k = S(); writeln(k.i); //prints 0 }Of course that generates a linker error. You just declared a function without a body. - Jonathan M Davisbut whats even more confusing: you are not allowed to declare an no_arg constructor. but you are allowed to declare one where all parameters have default parameters. but then, how to call it without args? auto k = S(); doesn't work?It's a bug. I'm pretty sure that there's a bug report for it already, but I'd have to go digging for it to know which one it is. - Jonathan M Davis
Sep 20 2012
On 09/20/2012 10:11 AM, Felix Hufnagel wrote:... but whats even more confusing: you are not allowed to declare an no_arg constructor. but you are allowed to declare one where all parameters have default parameters. but then, how to call it without args? auto k = S(); doesn't work?struct S{ this(int=0){} } void main(){ S s; s.__ctor(); }
Sep 20 2012
Le 20/09/2012 00:12, Felix Hufnagel a écrit :isn't it even worse? import std.stdio; struct S { int i; this(void* p = null){this.i = 5;} } void main() { //S l(); //gives a linker error auto k = S(); writeln(k.i); //prints 0 }Last time I checked it, it was not working. No constructor was called.
Sep 20 2012
monarch_dodra:struct S { int* p; } struct S2 { int* p; this(int){}; } void main() { S a; S* pa; //auto b = S; auto pb = new S; auto c = S.init; //auto pc = ??? auto d = S(); auto pd = new S(); auto e = S2(5); auto pe = new S2(5); }Tangential to your discussion: this needs to be allowed, because this removes one unnecessary special case/limit, avoiding the need to write some stupid boilerplate constructor code (I have written tons of those): struct Foo { int x, y; } void main() { auto f = new Foo(5, 10); } The currently usable workaround is silly in a language like D: struct Foo { int x, y; } void main() { auto f = new Foo; *f = Foo(5, 10); } Bye, bearophile
Sep 19 2012
The only thing I really miss is: class Foo {} struct Bar { Foo foo = new Foo(); } void main() { Bar s = Bar(); assert(s.foo !is null); }
Sep 20 2012
On Thursday, 20 September 2012 at 09:22:39 UTC, David wrote:The only thing I really miss is: class Foo {} struct Bar { Foo foo = new Foo(); } void main() { Bar s = Bar(); assert(s.foo !is null); }That probably won't _ever_ work, because that is a default *instruction*, not a default *value*. It is a default constructor in disguise :D which is a no-no, as it would break all of D's move semantics (which are pretty awesome, IMO).
Sep 20 2012