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??
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.
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.
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.
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.
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.
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:
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...
}));
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With