www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Limited Semi-PolyMorphic (LSP) structs?

reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
  Been a while and out of the loop, I need to get my hands dirty 
in the code again (soon). Anyways, let's get to the matter at 
hand as I'm thinking about it. I'm working on some code (or will 
work on code again) that could use a polymorphic type, but at the 
end it will never be shared externally and can never extend 
beyond 'Known' types. (and the 'types' are behavior 95% of the 
time)


  True polymorphism will let me compile a code to work with say 
'Object' and you can plug in any code and pass it an object and 
it will be happy, or inherit and work with it. These are great as 
classes. But I want to throw a wrench in the gears, I want to use 
structs.


  I know you're asking, Why? Well the data structure I'll be 
working with have records in the hundreds of thousands, but most 
of the time looking at/sorting them I don't necessarily need to 
allocate memory (or really even interact with them beyond 
sorting). Most of the time the OO aspect is just behavior and the 
data between them never changes (behave this way because you're a 
type XXX). It can take a very long time to unpack everything 
(when you never needed to unpack in the first place, or allocate 
and then de-allocate immediately, probably without recycling).


  Alright, structs can't inherit because they don't have an object 
pointer to go to their parent structures that they are connected 
to. If we eliminate the pointer and the overhead on some of that, 
we bring it down to the following:

  To be fully polymorphic you need to:
  * Be able to tell what type it is (Probably enum identifier)
  * Extended types cannot be larger than the base/original (or the 
base has to hold extra data in reserve for it's other types).


  When can this LSP be applicable?
  * When a type can be changed on the fly with no side effects 
other than intended (This 'text' struct is now 'left aligned text 
object')
  * When allocations/pointers aren't needed (why fully build the 
object at all?)
  * When it won't be extended beyond the current type... (only a 
few fixed subtypes)
  * When teardown/deallocation/~this() isn't needed (never 
allocates? Never deallocates! Just throw it all away! POD's are 
perfect!)
  * When you have no extra 'data' to append to a class/struct and 
only override methods/behavior (Encryption type switch from DES 
to BlowFish! Same API/methods, otherwise nothing really changed)
  * When you need a half step up from structs but don't need full 
classes/objects

  Limitations: Hmmm..
  * LSP's cannot be a template or mixin (they can call on them)
  * Known at compile-time
  * Only one of them can handle setup/teardown.


  Now, I wonder. What if we make it so it CAN inherit? We need 3 
examples, where something is in A, A&B, and B. We will get to 
that. iA and iB are the class/structs and A/B/C are the 
methods/functions. So...

//original based on...
class iA {
   void A();
   void C();
}
class iB : iA {
   void A();
   void B();
}

  Breaking them down for structs we have:

struct base_AB { //the 'manager' that does redirection and known 
size
   char type = 'A'; //polymorphic type identifier, for simplicity
   //shared 'parent' baggage ie -> iA
   //baggage space for iB

   //Exists in both iA & iB
   void A() {
     switch(type) {
       case 'A': (cast(iA) this).A(); break;
       case 'B': (cast(iB) this).B(); break;
       default: throw new Exception("method A() invalid for struct 
type:" ~ type);
     }
   }

   //B only exists in inherited
   void B() {
     if (type == 'B') (cast(iB) this).B();
     else throw new Exception();
   }

   //C exists everywhere
   void C() { (cast(iA) this).C(); }
}


  So far the base is easy to make, now however as for how iA and 
iB interact with eachother... Ignoring infinite loops problems:

struct iA {
   char type;
   //baggage - Size must be identical to base_AB

   void A() {
     A();
     //B(); /*iA doesn't really know about B so can't call it from 
here... not safely anyways*/
     C();
   };
}

   Now, C() can be called regardless of the type (no checks 
needed). iB.B() can also be called anywhere in iB with no 
problems, but it would be simpler if all calls regardless went to 
the base and let the base handle it. (Optimizations would 
probably remove unneeded checks later).

struct iB {
   char type;  //and other baggage

   void A() {
     A(); // actually: (cast(base_AB) this).A();
     B(); // actually: (cast(base_AB) this).B();
     C(); // actually: (cast(base_AB) this).C();
   }
   void B() {
     //ditto as A()'s
   }
}


  This can probably be easily done having a different naming set 
which handles the redirection, but the names and boiler-plate 
seems unnecessarily large and verbose for a simple task; 
Optimization will likely remove the excess unneeded portions in 
final binary form anyways.

//different name examples:
  struct base_AB {
   char type;  //and other baggage

   void A() {
     switch(type) {
       case 'A': (cast(iA) this).realA(); break;
       case 'B': (cast(iB) this).realB(); break;
       default: throw new Exception("method A() invalid for struct 
type:" ~ type);
     }
   }

struct iB {
   char type;
   //bagage

   //boilerplate
   void A() { cast(base_AB) this.A(); }
   void B() { cast(base_AB) this.B(); }
   void C() { cast(base_AB) this.C(); }

   void realA() {
     A();
     B(); //Path really is now: this.B() -> base_AB.B() -> 
iB.realB();
     C();
   }

   void realB() {  /* ... */  }
}


   Now how would one build it without all the extra boilerplate 
overhead (also preferably with fewer confusing elements) Three 
ideas are coming to mind.

   1) Template. This can work but it's a lot of overhead, a lot of 
work and is hard to keep track of. I have an experimental version 
that semi-works but can't be used seriously. My project sorta 
relying on this has come to a halt for the moment. It's also very 
finickle where data (and struct order) are involved and refuses 
to compiler out of a certain order. Also for calling functions 
that exist in current structs require you to forcibly call 'this' 
or go around it in order for it to do the redirections elsewhere.


   2) UDA's could be possible, if you tag a struct as belonging 
and then scan/add the appropriate code. But this seems like a lot 
of work and overhead. Probably won't work, plus a new UDA for 
each type vs known fixed extensions. Seems like a lot of extra 
work. Maybe?

   struct base_AB  LSP(iA, iB) {}
   struct iA  LSP_base(base_AB) {}
   struct iB  LSP_base(base_AB) {}


   3) Compiler. This seems the most likely (and 95% of it is 
already in place for classes), but i can't implement it myself 
easily; Plus i am not sure if Walter or Andrei would want/allow 
it. If you squint your eyes you can see it looks very similar to 
the original struct/class formula for C++ (without using 
pointers/new), except that the total size reserved is known/set 
in advance to incorporate all types (fixed size, no 
growing/shrinking by casting).


  Perhaps introducing a third base type (so struct, classes and 
LSP's)? This might help but depends on the amount of extra 
complexity would be needed, or how many bugs it could 
introduce... or incorporate it as the normal struct but is a 
experimental/disabled feature unless you apply a certain 
attribute (or set of?)


   If there's a suggestion of how to make this work and feel more 
natural (class-like without allocation/pointers) then 
suggestions/ideas would be nice. It's possible this type of 
feature is just not wanted. But I'm willing to bet there's quite 
a few places this type of feature/set could be utilized when you 
consider what it can be used for.
Aug 26 2013
next sibling parent reply "qznc" <qznc web.de> writes:
On Monday, 26 August 2013 at 11:20:17 UTC, Era Scarecrow wrote:
  Been a while and out of the loop, I need to get my hands dirty 
 in the code again (soon). Anyways, let's get to the matter at 
 hand as I'm thinking about it. I'm working on some code (or 
 will work on code again) that could use a polymorphic type, but 
 at the end it will never be shared externally and can never 
 extend beyond 'Known' types. (and the 'types' are behavior 95% 
 of the time)


  True polymorphism will let me compile a code to work with say 
 'Object' and you can plug in any code and pass it an object and 
 it will be happy, or inherit and work with it. These are great 
 as classes. But I want to throw a wrench in the gears, I want 
 to use structs.


  I know you're asking, Why? Well the data structure I'll be 
 working with have records in the hundreds of thousands, but 
 most of the time looking at/sorting them I don't necessarily 
 need to allocate memory (or really even interact with them 
 beyond sorting). Most of the time the OO aspect is just 
 behavior and the data between them never changes (behave this 
 way because you're a type XXX). It can take a very long time to 
 unpack everything (when you never needed to unpack in the first 
 place, or allocate and then de-allocate immediately, probably 
 without recycling).


  Alright, structs can't inherit because they don't have an 
 object pointer to go to their parent structures that they are 
 connected to. If we eliminate the pointer and the overhead on 
 some of that, we bring it down to the following:

  To be fully polymorphic you need to:
  * Be able to tell what type it is (Probably enum identifier)
  * Extended types cannot be larger than the base/original (or 
 the base has to hold extra data in reserve for it's other 
 types).


  When can this LSP be applicable?
  * When a type can be changed on the fly with no side effects 
 other than intended (This 'text' struct is now 'left aligned 
 text object')
  * When allocations/pointers aren't needed (why fully build the 
 object at all?)
  * When it won't be extended beyond the current type... (only a 
 few fixed subtypes)
  * When teardown/deallocation/~this() isn't needed (never 
 allocates? Never deallocates! Just throw it all away! POD's are 
 perfect!)
  * When you have no extra 'data' to append to a class/struct 
 and only override methods/behavior (Encryption type switch from 
 DES to BlowFish! Same API/methods, otherwise nothing really 
 changed)
  * When you need a half step up from structs but don't need 
 full classes/objects

  Limitations: Hmmm..
  * LSP's cannot be a template or mixin (they can call on them)
  * Known at compile-time
  * Only one of them can handle setup/teardown.


  Now, I wonder. What if we make it so it CAN inherit? We need 3 
 examples, where something is in A, A&B, and B. We will get to 
 that. iA and iB are the class/structs and A/B/C are the 
 methods/functions. So...

 //original based on...
 class iA {
   void A();
   void C();
 }
 class iB : iA {
   void A();
   void B();
 }
Hm, my try would be alias this for inheritance and function pointers for virtual methods. struct iA { void function(iA) A; void C(); } struct iB { iA _a; alias this = _a; void B(); } If you have multiple "subclasses" for iA, you can use a tagged-union from std.variant.
Aug 26 2013
parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Monday, 26 August 2013 at 12:42:43 UTC, qznc wrote:
 Hm, my try would be alias this for inheritance and function 
 pointers for virtual methods.

 struct iA {
   void function(iA) A;
   void C();
 }

 struct iB {
   iA _a;
   alias this = _a;
   void B();
 }

 If you have multiple "subclasses" for iA, you can use a 
 tagged-union from std.variant.
Hmmm. I haven't considered std.variant, and glancing at it (although I'll have to be more in depth later) it doesn't look like what I'm really going for. Besides the iA and iB are far too simplistic compared to what I really need to do. For the subclasses there's at least 10, some of it's just for comparison/ordering, but some of it is for pre/post operations. Let's see... HEDR (Header information) NAME (raw text) DATA (raw, or location x,y,z, or a mapping, etc) SCPT (text Script data) ENAM (data...) FLAG (data, search/handling different) NPCO (string, of item) NPCS (string, of spell) FRMR (Object id) INAM (ID for dialog) PNAM (linked list, previous dialog) NNAM (linked list, next dialog) These are just a few of them, Data actually has like 15+ entries although only a few of them require special cases. multiple alias this's won't work. There's also multiple cases where the override/subclass is merely to generate the NAME (or ID entry) differently ( partially for sorting but mostly informational purposes) so it may reference the original object but I don't want to have to do a bunch of forced casts (and clutter up code).
Aug 26 2013
prev sibling next sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
  It just came to mind that what I want is almost more a unioned 
struct. Perhaps this will come up with something closer to what I 
am really looking for.

assuming we could use a union, it would be closer to:

//type is the identifier of which one it's going to be using
union AB {
   iA ia;
   iB ib;

   void A(){}
   void B(){}
   void C(){}
}

struct iA {
   char type;
   void A();
   void C();
}
struct iB {
   char type;
   void A();
   void B();
}

  Now last i checked putting functions calls in unions wasn't 
really a good idea or supported, however we have a problem where 
iA and iB don't know about the union AB, and they will still try 
to call the local struct's data first (or the namespace) before 
anything else. This means if C() get's called, and the type is 
suppose to use iB, then when C() calls A(), it gets iA's A() and 
not iB's A().

  If however we try to do inner structs, then:
struct AB {
   char type;
   iA ia;
   iB ib;

   struct iA {
     void real_A() { //regardless what we call, the outer struct 
gets the calls
       A(); //line 9
       B();
       C();
     }
     void real_C() {
       A();
       B();
       C();
     }
   }

   struct iB {
     void real_A() {
       A();
       B();
       C();
     }
     void real_B() {
       A();
       B();
       C();
     }
   }

   void A() { //calls real_A or real_B
         switch (type) {
             case 'A': ia.real_A();
             case 'B': ib.real_A();
             default: throw new Exception("");
         }
   }
   void B() {
         switch (type) {
             case 'B': ib.real_B();
             default: throw new Exception("");
         }
   }
   void C() {
     ia.real_C();
   }
}

   So far this does seem to want to work, but last I checked I 
thought this was illegal, the reason being the inner structs 
needed a pointer to their outer parent which wasn't a class, at 
which point there was something just difficult about it.

test.d(9): Error: this for A needs to be type AB not type iA
test.d(10): Error: this for B needs to be type AB not type iA
test.d(11): Error: this for C needs to be type AB not type iA
test.d(14): Error: this for A needs to be type AB not type iA
test.d(15): Error: this for B needs to be type AB not type iA
test.d(16): Error: this for C needs to be type AB not type iA
test.d(22): Error: this for A needs to be type AB not type iB
test.d(23): Error: this for B needs to be type AB not type iB
test.d(24): Error: this for C needs to be type AB not type iB
test.d(27): Error: this for A needs to be type AB not type iB
test.d(28): Error: this for B needs to be type AB not type iB
test.d(29): Error: this for C needs to be type AB not type iB

  I recall there being something to help get around this, but a 
delegate didn't seem quite like the right answer...

  I really really don't want to rely on classes, passing a 
reference to the caller may work but adds unnecessary stuff (plus 
you have to explicitly call the referenced rather than having it 
feel like what it should). 'alias this' might help (as with my 
template attempt), but it quickly becomes a chore to keep up 
things and gets ugly fast.

  Mmm..
Aug 27 2013
prev sibling next sibling parent reply "Andre Artus" <andre.artus gmail.com> writes:
Era,

I haven't had time to go through your everything you wrote here 
but are you looking to create some form of discriminated union 
(AKA tagged union) using D structs?

Do you have a specific problem you need to solve, or are you just 
exploring the language?
Aug 27 2013
parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Wednesday, 28 August 2013 at 03:45:06 UTC, Andre Artus wrote:
 Era,

 I haven't had time to go through your everything you wrote here 
 but are you looking to create some form of discriminated union 
 (AKA tagged union) using D structs?

 Do you have a specific problem you need to solve, or are you 
 just exploring the language?
The tagged union struct does sorta match the basic idea. What i have is a structure that basically is just a single struct, however it's semi-polymorphic. (I guess that's obvious by the subject). What changes is only really behavior and rarely would it actually add more data, so inheriting and OOP fits it fairly well, however since structs can't inherit it becomes an issue. I can either inherit, or i can make the function(s) large and clunky and basically do it as C. ie: struct AB { char type; void A() { if (type == 'A') { } if (type == 'B') { } } } Etc. One of the biggest reasons to modify the behavior is for the records/subrecords i'm working on they need to build their identifiers based on what they are. Locations give X,Y, if it's an NPC, their name, if it's a texture the texture name, if it's a Dialog it needs to create a hash and append it to the ID afterwards. These are just a few. If i can inherit all the individual creations shorten to less than 10 lines per, otherwise i have a massive function. Here's one such function: Mind you this is from C and the code is already hard to follow. Inheritance simplifies and breaks it apart easier, but they don't NEED the full polymorphism & classes (and vTables), I'd rather make overriding structs and 'build_ID' per the individual (and unique cases), but I don't want to have to manage it that way. To note "memcmp(rec->name, "PGRD", N_LEN)" is the test for the polymorphism. Records and subrecords are identified by a 4 character string. [code] /*builds the record's ID name that will be used with sorting and search routines. Appends appropriate order information for DIAL's and makes all INFO's unique (known to have duplicates sometimes). */ void build_ID(Record *rec) { //build the ID string to identify this for speedy searches. memset(rec->ID, 0, REC_ID_SIZE); memcpy(rec->ID, rec->name, N_LEN); rec->ID[N_LEN] = '_'; SubRecord *sr = find_SubRecord(rec, "NAME", NULL); SubRecord *sr2 = find_SubRecord(rec, "INDX", NULL); if (sr) { int i = sr->size; if (sr->size > (REC_ID_SIZE - 6)) i = REC_ID_SIZE - 6; strncpy(rec->ID + N_LEN + 1, sr->data, i); //possible buffer overrun without limiter. } else if (sr2 && memcmp(rec->name, "SKIL", N_LEN) == 0){ struct flags_paired *flag_output[MAX_FLAGS]; flag_output[0] = NULL; //clear it out. get_str_flag(flag_output, "ISkill ID", *(sr2->number)); sprintf(rec->ID, "SKIL_%02d - %s", *(sr2->number), flag_output[0]->string); return; } else if (sr2 && memcmp(rec->name, "MGEF", N_LEN) == 0){ struct flags_paired *flag_output[MAX_FLAGS]; flag_output[0] = NULL; //clear it out. get_str_flag(flag_output, "IMagicEffect Index", *(sr2->number)); sprintf(rec->ID, "MGEF_%03d - %s", *(sr2->number), flag_output[0]->string); return; } if (memcmp(rec->name, "CELL", N_LEN) == 0) { int *x, *y, *z; sr2 = find_SubRecord(rec, "RGNN", NULL); if (sr2 && (strlen_safe(rec->ID, REC_ID_SIZE) + strlen_safe(sr2->data, sr2->size) < REC_ID_SIZE)) { sprintf(rec->ID + strlen_safe(rec->ID, REC_ID_SIZE), "%s%s", ((strlen(rec->ID) > 5) ? ", " : ""), sr2->string); } sr = find_SubRecord(rec, "DATA", NULL); if (sr) { //if it's interrior, the previous name will do. x = sr->data + 4; y = sr->data + 8; z = sr->data; if ((*z & 1) == 0) { //ensure it's exterior when appending location rec->ID[REC_ID_SIZE - 13] = 0; //Forces truncation if too long sprintf(rec->ID + strlen_safe(rec->ID, REC_ID_SIZE), " - %04d,%04d", *x, *y); } else { //ensure cleanup of x ,y *x = 0; *y = 0; } } } if (memcmp(rec->name, "DIAL", N_LEN) == 0) { //prefixes for sorting. In this order. //Journals, Dialogs, Greetings. //this is due to noticing a 'oath of silence' always coming up for no reason. //apparently the journal and other entries must come before they are called. char *name = sr->data; //from previous sr char *type = "U"; //U stands for Unknown. sr = find_SubRecord(rec, "DATA", NULL); if (sr) type = sr->data; char letter = 'Z'; switch(*type) { case 4: //Journal letter = 'A'; break; case 0: //regular topic letter = 'B'; break; case 2: //Greeting letter = 'C'; break; case 3: //Persuasion letter = 'D'; break; case 1: //Voice letter = 'E'; break; case 'U': //default unknown. letter = 'U'; break; } sprintf(rec->ID, "DIAL_%c_%s", letter, name); } if (memcmp(rec->name, "INFO", N_LEN) == 0) { sr = find_SubRecord(rec, "INAM", NULL); if (sr) { strncpy(rec->ID + N_LEN + 1, sr->data, REC_ID_SIZE - 7); return; } } if (memcmp(rec->name, "SCPT", N_LEN) == 0) { sr = find_SubRecord(rec, "SCHD", NULL); if (sr) { strncpy(rec->ID + N_LEN + 1, sr->data, 32); //first 32's the name return; } } if (memcmp(rec->name, "PGRD", N_LEN) == 0) { //sr should still be valid... sr = find_SubRecord(rec, "DATA", NULL); if (sr) { int *i_val = sr->data, sz; sz = strlen_safe(rec->ID, REC_ID_SIZE); if (sz > (REC_ID_SIZE - 12)) sz = REC_ID_SIZE - 12; if (*i_val || i_val[1]) { rec->ID[REC_ID_SIZE - 13] = 0; //Forces truncation if too long sprintf(rec->ID + sz, " - %04d,%04d", *i_val, *(i_val + 1)); } return; } } if (strlen_safe(rec->ID, REC_ID_SIZE) <= N_LEN + 1 && (memcmp(rec->name, "TES3", N_LEN) != 0 && memcmp(rec->name, "LAND", N_LEN) != 0)) { sr = find_SubRecord(rec, "DATA", NULL); if (sr) { int *i_val = sr->data; sprintf(rec->ID + N_LEN, " - %08d", *i_val); //first 32 are the name } } if (strlen_safe(rec->ID, REC_ID_SIZE) <= N_LEN + 1 || memcmp(rec->name, "LAND", N_LEN) == 0) { //see if an Index INDX or INTV would work sr = find_SubRecord(rec, "INTV", NULL); if (sr == NULL) sr = find_SubRecord(rec, "INDX", NULL); if (sr) { int *i_val = sr->data; rec->ID[REC_ID_SIZE - 13] = 0; //Forces truncation if too long if (sr->size == 8) { //first 32 are the name if (memcmp(rec->name, "LAND", N_LEN)) { rec->ID[REC_ID_SIZE - 18] = 0; //Forces truncation if too long sprintf(rec->ID + strlen_safe(rec->ID, REC_ID_SIZE), "- Index %04d,%04d", *i_val, *(i_val + 1)); } else //if it's land, we match CELL format. sprintf(rec->ID + strlen_safe(rec->ID, REC_ID_SIZE), " - %04d,%04d", *i_val, *(i_val + 1)); } else sprintf(rec->ID + strlen_safe(rec->ID, REC_ID_SIZE), "- Index %04d", *i_val); //first 32 are the name return; } } } [/code]
Aug 27 2013
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Aug 28, 2013 at 06:45:11AM +0200, Era Scarecrow wrote:
 On Wednesday, 28 August 2013 at 03:45:06 UTC, Andre Artus wrote:
Era,

I haven't had time to go through your everything you wrote here
but are you looking to create some form of discriminated union
(AKA tagged union) using D structs?

Do you have a specific problem you need to solve, or are you just
exploring the language?
The tagged union struct does sorta match the basic idea. What i have is a structure that basically is just a single struct, however it's semi-polymorphic. (I guess that's obvious by the subject). What changes is only really behavior and rarely would it actually add more data, so inheriting and OOP fits it fairly well, however since structs can't inherit it becomes an issue. I can either inherit, or i can make the function(s) large and clunky and basically do it as C.
[...] One trick that you may find helpful, is to use alias this to simulate struct inheritance: struct Base { int x; } struct Derived1 { Base __base; alias __base this; int y; } struct Derived2 { Base __base; alias __base this; int z; } void baseFunc(Base b) { ... } void derivFunc1(Derived1 d) { auto tmp = d.y; auto tmp2 = d.x; // note: "base struct" member directly accessible } void derivFunc2(Derived2 d) { auto tmp = d.z; auto tmp2 = d.x; } Derived1 d1; Derived2 d2; baseFunc(d1); // OK baseFunc(d2); // OK derivFunc1(d1); // OK //derivFunc2(d1); // Error: Derived1 can't convert to Derived2 derivFunc2(d2); // OK This won't help you if you need a single variable to store both structs "derived" this way, though, 'cos you wouldn't know what size the structs should be. You *could* get away with using Base* (Base pointers) in a semi-polymorphic way as long as you're sure the derived structs don't go out of scope before the Base*, but it gets a bit tricky at that point, and you start to need real classes instead. T -- Once bitten, twice cry...
Aug 27 2013
parent reply "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Wednesday, 28 August 2013 at 05:13:51 UTC, H. S. Teoh wrote:
 One trick that you may find helpful, is to use alias this to 
 simulate
 struct inheritance:

 	struct Base {
 		int x;
 	}

 	struct Derived1 {
 		Base __base;
 		alias __base this;
 		int y;
 	}

 	struct Derived2 {
 		Base __base;
 		alias __base this;
 		int z;
 	}

 	void baseFunc(Base b) { ... }
 	void derivFunc1(Derived1 d) {
 		auto tmp = d.y;
 		auto tmp2 = d.x; // note: "base struct" member directly 
 accessible
 	}
 	void derivFunc2(Derived2 d) {
 		auto tmp = d.z;
 		auto tmp2 = d.x;
 	}

 	Derived1 d1;
 	Derived2 d2;
 	baseFunc(d1); // OK
 	baseFunc(d2); // OK
 	derivFunc1(d1); // OK
 	//derivFunc2(d1); // Error: Derived1 can't convert to Derived2
 	derivFunc2(d2); // OK

 This won't help you if you need a single variable to store both 
 structs "derived" this way, though, 'cos you wouldn't know what 
 size the structs should be. You *could* get away with using 
 Base* (Base pointers) in a semi-polymorphic way as long as 
 you're sure the derived structs don't go out of scope before 
 the Base*, but it gets a bit tricky at that point, and you 
 start to need real classes instead.
Yes that could work, it's a bit how my templates work to get around (some of) this problem. But it doesn't really help with keeping it looking and feeling clean like it's suppose to. I shouldn't have to fight the language to get something that's effectively there already. It's possible to have a pointer to the base manually added with the alias this, I've done that for an experimental sorting struct before, but even if THAT works, it will still work in the local struct before it will work from the 'base' or where it inherited from. I've been thinking, perhaps i can get around it using a DSL/mixins that will give me what i want while not touching the D language, and hopefully the D language moves and gives me what i want rather than doing mixin magic to glue it all together. But then wrapping your mind around how it 'should work' and 'what it actually does to get it working' will create very interesting errors, plus it's all hidden so only by expanding and working through a bunch of garbage would you figure it all out. *sighs* I'll post in the main D forum section regarding behavior of inherited structs rather than my much larger post; Perhaps Walter or Andrei might see what I want and have a better solution. Maybe having inherited structs adds too much complexity for what a struct should be.
Aug 27 2013
parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
  K got a prototype working, I know there's issues with it but for 
now it looks simple and acts the way it is suppose to (at least 
to the unittest) so... Had to use templates through all the 
functions in order for auto ref to work right (hopefully forwards 
what it needs...)


/*
   //the only thing i would need to update, wish i didn't have to 
though...
   enum entries = ["iA:A,C",
                   "iB:A,B"];

   //AB as the base struct. 'real_' defaulted for the real 
functions.
   mixin(LSPS("AB", entries));
*/

     //generated output - start
     struct AB {
       LSPS_Type type;
       auto ref A(T...)(auto ref T t) {
         void* ptr = &this;
         with(LSPS_Type) { switch(type) {
           case LSPS_iB: return (cast(iB*) ptr).real_A(t);
           default: return (cast(iA*) ptr).real_A(t);
         }}
     }

       auto ref B(T...)(auto ref T t) {
         void* ptr = &this;
         with(LSPS_Type) { switch(type) {
           case LSPS_iB: return (cast(iB*) ptr).real_B(t);
           default: throw new Exception("Function 'B' not 
avaliable for subtype of "~to!string(type)~"");
         }}
       }

       auto ref C(T...)(auto ref T t) {
         void* ptr = &this;
         return (cast(iA*) ptr).real_C(t);
       }

       enum LSPS_Type {LSPS_iA, LSPS_iB, }
     }
     //generated - end

     //the two structs that are inherited.
     struct iA {
       AB lsps; alias lsps this;
       string real_A(){ return "iA_A";}
       string real_C(){ return "iA_C";}
     }
     struct iB {
       AB lsps; alias lsps this;
       string real_A(){ return "iB_A";}
       string real_B(){ return "iB_B";}
     }

   //Doesn't matter if it's iA, iB or AB, it should act the same
   iA ab;
   assert(ab.A() == "iA_A");
   assertThrown(ab.B());
   assert(ab.C() == "iA_C");

   ab.type = AB.LSPS_Type.LSPS_iB;
   assert(ab.A() == "iB_A");
   assert(ab.B() == "iB_B");
   assert(ab.C() == "iA_C");
Aug 28 2013
prev sibling parent reply Artur Skawina <art.08.09 gmail.com> writes:
On 08/28/13 06:45, Era Scarecrow wrote:
 On Wednesday, 28 August 2013 at 03:45:06 UTC, Andre Artus wrote:
 Era,

 I haven't had time to go through your everything you wrote here but are you
looking to create some form of discriminated union (AKA tagged union) using D
structs?

 Do you have a specific problem you need to solve, or are you just exploring
the language?
The tagged union struct does sorta match the basic idea. What i have is a structure that basically is just a single struct, however it's semi-polymorphic. (I guess that's obvious by the subject). What changes is only really behavior and rarely would it actually add more data, so inheriting and OOP fits it fairly well, however since structs can't inherit it becomes an issue. I can either inherit, or i can make the function(s) large and clunky and basically do it as C. ie: struct AB { char type; void A() { if (type == 'A') { } if (type == 'B') { } } } Etc. One of the biggest reasons to modify the behavior is for the records/subrecords i'm working on they need to build their identifiers based on what they are. Locations give X,Y, if it's an NPC, their name, if it's a texture the texture name, if it's a Dialog it needs to create a hash and append it to the ID afterwards. These are just a few. If i can inherit all the individual creations shorten to less than 10 lines per, otherwise i have a massive function.
It's hard to tell what your actual requirements are; this discriminated union might help. It does a bit more than what you ask for, as it also gives access to /data/, not just methods; this shouldn't be a problem. The data access parts could be even more efficient, but I failed to figure out a way to check for perfectly overlapping fields at CT - offsetof doesn't work after an alias-this conversion takes place, CTFE does not allow the pointer arithmetic required, and when ctfe does, the result can be false negatives. Maybe someone else has an idea how to correctly implement the `atSameOff` function below (which would make accesses to compatible data members free; right now the type check is never omitted. It could also be done using manual annotations, but those shouldn't be necessary...) artur struct B { int x, y, z; auto a() { return 0; } } struct S1 { B super_; alias super_ this; auto a() { return x; } } struct S2 { B super_; alias super_ this; auto a() { return x+y; } } struct S3 { B super_; alias super_ this; auto a() { return x+y*z; } } struct S3x { int gg; B super_; alias super_ this; auto a() { return x+y*z+gg; } } struct DiscUnion(UE...) { union { UE members; } int type; mixin(evalExpMap!("%...;", q{ this(B:UE[%d])(B b) { this = b; } void opAssign(B:UE[%d])(B b) { scope (failure) type = 0; members[%d] = b; type = %d+1; }; }, UE)); ref DT as(DT)() property { foreach (I, _; UE) static if(is(DT==UE[I])) if (type==I+1) return members[I]; assert(0, "Invalid DiscUnion extraction"); } template opDispatch(string M) { auto ref opDispatch(A...)(A a) { foreach (I, _; UE) if (type==I+1) return mixin("members[I]."~M~"(a)"); assert(0, "Invalid DiscUnion state"); } auto ref opDispatch()() property if (isFieldOf!(M, UE[0]) && !sameRep!(M, UE)) { foreach (I, _; UE) if (type==I+1) return mixin("members[I]."~M); assert(0, "Invalid DiscUnion state"); } ref opDispatch()() property if (isFieldOf!(M, UE[0]) && sameRep!(M, UE)) { return mixin("members[0]."~M); } } } alias S = DiscUnion!(S1, S2, S3); alias SX = DiscUnion!(S3, S3x); void main() { S s = S1(B(2,3,4)); assert (s.a()==2); s = S2(B(2,3,4)); assert (s.a()==5); s = S3(s.super_); assert (s.a()==14); assert (s.x==2); auto t = s.as!S3; static assert (is(S3==typeof(t))); assert (t.y==3); SX sx = S3x(42, B(2,3,4)); assert (sx.a()==14+42); assert (sx.x==2); sx = S3(B(2,3,4)); assert (sx.x==2); } template isFieldOf(string M, T) { enum isFieldOf = is(typeof(function(T t) { return mixin("t."~M); })); } // XXX `offsetof` returns bogus results after an implicit conversion. // XXX Should be possible to fix `atSameOff`, to pass the two asserts below // XXX using CTFE - but that is too limited, does not allow pointer // XXX substraction even when both point to fields of the same object, and // XXX introduces other problems (like incorrect pointer equality evaluation). bool atSameOff(string M, A, B)() property { return false; // XXX way too conservative, but safe. FIXME. } //static assert(atSameOff!("x", S1, S2)); // FIXME. static assert(!atSameOff!("x", S3, S3x)); bool sameRep(string M, TYPES...)() property { static if (is(typeof(function(TYPES t) { return mixin("t[0]."~M); }))) return false; // only data fields can safely alias. else { foreach (I, _; TYPES[1..$]) static if (!atSameOff!(M, TYPES[0], TYPES[I]) || (!is(typeof(mixin("TYPES[0]."~M)) == typeof(mixin("TYPES[I]."~M))))) return false; return true; } } template evalExpMap(string C, string F, A...) { enum evalExpMap = { import std.array, std.conv; string s, l; static if (is(typeof(A))) alias B = typeof(A); else alias B = A; foreach (I, _; B) { auto r = replace( replace(F, "%s", A[I].stringof), "%d", to!string(I)); l ~= (I?", ":"") ~ r; s ~= r ~ ";\n"; } return replace(replace(C, "%...;", s), "%...", l); }(); }
Aug 28 2013
parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
On Wednesday, 28 August 2013 at 15:20:34 UTC, Artur Skawina wrote:
 It's hard to tell what your actual requirements are; this 
 discriminated union might help. It does a bit more than what 
 you ask for, as it also gives access to /data/, not just 
 methods; this shouldn't be a problem.
Requirements, I don't have any written down but I have it in my head. I want to replace specific functions, I don't want to use a vTable (or pointer or anything like that). They are replaced based on fixed criteria of what the changed behavior is/needs to be. I want to use OO and inheritance to split it into logical portions (after sorta implementing inheritance/semi-polymorphism in pure C functions the code got ugly fast and is difficult to navigate through).
 The data access parts could be even more efficient, but I 
 failed to figure out a way to check for perfectly overlapping 
 fields at CT - offsetof doesn't work after an alias-this 
 conversion takes place, CTFE does not allow the pointer 
 arithmetic required, and when ctfe does, the result can be 
 false negatives. Maybe someone else has an idea how to 
 correctly implement the 'atSameOff' function below (which would 
 make accesses to compatible data members free; right now the 
 type check is never omitted. It could also be done using manual 
 annotations, but those shouldn't be necessary...)
It looks like I'll have to read this in more detail when I can wrap my head around it. I'll give it a look.
Aug 28 2013
prev sibling parent "Era Scarecrow" <rtcvb32 yahoo.com> writes:
  Mixin is suppose to inject code as though you posted it there, 
correct? I've got a case with my LSPS function(s) where it fails 
my tests when I use the mixin, but if I copy/paste the output 
from the screen back into the source it works just fine.

  All these new headaches with the new version and coming back are 
starting to tick me off.
Sep 02 2013