Jump to page: 1 2 3
Thread overview
this() not executing code on structs
Oct 21, 2009
zoli
Oct 21, 2009
dsimcha
Oct 21, 2009
zoli
Oct 21, 2009
dsimcha
Oct 21, 2009
Denis Koroskin
Oct 21, 2009
Bartosz Milewski
Oct 21, 2009
Leandro Lucarella
Oct 23, 2009
Don
Oct 23, 2009
Denis Koroskin
Oct 23, 2009
Denis Koroskin
Oct 24, 2009
Denis Koroskin
Oct 21, 2009
Bartosz Milewski
Oct 21, 2009
Rainer Deyke
Oct 21, 2009
grauzone
Oct 21, 2009
dsimcha
Oct 22, 2009
grauzone
Oct 22, 2009
Denis Koroskin
Oct 22, 2009
grauzone
Oct 23, 2009
grauzone
October 21, 2009
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
October 21, 2009
Andrei Alexandrescu 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.
> 

If the new "operator" for the class is redesigned to have multiply allocation types
(allocate on heap, allocate on stack, etc.?), there is no main drawback of using classes instead of structs for small objects( like vec2, vec3) as well.
For function parameters, use the reference for in/out and, use a copy of the original for value parameters ( personally I hate codes where they change the input parameters when they are value types - for larger codes it's hard to track what's going on in the function, and whether it's the original or the modified value...)

I don't think if there's any reason to use struct with member functions from that point, use class instead. And hence struct can be the good old plain data structures again, to give some semantics to a block of memory.

But if the scoped allocation is gone, the struct will require much more construction powers, and will be used more (ex vec2, vec3, etc.)

October 21, 2009
== Quote from zoli (zoli@freemail.hu)'s article
> Andrei Alexandrescu 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.
> >
> If the new "operator" for the class is redesigned to have multiply allocation types (allocate on heap, allocate on stack, etc.?), there is no main drawback of using
classes instead of structs for small objects( like vec2, vec3) as well.

Yes there is.  How about the cost of storing vtbl and monitor?  Actually, just
last night I was in a corner case where this mattered (as well as needing fine
control over allocation), so I ended up rolling my own polymorphism with structs.
 This is not a complaint about the design of D classes.  They do exactly what they
should do:  Handle the first 99% of use cases well and make people in obscure
corner cases roll their own from lower level primitives rather than succumbing to
the inner platform effect.  However, I think it would be ridiculous not to allow
simple syntactic sugar like non-polymorphic member functions on structs.

> I don't think if there's any reason to use struct with member functions from
that point, use class instead. And hence struct can be the good old plain data structures again, to give some semantics to a block of memory.

Ok, simple example that's not a corner case:  How about storing one inline in an array?
October 21, 2009
> > If the new "operator" for the class is redesigned to have multiply allocation types (allocate on heap, allocate on stack, etc.?), there is no main drawback of using
> classes instead of structs for small objects( like vec2, vec3) as well.
> 
> Yes there is.  How about the cost of storing vtbl and monitor?  Actually, just

I keep forgetting those (4-8) extra bytes per instance :)
And what about the final classes (with no ancestors)? Is there a vtbl for them since they cannot be derived and they have no virtual parent to inherit members from ? Or the Object with opHash and the other operators are virtual by nature in all classes ? (sorry, I don't know D that much)

Well, actually it seems as struct is a much better and clear solution. In that case I vote for this().

October 21, 2009
== Quote from zoli (galap@freemail.hu)'s article
> > > If the new "operator" for the class is redesigned to have multiply
allocation types
> > > (allocate on heap, allocate on stack, etc.?), there is no main drawback of using
> > classes instead of structs for small objects( like vec2, vec3) as well.
> >
> > Yes there is.  How about the cost of storing vtbl and monitor?  Actually, just
> I keep forgetting those (4-8) extra bytes per instance :)
> And what about the final classes (with no ancestors)?

Even final classes w/ no explicit ancestors are implicitly subclasses of Object, which defines virtual functions.  Therefore, *all* class instances must have a vtbl pointer.
October 21, 2009
On Wed, 21 Oct 2009 20:15:16 +0400, Andrei Alexandrescu <SeeWebsiteForEmail@erdani.org> 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.
October 21, 2009
Andrei Alexandrescu Wrote:

>     this() { myCount = count++; }       // ERROR

It's worse than that. Try this:

struct foo {
       this(int dummy = 0) { writeln("Default constructor");}
}

foo x = foo();

Nothing gets printed. If default constructors are disallowed, so should constructors with all parameters defaulted.
October 21, 2009
This means that no non-trivial invariant may be defined for a struct. In most cases it doesn't matter, but it might be a problem with the smart pointer family.

For instance, refcounted may not assume that the shared refcount pointer has been allocated. Without this invariant all refcounted operations must have additional code that tests for null and does the right thing (which is not always obvious).
October 21, 2009
Bartosz Milewski, el 21 de octubre a las 16:33 me escribiste:
> Andrei Alexandrescu Wrote:
> 
> >     this() { myCount = count++; }       // ERROR
> 
> It's worse than that. Try this:
> 
> struct foo {
>        this(int dummy = 0) { writeln("Default constructor");}
> }
> 
> foo x = foo();
> 
> Nothing gets printed. If default constructors are disallowed, so should constructors with all parameters defaulted.

Fill bug reports in bugzilla, please!

-- 
Leandro Lucarella (AKA luca)                     http://llucax.com.ar/
----------------------------------------------------------------------
GPG Key: 5F5A8D05 (F8CD F9A7 BF00 5431 4145  104C 949E BFB6 5F5A 8D05)
----------------------------------------------------------------------
Los jóvenes no son solo brazos que nos cargan... También se los puede
mandar a la guerra, que es su obligación.
	-- Ricardo Vaporeso
October 21, 2009
Andrei Alexandrescu 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.

Any struct that uses dynamic memory allocation internally.

Any struct that registers its instances in some dort of global registry.

'ValueType!T', which turns reference type 'T' into a value type.

A 'ScopedLock' variant that uses a single global mutex.

RAII wrappers over global initialization/deinitialization functions.

A 'UniqueId' struct that initializes to a value that is guaranteed to be distinct from the vale of any other 'UniqueId' used by the program.


-- 
Rainer Deyke - rainerd@eldwood.com
« First   ‹ Prev
1 2 3