www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Nested Structs

reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
The follow code demonstrates a solution to the nested structs. It 
is far from optimal but maybe it is possible to use as a base 
solution:

http://dpaste.dzfl.pl/7f086694

What the code demonstrates is that a nested struct can use the 
parent class(or struct) without having to store a reference to 
it. This, then, does not waste space to store a reference that is 
not needed.

The issue is we must hard code the offsets of the struct objects 
inside the class(something the compiler already knows). Any 
modification of the class will require changing the values.

In fact, the compiler could do all the dirty work behind the 
scenes pretty efficiently(all compile time offset computation) 
and not require a templated struct(which seems to slow down the 
compilation of the program significantly... unless my computer is 
acting up).

In any case, the code is a first step. The next being computing 
the offsets of the structs automatically which will give a useful 
method of solving the problem until, hopefully, something is 
implemented directly in the compiler.














Code:

module main;

import std.stdio;

class A {
	A a;

	struct B(int ofs)  {
		int Value;
		A Parent()
		{
			auto p = cast(void *)&this - ofs;
			return cast(A)(p);
		}		
	}

	B!(12) b1;
	B!(12 + 4) b2;
	string Name;

	this()
	{
		Name = "Class";
		a = this;

	}
}



int main(string[] argv)
{




	auto asize = A.classinfo.init.length;
	auto bsize = A.B!(0).sizeof;
	A a = new A();
	auto x = a.b1.Parent();
	auto y = a.b2.Parent();
	
Dec 11 2012
parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
Here is a solution I came up with that seems to work fine and 
does not require hard coding any values. Hence, it is useable. 
Unfortunately it looks clunky and is: (and it would be nice to 
speed up the the method call and possible code it in such a way 
that if D directly supports this in the future it will be easy to 
update)


http://dpaste.dzfl.pl/64025e0a


The idea is rather simple: We pass the offset of the struct 
object to itself as a template parameter. This allows the struct 
to calculate where it is at relative to the parent, which then 
allows it to access the members of the parent. This is quite easy 
to do and no issue. (except, of course, copying the struct will 
potentially invalidate it's parent)

The problem is actually calculating the offsets of these structs 
in the class(which are passed to the struct). Hard coding is a no 
go and using offsetof will not work because of forward 
referencing(you need to know the size of the type to find out the 
offsets

To do this, kinda hackish, but works, is we template the class 
containing the structs. This allows us to conditionally create 
the class. A!0, creates a class that we can then use to get the 
offsets. The forward references are no longer since we use A!0 
which passes dummy offsets to the structs. A!x, for x > 0, then 
calculates the offsets of the structs using A!0 as a blueprint. 
A!0 has no offsetof so no forward referencing. A!1 uses offsetof 
for A!0, so, again, no forward referencing. Since passing offsets 
do not change the size we should expect A!1 to have the layout as 
A!0.

The "trick" here is:

static if (dummy == 0) B!(0) b1; else  B!(A.b1.offsetof) b1;


As you can see in the code, it's quite messy and ideally should 
look like a normal nested struct inside a class without any 
template parameters being used.

I'm hoping someone can come up with an elegant way to wrap this 
neatly into a package that can be used as any normal nested 
struct, or close.

Thanks...














Code:

module main;

import std.stdio;




class cAt(int dummy)
{
	alias cAt!0 A;
	struct B(int ofs)  {
		int Value;
		A Parent()
		{
			auto p = cast(void *)&this - ofs;
			return cast(A)(p);
		}		
	}

	static if (dummy == 0) B!(0) b1; else  B!(A.b1.offsetof) b1;
	static if (dummy == 0) B!(0) b2; else  B!(A.b2.offsetof) b2;

	string Name;

	this()
	{
		Name = "Class";
	}
}
alias cAt!1 A;

int main(string[] argv)
{
	

	auto asize = A.classinfo.init.length;
	auto bsize = A.B!(0).sizeof;
	A a = new A();
	auto x = a.b1.Parent();
	auto y = a.b2.Parent();
	
	auto s1 = A.init.b1.offsetof;
	auto s2 = A.init.b2.offsetof;

	auto a_ptr = cast(void*)a;
	auto x_ptr = cast(void*)x;
	auto y_ptr = cast(void*)y;

	assert(a_ptr == x_ptr);
	assert(x_ptr == y_ptr);

	getchar();
	return 0;
}
Dec 12 2012
parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
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.
Dec 12 2012
parent reply "Max Samukha" <maxsamukha gmail.com> writes:
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.
Dec 12 2012
next sibling parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
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.
Half done? Has it even been implemented at all? In any case my method seems to provide a solution to the problem in the mean time. I have updated the code to work correct(had a small bug) and it uses an alias to make it easier to update in the future if D finally does support this construct in full. http://dpaste.dzfl.pl/64025e0a If outer is the appropriate keyword then Parent can easily be changed. Little would have to be done to the structs to make it work. In fact, the method should work if D ever decides to support such a feature with little change. Compile times could be increased though by removing the templating, but unfortunately that might require a lot of work. A mixin might simplify it.
Dec 12 2012
parent reply "Max Samukha" <maxsamukha gmail.com> writes:
On Thursday, 13 December 2012 at 00:02:01 UTC, js.mdnq wrote:
 Half done? Has it even been implemented at all?
http://dlang.org/class.html#nested It is implemented for nested classes but not structs.
 In any case my method seems to provide a solution to the 
 problem in the mean time. I have updated the code to work 
 correct(had a small bug) and it uses an alias to make it easier 
 to update in the future if D finally does support this 
 construct in full.

 http://dpaste.dzfl.pl/64025e0a

 If outer is the appropriate keyword then Parent can easily be 
 changed.
The name doesn't matter. I just wanted to annoy Walter again with yet another bit of evidence that completeness of a feature is important even if it doesn't seem to have an obvious use case. People will do what is logical and end up with hacks like that.
 Little would have to be done to the structs to make it work. In 
 fact, the method should work if D ever decides to support such 
 a feature with little change. Compile times could be increased 
 though by removing the templating, but unfortunately that might 
 require a lot of work. A mixin might simplify it.
Dec 12 2012
parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
On Thursday, 13 December 2012 at 00:37:16 UTC, Max Samukha wrote:
 On Thursday, 13 December 2012 at 00:02:01 UTC, js.mdnq wrote:
 Half done? Has it even been implemented at all?
http://dlang.org/class.html#nested It is implemented for nested classes but not structs.
 In any case my method seems to provide a solution to the 
 problem in the mean time. I have updated the code to work 
 correct(had a small bug) and it uses an alias to make it 
 easier to update in the future if D finally does support this 
 construct in full.

 http://dpaste.dzfl.pl/64025e0a

 If outer is the appropriate keyword then Parent can easily be 
 changed.
The name doesn't matter. I just wanted to annoy Walter again with yet another bit of evidence that completeness of a feature is important even if it doesn't seem to have an obvious use case. People will do what is logical and end up with hacks like that.
 Little would have to be done to the structs to make it work. 
 In fact, the method should work if D ever decides to support 
 such a feature with little change. Compile times could be 
 increased though by removing the templating, but unfortunately 
 that might require a lot of work. A mixin might simplify it.
Ok, maybe thats why my posts have not received any attention except by you ;) Checking the sizes of classes, it seems that inner classes store a ptr to the outer class. This is exactly what I am trying to avoid. So, In fact, my method seems to save more space if I'm not mistake. Of course, they are not as safe since they don't keep the outer class ptr with them as they move around. http://dpaste.dzfl.pl/b20e1412 It's strange that in my case the size of Q is 24 rather than 20 on dpaste. In any case F should be 8 rather than 12 which suggests it is storing outer as a ptr inside the class. When using my code, the struct size is 1 (the size of an empty struct) and the class size is 20. Not sure why a nested class increases the size of the parent class on my setup but not dpaste, but regardless, obviously D is storing a ptr in the classes, which are not necessary in many cases. I think I have an idea how to make it a bit more elegant that I will try and if it works I'll start using it in my code.
Dec 12 2012
parent reply d coder <dlang.coder gmail.com> writes:
 The name doesn't matter. I just wanted to annoy Walter again with yet
 another bit of evidence that completeness of a feature is important even if
 it doesn't seem to have an obvious use case. People will do what is logical
 and end up with hacks like that.
I believe it *is* an obvious use case. In D we use structs when we want to save on memory. And creating a class environment to encapsulate a system of structs is an obvious use case. In fact I am in this situation and thanks to your hack, I will try to use it for the time being.
  Little would have to be done to the structs to make it work. In fact,
 the method should work if D ever decides to support such a feature with
 little change. Compile times could be increased though by removing the
 templating, but unfortunately that might require a lot of work. A mixin
 might simplify it.
Ok, maybe thats why my posts have not received any attention except by you ;)
Let us trend it ;-) Regards - Puneet
Dec 12 2012
parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
On Thursday, 13 December 2012 at 01:57:26 UTC, d coder wrote:
 The name doesn't matter. I just wanted to annoy Walter again 
 with yet
 another bit of evidence that completeness of a feature is 
 important even if
 it doesn't seem to have an obvious use case. People will do 
 what is logical
 and end up with hacks like that.
I believe it *is* an obvious use case. In D we use structs when we want to save on memory. And creating a class environment to encapsulate a system of structs is an obvious use case. In fact I am in this situation and thanks to your hack, I will try to use it for the time being.
  Little would have to be done to the structs to make it work. 
 In fact,
 the method should work if D ever decides to support such a 
 feature with
 little change. Compile times could be increased though by 
 removing the
 templating, but unfortunately that might require a lot of 
 work. A mixin
 might simplify it.
Ok, maybe thats why my posts have not received any attention except by you ;)
Let us trend it ;-) Regards - Puneet
I've created some mixins that make life a little easier: http://dpaste.dzfl.pl/64025e0a It makes it easier to refactor/rename. mixin(StructNestType!("B", "_A!(", "_NestLevel", "b1")); note the weird syntax of the class "_A!(", this is to allow when A has more than one template parameter. Note that if you might want to create your constructors only for _NestLevel = true. e.g., use a static if. This way you don't have lame casting issues trying to cast one struct to another because they differ on the _NestLevel. The opAssign is used to copy one type to another. One can only make assignments from objects of the same parent type. (else the parent will be wrong) Using alias this will help avoid other problems. (which is sort of the point) These nested structs are meant to wrap values to add encapsulated functionality. As far as the outside world is concerned they are suppose to be just normal values, with, potentially, some additional functionality. I believe that the code is now usable in that it accomplishes the task(zero overhead and small performance hit) and can be refactored somewhat easily. The syntax is far from pretty but if D ever gets such a feature it shouldn't be too hard to update the code(for large projects, one could make a simple regex parser for it to update everything). It would be nice if D implements such a feature because it will look more natural.
Dec 13 2012
parent "Rob T" <rob ucora.com> writes:
On Thursday, 13 December 2012 at 08:55:44 UTC, js.mdnq wrote:
 It would be nice if D implements such a feature because it will 
 look more natural.
It sure would be nice. With D, you should not have to mess around with a pointer like this. Anyway, thanks for your efforts, we at least have a "hack" solution to try out. BTW, there should be bug report or something about fixing the incomplete "outer" feature. Anyone looked yet? --rt
Dec 13 2012
prev sibling parent reply "Mafi" <mafi example.org> writes:
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
Dec 13 2012
parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
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.
Dec 13 2012
parent reply "Rob T" <rob ucora.com> writes:
I guess the complicating factor is that a nested struct could not 
be copied out of one class into another of a different type, so I 
can see why it's not implemented. The compiler would have to 
prevent copies out, or the language would have to be modified to 
allow nesting but with some new convention to make it clear that 
the struct is nested, not sure if it's worth it though, you can 
always make do without it.

--rt
Dec 13 2012
next sibling parent "js.mdnq" <js_adddot+mdng gmail.com> writes:
On Friday, 14 December 2012 at 06:27:39 UTC, Rob T wrote:
 I guess the complicating factor is that a nested struct could 
 not be copied out of one class into another of a different 
 type, so I can see why it's not implemented. The compiler would 
 have to prevent copies out, or the language would have to be 
 modified to allow nesting but with some new convention to make 
 it clear that the struct is nested, not sure if it's worth it 
 though, you can always make do without it.

 --rt
This is why I suggested a new type earlier so that it is obvious something is different. Since my initial reason for needed such a structure was to wrap built in types to provide functionality it's not a big deal because one can override opAssign and use alias this. Doing so basically lets the "outside world" use the type as if it were the value type and when copied they are copying the built in types rather than the structs. If it's a big deal it is not hard to create a duplicate struct with the added outer pointer similar to what is done with nested classes and then just copy over the data from one to the other. This will work fine except the need to create two structs with different, although, now that I think about it, I could potentially use a static if to create such a dual struct which would then help avoid orphans. When an assignment from the struct is used it will return, not itself, but either it's value(if a wrapper) or this new struct which is identical except containing a ptr to it's parent. I'll try to add it and see how it works.
Dec 13 2012
prev sibling parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
http://dpaste.dzfl.pl/64025e0a

contains updated code. When the offset of the struct is 0 it 
contains an actual ptr to the class(the standard way) and hence 
can be "orphaned". When the offset is not 0 then it is part of a 
class object and can use a calculation to get the parent.

Both methods work somewhat easily together as demonstrated by the 
last example.

Unfortunately it seems we have to make an assignment.

A.B!(0) b = a.b1;

does not work but

A.B!(0) b;
b = a.b1;

does. Not sure if there is some way to make the first work.
Dec 13 2012
parent reply "Rob T" <rob ucora.com> writes:
On Friday, 14 December 2012 at 07:39:33 UTC, js.mdnq wrote:
 Unfortunately it seems we have to make an assignment.

 A.B!(0) b = a.b1;
This one tries to call a constructor that takes in a typeof(a.b1). Do you have a constructor for it?
 does not work but

 A.B!(0) b;
 b = a.b1;
 does. Not sure if there is some way to make the first work.
--rt
Dec 14 2012
parent reply "js.mdnq" <js_adddot+mdng gmail.com> writes:
On Friday, 14 December 2012 at 09:47:18 UTC, Rob T wrote:
 On Friday, 14 December 2012 at 07:39:33 UTC, js.mdnq wrote:
 Unfortunately it seems we have to make an assignment.

 A.B!(0) b = a.b1;
This one tries to call a constructor that takes in a typeof(a.b1). Do you have a constructor for it?
 does not work but

 A.B!(0) b;
 b = a.b1;
 does. Not sure if there is some way to make the first work.
--rt
oh, thats what it does?!?!?! ;) Ok, well, then it should be easy to add.
Dec 14 2012
parent reply "Rob T" <rob ucora.com> writes:
There's an interesting discussion going on that may be related to 
this subject.

http://forum.dlang.org/thread/mailman.2705.1355596709.5162.digitalmars-d puremagic.com

Note the definition with the "hidden reference frame" baggage, 
and to get rid of the extra baggage use "static struct".

The reference frame mentioned however is not for a parent class 
or struct, but maybe its similar enough that nested structs could 
possibly be extended with a hidden reference to the parent class 
or struct.

It all seems debatable though with various levels of 
complications associated with implementing a nest struct.

--rt
Dec 15 2012
parent "js.mdnq" <js_adddot+mdng gmail.com> writes:
On Sunday, 16 December 2012 at 01:04:44 UTC, Rob T wrote:
 There's an interesting discussion going on that may be related 
 to this subject.

 http://forum.dlang.org/thread/mailman.2705.1355596709.5162.digitalmars-d puremagic.com

 Note the definition with the "hidden reference frame" baggage, 
 and to get rid of the extra baggage use "static struct".

 The reference frame mentioned however is not for a parent class 
 or struct, but maybe its similar enough that nested structs 
 could possibly be extended with a hidden reference to the 
 parent class or struct.

 It all seems debatable though with various levels of 
 complications associated with implementing a nest struct.

 --rt
I was just looking at that and thought it was pretty interesting and that maybe the technique I used might apply. But after second thought I don't think it will work because copies of the struct are passed around instead. Which, if you recall, is still somewhat of an issue with the nested struct model I'm using. Of course, with overrides of opAssign, this, and opCast one could possibly avoid this issue too. It seems to me that the stack frame has taken place of the class object. When the voldemort struct is returned from the function, the outer values come from the stack frame(rather than the class object). When you return the voldemort struct, it also orphans it in a similar way it does for the class object. That is, unless the stack frame is always correct, which I haven't convinced myself of yet. Although I have a solution using the nested struct idea directly(simply use a functor), I think will work... I'll post it on the other thread. (this might imply that the voldemort construct is pretty solid, or can be made to be so)
Dec 16 2012