Nested Structs (Solution)

js.mdnq js_adddot+mdng at gmail.com
Thu Dec 13 16:07:25 PST 2012


On Thursday, 13 December 2012 at 20:56:05 UTC, Mafi wrote:
> On Wednesday, 12 December 2012 at 22:58:47 UTC, Max Samukha 
> wrote:
>> On Wednesday, 12 December 2012 at 22:19:54 UTC, js.mdnq wrote:
>>> Also, I initially tried to do
>>>
>>> 	B!(A.b1.offsetof) b1;
>>>
>>> a'la
>>>
>>> http://forum.dlang.org/thread/mailman.2627.1355335532.5162.digitalmars-d@puremagic.com
>>>
>>> but dmd 2.060 crashes, which is why I moved on to using a 
>>> static if.
>>
>> That's 'outer', yet another half-done D feature. It should be 
>> implemented for nested structs.
>
> It's not half-done. Structs are supposed to be POD. They should
> contain exactly the members that you list; no more hidden stuff.
> See TDPL 7.1.8 (p.262): "Unlike classes netsted within classes,
> nested structs and nested classes within structs dont contain 
> any
> hidden member outer - there's no special code generated."
> I am not sure about classes within structs but nested structs
> should behave like they do IMHO.
>
> Mafi

True nested structs do not contain hidden data. The data is 
attached. There is no reason why a nested struct should not have 
access to it's outer class EXCEPT to prevent copy issues. If one 
is simply using structs to wrap types inside a class for 
encapsulation then it should be able to access the class 
members... else it becomes somewhat of an orphan without much use.

Basically it's more of a matter of logical grouping. A class'es 
fields are laid out in memory sequentially. A struct field is 
just part of that layout.

01 Field
02 Field
03 Struct Field
    03.1 Field
    03.2 Field
04 Field
etc...

which can also be seen as

01 Field
02 Field
03 Field
04 Field
05 Field

the struct field's this ptr is always some fixed offset into the 
class, so it is easy to get the outer by a simple calculation(as 
I have demonstrated with the code I posted).

The usefulness, again, is that one can encapsulate(go from a 
class of nothing but fields to a class of structs) many pieces of 
functionality. But if the encapsulation prevents one from 
interaction with outer class members, then it can be too 
limiting. Nested classes solve this problem by storing a ptr to 
the class, that is ok, right? (it is necessary, my method won't 
work with classes since they are reference types)

But if it's ok with classes then why are nested structs required 
to be orphans OR store a ptr to the class, which bloats the 
struct?

The only thing "my" method does is stop one from having to store 
such a pointer to the class, which, for many cases, is redundant.

So, if you want to argue against my method, really you should 
then be arguing against the case

class A
{
    struct B{
      A a;
    }
}

because it is pretty much exactly the same except for one point: 
If we do away with the explicit ptr storage in the struct to save 
memory, we can't arbitrarily copy the nested structs to similar 
struct types. Alias this and opAssign help solve these problems 
though.

I think my code solves a very real problem of providing efficient 
encapsulation of class data and functionality. One doesn't have 
to have one large huge class full of tons of fields and methods 
but can break a class down into chunks and build a hierarchy, 
similar to inheritance, but without the overhead. (a sort of 
slimed down inheritance.

For my case, why I went down this pass, was to solve this problem:

class A {
    byte X;
    .. other stuff...
}

Suppose I need to "intercept" assignments to X to, say, mask the 
value or whatever. How? I could create a new type. I could use an 
external struct.

struct bbyte { byte x; alias x this; }

class A {
    bbyte X;
    .. other stuff...
}

but suppose X is always associated with A, it's never found alone 
so to speak. Suppose, I need to access something from A while in 
X:



class A {
    bbyte X;
    string Name;
    .. other stuff...
}


BUT, now bbyte is 4 times larger than it needs to be just to 
access A. Since I said X will always be part of A, we can do this:


class A {
    struct bbyte
    {
       byte x; alias x this;
       A a;
       void Do() { writeln(a.Name); }
    }
    bbyte X;
    string Name;
    .. other stuff...
}

which is simply more logical but nothing different.

BUT!!!! NOW! we can reduce the size of bbyte significantly:

class A {
    struct bbyte
    {
       byte x; alias x this;
       void Do() { writeln(outer.Name); }
    }
    bbyte X;
    string Name;
    .. other stuff...
}


To me, this is a huge improvement. Of course, to do this in D one 
has to "hack" it up.

As long as we don't create orphaned bbytes they always will have 
a parent. Since we are using them as "wrappers" we don't care all 
that much since any time they are used as "orphans" they are 
really just the value type they wrap. (through alias this and 
opAssign/cast)

In some sense, these types are not value types but simply part of 
the class but have compiler like encapsulation.

In any case, Just because everyone won't find it useful does not 
mean that it is not useful. Even if just a few people need some 
construct like this then it would be worthwhile to have. It 
really sucks when you need something and you don't have it.












More information about the Digitalmars-d mailing list