November 25, 2012
There was a topic on this NG before by deadalnix but apparently failed to penetrate the masses.I've brought some unpleasant facts together to get it a death warrant.

1. Quick intro. An example of surprise factor with T() and templated constructor.

import std.conv;
struct A{
   int[] padded;
   this(T...)(T args){
    padded = [0];
    foreach(i, v; T)
        padded ~= to!int(args[i]);
    padded ~= 0;
   }
//...
}

unitest{
    A a = A(2, 3);
    assert(a.padded == [0, 2, 3, 0]);
    A b = A(1);
    assert(b.padded == [0, 1, 0]);
    A c = A();
    assert(c.padded is null); //Spoiler: it passes ;)
}

Enjoy the nice pitfall. Now if it was inside generic function then you'd better check if your type-tuple is empty because compiler will go on limb and substitute it with T.int:

void someFunc(T...)(T args)
{
   ...
   //somewhere we need to pass some of args to construct A
   A x = A(args[1..$]); //may or may not call constructor depending on args
}

For more real world example - it happens with Phobos containers - you can't create an empty one. Either T.init or non-zero argument list.
See e.g. this pull https://github.com/D-Programming-Language/phobos/pull/953

2. Let's go a bit further with generic types. Suppose we remove T() as T.init. What if it breaks somebody's rigorously maintained code?
Surprise - this notation doesn't even exist for built-in types (unlike T.init). Say with:

int fooTest(T)(){
    T test = T();
    return 3;
}

//next line fails to compile with:
//Error: function expected before (), not int of type int
static assert(fooTest!int() == 3);

static assert(fooTest!A() == 3); //OK

3. Even further any code relying on some arbitrary (but not built-in!) type to have T() return T.init is broken in many ways as there is static opCall, that instead can say... order you a burrito (depending on author's maliciousness) ?
Simply put there is not even a single guarantee that static opCall of type T will return that particular type.

After killing this ugly craft the suggestion is to, of course, introduce 0-argument constructors. The syntax is already there. static opCall can't prevent introduction of 0-arg constructor as the compiler already has to disambiguate between the static opCall and the ctor. (BTW which one wins?)

It'll also help immensely people that currently use static opCall to emulate 0-arg ctor and thus use convention rather then guarantee that it does the job of 0-arg constructor.

Thoughts?

-- 
Dmitry Olshansky
November 25, 2012
On 11/25/2012 05:47 PM, Dmitry Olshansky wrote:
> After killing this ugly craft the suggestion is to, of course, introduce 0-argument constructors.

I think it should be done, but then it is even less clear how .init should work.

>  static opCall can't prevent introduction of 0-arg constructor as the compiler already has to disambiguate between the static opCall and the ctor. (BTW which one wins?)

It does not disambiguate.

I think constructors and static opCall should just overload against each other,

The current behaviour in case there are both opCall and constructors is to attempt to pick opCall in case the number of arguments is zero and the constructors otherwise.


> ...
>
> Thoughts?
>

1. is valid.
2./3. are examples of insufficient template constraints.

If the suggestion is to get rid of the built-in struct 0-arg default constructor iff the user provides his own constructors, as is done with the more than 0-arg default constructors, then I fully agree.
November 25, 2012
11/25/2012 9:25 PM, Timon Gehr пишет:
> On 11/25/2012 05:47 PM, Dmitry Olshansky wrote:
>> After killing this ugly craft the suggestion is to, of course,
>> introduce 0-argument constructors.
>
> I think it should be done, but then it is even less clear how .init
> should work.

I suppose that T.init is the blank object, that is composed of respective initial values of its fields. Initial value is the one specified during declaration of a field or .init of its type.

You mean the more or less working @disable this();  ?
That might need extra consideration.

>
>>  static opCall can't prevent introduction of 0-arg constructor as the
>> compiler already has to disambiguate between the static opCall and the
>> ctor. (BTW which one wins?)
>
> It does not disambiguate.
>
> I think constructors and static opCall should just overload against each
> other,
+1
I can't think of anything more logical then to do just that.

>
> The current behaviour in case there are both opCall and constructors is
> to attempt to pick opCall in case the number of arguments is zero and
> the constructors otherwise.
>
>
>> ...
>>
>> Thoughts?
>>
>
> 1. is valid.
> 2./3. are examples of insufficient template constraints.
>

Agreed. The point of second however was mostly about built-ins not having this way of default construction. Thus T() is quite brittle in generic code (if the default construction is implied).


> If the suggestion is to get rid of the built-in struct 0-arg default
> constructor iff the user provides his own constructors, as is done with
> the more than 0-arg default constructors, then I fully agree.

Indeed, I haven't thought of automatically provided per-field constructors. I like your suggestion to do the same with 0-arg ones - provided automatic one returning T.init. Should be more consistent and even backwards compatible I guess.

-- 
Dmitry Olshansky
November 28, 2012
On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky wrote:
> Thoughts?

I don't know about "killing" T(), but I think there *needs* to be an (easy) mechanism to declare ***and*** run-time initialize an object, in a single and comprehensive line.

I had proposed something 2 months ago here: http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky@forum.dlang.org

The proposal wasn't perfect, but still. We need to figure something out...
November 29, 2012
On 11/29/2012 4:47 AM, monarch_dodra wrote:
> On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky wrote:
>> Thoughts?
>
> I don't know about "killing" T(), but I think there *needs* to be an
> (easy) mechanism to declare ***and*** run-time initialize an object, in
> a single and comprehensive line.
>
> I had proposed something 2 months ago here:
> http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky@forum.dlang.org
>
> The proposal wasn't perfect, but still. We need to figure something out...

The original idea is that there should be *no such thing* as default construction of a struct as being anything other than T.init. The default construction of a struct should be a compile time creature, not a runtime one.

Any methods or workarounds to try and make T() produce something different from T.init is bad D practice. The compiler tries to statically head them off, but probably should do a better job of that.
November 29, 2012
On Thursday, 29 November 2012 at 03:24:40 UTC, Walter Bright wrote:
> The original idea is that there should be *no such thing* as default construction of a struct as being anything other than T.init. The default construction of a struct should be a compile time creature, not a runtime one.


Why?
November 29, 2012
On Thursday, November 29, 2012 14:24:38 Walter Bright wrote:
> On 11/29/2012 4:47 AM, monarch_dodra wrote:
> > On Sunday, 25 November 2012 at 16:47:08 UTC, Dmitry Olshansky wrote:
> >> Thoughts?
> > 
> > I don't know about "killing" T(), but I think there *needs* to be an
> > (easy) mechanism to declare ***and*** run-time initialize an object, in
> > a single and comprehensive line.
> > 
> > I had proposed something 2 months ago here: http://forum.dlang.org/thread/bvuquzwfykiytdwsqkky@forum.dlang.org
> > 
> > The proposal wasn't perfect, but still. We need to figure something out...
> 
> The original idea is that there should be *no such thing* as default construction of a struct as being anything other than T.init. The default construction of a struct should be a compile time creature, not a runtime one.
> 
> Any methods or workarounds to try and make T() produce something different from T.init is bad D practice. The compiler tries to statically head them off, but probably should do a better job of that.

I believe that it's very common practice in D to use static opCall to effectively give structs a default constructor when one is desired. I don't think that we need to try and add any default construction mechanism beyond that and that init is sufficient for most cases, but I don't see any reason why using static opCall for providing something closer to a default constructor would be a bad idea, and your post seems to indicate that you think that it is.

- Jonathan M Davis
November 29, 2012
On Thursday, 29 November 2012 at 05:31:13 UTC, Jonathan M Davis wrote:
> <snip>


I'm just not understanding the whole "the default construction of a struct should be a compile time creature, not a runtime one".



Don't you have to initialize the struct with zero's either way?

So either way, you're going to have to initialize it... so no perf increase in any way. Why prevent the user from default-initializing it the way he wants to?
November 29, 2012
On Thursday, 29 November 2012 at 10:41:46 UTC, Mehrdad wrote:
> I'm just not understanding the whole "the default construction of a struct should be a compile time creature, not a runtime one".
>
>
>
> Don't you have to initialize the struct with zero's either way?
>
> So either way, you're going to have to initialize it... so no perf increase in any way. Why prevent the user from default-initializing it the way he wants to?

Every type has a CT-known default initializer, even classes have (null). If structures had a runtime one, this would break code (especially templates and CTFE) which relies on knowing something about constant default instance of a type at CT.

extern bool foo();

struct S
{
  int i;
  this() {
    i = foo() ? 1 : -1;
  }
}
---------
S s;
dosmth(s);
---------
//somewhere in Phobos

void dosmth(T) (T obj)
{
  T val; // is i 0, -1 or 1 ?
}
November 29, 2012
On Thursday, November 29, 2012 11:41:45 Mehrdad wrote:
> On Thursday, 29 November 2012 at 05:31:13 UTC, Jonathan M Davis
> 
> wrote:
> > <snip>
> 
> I'm just not understanding the whole "the default construction of a struct should be a compile time creature, not a runtime one".
> 
> 
> 
> Don't you have to initialize the struct with zero's either way?
> 
> So either way, you're going to have to initialize it... so no perf increase in any way. Why prevent the user from default-initializing it the way he wants to?

All init values must be known at compile time, and there are a number of places in the language where init needs to be used and default construction could never be used like it is in C++. Some limited default construction which could be done at compile time could be used in such cases, but it would have to be quite limited and would generally defeat the purpose of default construction in the first place. For instance, the possibility of exceptions being thrown totally screws with things, so default constructors would have to be nothrow. They'd probably have to be pure as well (and given that they'd have to be run at compile time, there wouldn't be any mutable static variables to access anyway, so the constructor would end up being effectively pure regarldess). And really, if you have to run them at compile time (which the language requires in order to do a number of the things that it does with init values), then default constructors don't buy you much of anything anyway. You'd never be able to really do more than just default initialize all of the member variables. You'd just be doing it in the default constructor instead of initalizing them directly.

The main gain from having default constructors comes from being able to do stuff like RAII where just declaring the variable is enough (MFC's hourglass is a good example of that). It has to be able to do stuff at runtime to be of any real use, but all types need to be constructable at compile time, so that just doesn't work for a type which sits directly on the stack. At best, you'd end up with an init property and a default constructor, and depending on where the type was used, it would end being default-initialized with init or default constructed with the constructor, making the potential confusion and inconsistency great, and it would completely defeat the typical purpose of the default constructor of guaranteeing a particular default state.

It all comes back to having to have a default state which must be known at compile time, since D uses it all over the place (default initializing member variables, default initializing elements in arrays, setting out parameters, etc.). That feature effectively kills default construction. You can have a no- args constructor via static opCall, but it's fundamentally different from a default constructor and really doesn't fill the same role at all.

- Jonathan M Davis
« First   ‹ Prev
1 2 3 4 5 6 7 8 9
Top | Discussion index | About this forum | D home