View mode: basic / threaded / horizontal-split · Log in · Help
September 19, 2012
no-arg constructor for structs (again)
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?
September 19, 2012
Re: no-arg constructor for structs (again)
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?
September 19, 2012
Re: no-arg constructor for structs (again)
I don't think making the use of optional parens affect semantics is an
idea worth following.
September 19, 2012
Re: no-arg constructor for structs (again)
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.
September 19, 2012
Re: no-arg constructor for structs (again)
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
September 19, 2012
Re: no-arg constructor for structs (again)
On Wednesday, 19 September 2012 at 18:08:15 UTC, Dmitry Olshansky 
wrote:
> 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?

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).

> 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.
>
>> 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).

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.)
September 19, 2012
Re: no-arg constructor for structs (again)
Le 19/09/2012 20:09, Dmitry Olshansky a écrit :
> 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?
>

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.
September 19, 2012
Re: no-arg constructor for structs (again)
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
}
September 19, 2012
Re: no-arg constructor for structs (again)
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
September 20, 2012
Re: no-arg constructor for structs (again)
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
« First   ‹ Prev
1 2
Top | Discussion index | About this forum | D home