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









"Era Scarecrow" <rtcvb32 yahoo.com> 