digitalmars.D.learn - Limited Semi-PolyMorphic (LSP) structs?
- Era Scarecrow (183/183) Aug 26 2013 Been a while and out of the loop, I need to get my hands dirty
- qznc (14/79) Aug 26 2013 Hm, my try would be alias this for inheritance and function
- Era Scarecrow (27/40) Aug 26 2013 Hmmm. I haven't considered std.variant, and glancing at it
- Era Scarecrow (101/101) Aug 27 2013 It just came to mind that what I want is almost more a unioned
- Andre Artus (6/6) Aug 27 2013 Era,
- Era Scarecrow (195/201) Aug 27 2013 The tagged union struct does sorta match the basic idea. What i
- H. S. Teoh (42/60) Aug 27 2013 [...]
- Era Scarecrow (25/65) Aug 27 2013 Yes that could work, it's a bit how my templates work to get
- Era Scarecrow (59/59) Aug 28 2013 K got a prototype working, I know there's issues with it but for
- Artur Skawina (117/140) Aug 28 2013 It's hard to tell what your actual requirements are; this discriminated ...
- Era Scarecrow (12/26) Aug 28 2013 Requirements, I don't have any written down but I have it in my
- Era Scarecrow (6/6) Sep 02 2013 Mixin is suppose to inject code as though you posted it there,
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
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
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
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
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
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
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:[...] 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...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.
Aug 27 2013
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
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
On 08/28/13 06:45, Era Scarecrow wrote:On Wednesday, 28 August 2013 at 03:45:06 UTC, Andre Artus 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. 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); }(); }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.
Aug 28 2013
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
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