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

Reply via email to