Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why can I not implement default constructors for structs in D?

Writing code like

struct S
{
    this() // compile-time error
    {
    }
}

gives me an error message saying

default constructor for structs only allowed with @disable and no body.

Why??

like image 556
Ralph Tandetzky Avatar asked May 20 '13 11:05

Ralph Tandetzky


People also ask

Can a struct have a default constructor?

Implicitly-declared default constructor If no user-declared constructors of any kind are provided for a class type (struct, class, or union), the compiler will always declare a default constructor as an inline public member of its class.

Can you make a constructor for a struct?

Constructors must have the same name as the class itself, and they can be defined with an arbitrary number of parameters. So, a struct can have multiple constructors using function overloading, but it's also possible to have no constructors for very simple structs with only built-in type members.

Do structs have default constructors C#?

C# does not allow a struct to declare a default, no-parameters, constructor. The reason for this constraint is to do with the fact that, unlike in C++, a C# struct is associated with value-type semantic and a value-type is not required to have a constructor.

Can structs have default values C++?

When we define a struct (or class) type, we can provide a default initialization value for each member as part of the type definition. This process is called non-static member initialization, and the initialization value is called a default member initializer.


2 Answers

It stems from the fact that all types in D must have a default value. There are quite a few places where a type's init value gets used, including stuff like default-initializing member variables and default-initializing every value in an array when it's allocated, and init needs to be known at compile for a number of those cases. Having init provides quite a few benefits, but it does get in the way of having a default constructor.

A true default constructor would need to be used in all of the places that init is used (or it wouldn't be the default), but allowing arbitrary code to run in a number of the cases that init is used would be problematic at best. At minimum, you'd probably be forced to make it CTFE-able and possibly pure. And as soon as you start putting restrictions like that on it, pretty soon, you might as well just directly initialize all of the member variables to what you want (which is what happens with init), as you wouldn't be gaining much (if anything) over that, which would make having default constructors pretty useless.

It might be possible to have both init and a default constructor, but then the question comes up as to when one is used over the other, and the default constructor wouldn't really be the default anymore. Not to mention, it could become very confusing to developers as to when the init value was used and when the default constructor was used.

Now, we do have the ability to @disable the init value of a struct (which causes its own set of problems), in which case, it would be illegal to use that struct in any situation that required init. So, it might be possible to then have a default constructor which could run arbitrary code at runtime, but what the exact consequences of that would be, I don't know. However, I'm sure that there are cases where people would want to have a default constructor that would require init and therefore wouldn't work, because it had been @disabled (things like declaring arrays of the type would probably be one of them).

So, as you can see, by doing what D has done with init, it's made the whole question of default constructors much more complicated and problematic than it is in other languages.

The normal way to get something akin to default construction is to use a static opCall. Something like

struct S
{
    static S opCall()
    {
        //Create S with the values that you want and return it.
    }
}

Then whenever you use S() - e.g.

auto s = S();

then the static opCall gets called, and you get a value that was created at runtime. However, S.init will still be used any place that it was before (including S s;), and the static opCall will only be used when S() is used explicitly. But if you couple that with @disable this() (which disables the init property), then you get something akin to what I described earlier where we might have default constructors with an @disabled init.

We may or may not end up with default constructors being added to the language eventually, but there are a number of technical problems with adding them due to how init and the language work, and Walter Bright doesn't think that they should be added. So, for default constructors to be added to the language, someone would have to come up with a really compelling design which appropriately resolves all of the issues (including convincing Walter), and I don't expect that to happen, but we'll see.

like image 82
Jonathan M Davis Avatar answered Oct 13 '22 22:10

Jonathan M Davis


This is one of cases much more tricky than one can initially expect.

One of important and useful features D has over C++ is that every single type (including all user types) has some initial non-garbage value that can be evaluated at compile-time. It is used as T.init and has two important use cases:

  1. Template constraints can use T.init value to check if certain operations can be done on given type (quoting Kenji Hara's snippet):

    template isSomething(T) {
       enum isSomething = is(typeof({
           //T t1;                      // not good if T is nested struct, or has @disable this()
           //T t2 = void; auto x = t2;  // not good if T is non-mutable type
           T t = T.init;                // avoid default construct check
           ...use t...
       }));
    }
    
  2. Your variables are always initialized properly unless you explicitly use int i = void syntax. No garbage possible.

Given that, difficult question arises. Should we guarantee that T() and T.init are the same (as many programmers coming from C++ will expect) or allow default construction that may easily destroy that guarantee. As far as I know, decision was made that first approach is safer, despite being surprising.

However, discussions keep popping with various improvements proposed (for example, allowing CTFE-able default constructor). One such thread has appeared very recently.

like image 44
Mihails Strasuns Avatar answered Oct 13 '22 22:10

Mihails Strasuns