View mode: basic / threaded / horizontal-split · Log in · Help
November 25, 2012
Time to kill T() as (sometimes) working T.init alias ?
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
Re: Time to kill T() as (sometimes) working T.init alias ?
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
Re: Time to kill T() as (sometimes) working T.init alias ?
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
Re: Time to kill T() as (sometimes) working T.init alias ?
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
Re: Time to kill T() as (sometimes) working T.init alias ?
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
Re: Time to kill T() as (sometimes) working T.init alias ?
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
Re: Time to kill T() as (sometimes) working T.init alias ?
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
Re: Time to kill T() as (sometimes) working T.init alias ?
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
Re: Time to kill T() as (sometimes) working T.init alias ?
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
Re: Time to kill T() as (sometimes) working T.init alias ?
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
Top | Discussion index | About this forum | D home