On Wed, 21 Oct 2009 20:15:16 +0400, Andrei Alexandrescu <[email protected]> wrote:

Today, structs can't write their own this(). There aren't very solid reasons for that except that it makes language implementation more difficult.

I wonder how much of a problem that could be in practice. I realized today that the "Counted" example - a classic C++ primer example featuring a struct that counts its own instances - cannot be implemented in D.

In C++ the counted example looks like this:

struct Counted {
    static unsigned count;
    unsigned myCount;
    Counted() { myCount = count++; }
    Counted(const Counted& rhs) { myCount = count++; }
    Counted& operator=(const Counted& rhs) {
       // no writing to myCount
       return *this;
    }
    ~Counted() {
       --count;
    }
}

In D there's no chance to write Counted because you can always create Counted objects without executing any code.

struct Counted {
    static uint count;
    uint myCount;
    this() { myCount = count++; }       // ERROR
    this(this) { myCount = count++; }
    ref Counted opAssign(Counted rhs) {
       // no writing to myCount
       return this;
    }
    ~this() {
       --count;
    }
}

This being a toy example, I wonder whether there are much more serious examples that would be impossible to implement within D.


Andrei

I agree it's a very annoying limitation, but I believe there is a rationale behind it.

Imagine a class that aggregates a struct:

struct Foo { ... }

class Bar
{
   Foo foo;
   this() {}
   // ...
}

The class object construction is now consists of two steps:

1) memcpy the Bar.classinfo.init
2) call __ctor()

Unlike C++, there is no stage at which class members are being initialized, and it simplifies things quite a lot.

Think about the following: what happens if structs will be allowed default ctors? How to avoid double initialization?

You may end up with design very similar to C++ in this case (including an initialization list).

Some problems could be solved with an enforced explicit initialization of each member. That's the only way I see now that would avoid double initialization of a struct in presence of default and non-default ctors:

struct Scoped(T) // I like this name a lot more than InPlace, and especially InSitu
{
    this(Args...)(Args args)
    {
        T obj = cast(T)data.ptr;
        obj.__ctor(args);
    }

ubyte[T.classinfo.init.length] data = T.classinfo.init; // I believe this should work at compile-time, since classinfo is immutable
}

class Foo
{
    this() {}
    this(int i) {}
}

class Bar
{
    Scoped!(Foo) foo;
    this()
    {
// foo is already constructed by now (default ctor is called) which is cool
         // but what if I need to initialize it with some other ctor?
         foo = Scoped!(Foo)(42); // double initialization
    }

    int doesntNeedToBeInitializedExplicitlyInACtor = 17;
}

Enforcing explicit initialization of each member (unless its value is set at the declaration, as in the example above) is gracefully solving this issue.

Also think about exception safety: what if an exception is thrown in a class ctor - should the dtors be invoked on initialized members or not? If yes, in what order? D doesn't enforce initialization order, so it's a bit tricky to determine what members are already initialized and need to be destroyed (with a dtor call) efficiently. C++ handles issues like this very well IMO. I believe D should also have a sound solution to this problem.

Reply via email to