Domanda

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??

È stato utile?

Soluzione

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.

Altri suggerimenti

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top