www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - No struct extending?

reply Steve Horne <stephenwantshornenospam100 aol.com> writes:
In C++, 'struct' is (almost) a synonym for 'class'. More a declaration
of intent than a different thing. One useful side-effect of this is
that you can declare structs as extensions of other structs. This can
be useful even for plain-old-data. e.g. data structures with several
node types, but some shared fields. Inheritance doesn't always imply
virtual tables and stuff.

Of course you can handle this using...

  struct s_Branch
  {
    s_Common m_Common;

    ... (branch specific stuff)
  }

But all that 'pointer.m_Common.actual_member' is a pain.

Adding a union...

  union u_Variant
  {
    s_Common m_Common;
    s_Branch m_Branch;
    s_Leaf   m_Leaf;
  }

just means you have to specify 'm_Branch' or 'm_Leaf' for the
non-shared fields too.

Anonymous structs and unions can save on these extra dots and
identifiers, but they do a different job. They can't give you a family
of structs. Only a single struct/union combo - a variant record.

Plus, one thing I have in mind is templates that build up in layers
(mix-in layers pattern), and there will be an arbitrary number of
extensions applied. For example, if you want ordered keys, you apply
the 'gimme-keys' template as a mixin layer, and it extends whatever
structures, classes and methods it needs to.

This can still be handled - you just compose an access class in
parallel along with the structs. But it's a hassle.

Now, truth told, this mix-in layers bit isn't important. 'static if'
probably means I'm better off specifying things using mix-in layers
(setting up flags and aliases), but putting most of the final code in
one big template. It will be a lot more readable and maintainable that
way. The mix-in layers pattern is then mostly a way of avoiding having
too many parameters for one template.

I could use a D mixin to define the common fields, of course. And
whatever approach I take, there's pointer casting based on the
run-time type so that's not a big deal, though the C++ approach is
nice in that casting to the 'base struct' is implicit.

What bothers me is the chance of this happening...

  struct c_Common
  {
    mixin(common fields)
  }

  struct c_Leaf
  {
    int m_Misplaced_Field;  //  whoops!
    mixin(common fields)
  }

Or, for that matter, the same just using nested structs.

  struct s_Branch
  {
    int      m_Misplaced_Field;  //  whoops!
    s_Common m_Common;
  }

That is, there is no rule forcing the shared part into matched
locations in all structures, so when you do the union/pointer
casts/whatever you can end up looking at the wrong memory.

So - am I being paranoid?

It's a small thing, especially given the amount of work that D has
already saved me. And I'm not even convinced it's real. The node
layouts above, for instance, will all be part of the same module and
maintained together anyway. The odds of a misplaced field like that
should be next to zero, and the same probably applies in any family
tree of related structs.

I just thought I'd raise it and see what others think.

-- 
Remove 'wants' and 'nospam' from e-mail.
Sep 08 2006
next sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Steve Horne wrote:
 In C++, 'struct' is (almost) a synonym for 'class'. More a declaration
 of intent than a different thing. One useful side-effect of this is
 that you can declare structs as extensions of other structs. This can
 be useful even for plain-old-data. e.g. data structures with several
 node types, but some shared fields. Inheritance doesn't always imply
 virtual tables and stuff.
I can imagine that such a feature could be useful indeed. However, the approach you propose has a caveat. The problem is that the extending concepts currently in D - class inheritance, interface implementation and enum base types - satisfy the "is a" relationship. Basically, given class Qwert : Yuiop or enum Asdfg : int you can keep any Qwert in a variable of type Yuiop, or any Asdfg in a variable of type int, without losing any information. This won't be true of structs if we did what you're suggesting. Of course, it would at least make sense to somebody coming from a C++ background. But it will be necessary to know whether something's a struct or a class in order to be sure whether converting it to the base type would lead to loss of data. <snip>
 Adding a union...
 
   union u_Variant
   {
     s_Common m_Common;
     s_Branch m_Branch;
     s_Leaf   m_Leaf;
   }
 
 just means you have to specify 'm_Branch' or 'm_Leaf' for the
 non-shared fields too.
<snip> In a union? Doesn't seem to make sense. But an idea would be to have a means whereby a defined struct can be used as an anonymous struct. It would also be handy if a struct instance could be both anonymous and named, if you know what I mean. But you remind me of an idea I've had for a while, which is being able to derive a union from a union or struct. This would be used to wrap a structure from an external API and give it methods or alternative views of the data. Stewart. -- -----BEGIN GEEK CODE BLOCK----- Version: 3.1 GCS/M d- s:- C++ a->--- UB P+ L E W++ N+++ o K- w++ O? M V? PS- PE- Y? PGP- t- 5? X? R b DI? D G e++++ h-- r-- !y ------END GEEK CODE BLOCK------ My e-mail is valid but not my primary mailbox. Please keep replies on the 'group where everyone may benefit.
Sep 08 2006
parent reply Steve Horne <stephenwantshornenospam100 aol.com> writes:
On Fri, 08 Sep 2006 20:20:29 +0100, Stewart Gordon
<smjg_1998 yahoo.com> wrote:

Steve Horne wrote:
 In C++, 'struct' is (almost) a synonym for 'class'. More a declaration
 of intent than a different thing. One useful side-effect of this is
 that you can declare structs as extensions of other structs. This can
 be useful even for plain-old-data. e.g. data structures with several
 node types, but some shared fields. Inheritance doesn't always imply
 virtual tables and stuff.
I can imagine that such a feature could be useful indeed. However, the approach you propose has a caveat. The problem is that the extending concepts currently in D - class inheritance, interface implementation and enum base types - satisfy the "is a" relationship. Basically, given
branch_node isa node leaf_node isa node The common part only represents what is associated with node-ness, as opposed to specifically branch-nodeness or leaf-nodeness.
     class Qwert : Yuiop
you can keep any Qwert in a variable of type Yuiop
Not quite. You can reference any Qwert from a variable of type Yuiop. That implicit reference is important. Convert the instance itself and you lose data. And the kinds of run-time type resolution or binding are exactly the same as for nodes. I have to check before downcasting a pointer. You have to check before downcasting a reference. You just have some built-in compiler support, of course - I'm just working in the abstract sense of a heirarchy or classes, as opposed to the specific sense supported by this language. Branch-node isa node. Why shouldn't that relationship be known to the compiler? It's not just C++ oriented thinking. It's a wider issue, in principle. Call it database-style object orientation. An object oriented database basically provides heirarchies of related record structures, after all.
In a union?  Doesn't seem to make sense.
I'm saying that every time I access a field, I have to specify a qualifier for each layer... pointer.m_Branch.m_Field instead of... pointer.m_Field Nothing profound or anything - just saying that while anonymous unions can be used to get variants without needing extra qualifiers, it doesn't help in this case.
But an idea would be to have a means whereby a defined struct can be 
used as an anonymous struct.  It would also be handy if a struct 
instance could be both anonymous and named, if you know what I mean.
Kind of. Like using 'mixin' with a struct as the source instead of a template. Could be handy. But in the cases I have in mind, that's just a minor shorthand to one of the options I described anyway - mixin the base struct itself, rather than defining a separate template and mixing that into all structs. It doesn't tell the compiler anything new about my intent. In itself.
But you remind me of an idea I've had for a while, which is being able 
to derive a union from a union or struct.  This would be used to wrap a 
structure from an external API and give it methods or alternative views 
of the data.
Maybe. Actually, scratch all the inheritance stuff. It's just a special case of another requirement anyway, with structs. Aligning members. Not in the 'alignof' sense, but in terms of field positioning. Perhaps if I could write... struct s_Branch { at 0: s_Common m_Common; } Still have the dotted qualifiers to deal with, but that's just a minor irritation. The point is that the qualifier has this declaration of intent - where the embedded field should be. And then of course... struct s_Branch { at 0: mixin (s_Common); // s_Common being a struct } Now we're getting somewhere! Each 'at' clause would be a signal to either add whatever padding to the struct is needed, or to report an error if that offset has already been passed. Could be useful for general file/protocol layout stuff anyway. It can be easy to mess up with 'align', for instance. Especially if the default align size platform dependent? And then, how about something like... group (x, y) { struct s_Block_1 { at x : int32_t m_1; int32_t m_2; at y : float m_3; } struct s_Block_2 { at x : int32_t m_1; float m_2; at y : float m_3; } } That is, the (presumably in-memory-only) structs have common parts which must be at the same offset in each struct, but not specifying an exact location. Each 'at' offset would be set to the minimum value possible given the field sizes and alignment. And there'd be no possibility of packing later fields into earlier gaps, on the principle of least surprise. Another approach might be same-field-name-implies-same-offset within a group of structs. This is, of couse, in the field of obscure requirements and long-term possible futures. But an explicit field position sounds more immediately useful. -- Remove 'wants' and 'nospam' from e-mail.
Sep 09 2006
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
Steve Horne wrote:
 On Fri, 08 Sep 2006 20:20:29 +0100, Stewart Gordon
 <smjg_1998 yahoo.com> wrote:
<snip>
 The problem is that the extending concepts currently in D - class 
 inheritance, interface implementation and enum base types - satisfy the 
 "is a" relationship.  Basically, given
branch_node isa node leaf_node isa node The common part only represents what is associated with node-ness, as opposed to specifically branch-nodeness or leaf-nodeness.
But a branch_node isn't a node, if a node is only the little bit that two or more types have in common.
     class Qwert : Yuiop
 you can keep any Qwert in a variable of type Yuiop
Not quite. You can reference any Qwert from a variable of type Yuiop. That implicit reference is important. Convert the instance itself and you lose data.
It isn't important to the point I am making. What I basically mean is that you can do Yuiop asdfg; ... Qwert hjkl = asdfg; without any data loss involved in the assignment operation.
 And the kinds of run-time type resolution or binding are exactly the
 same as for nodes. I have to check before downcasting a pointer. You
 have to check before downcasting a reference. You just have some
 built-in compiler support, of course - I'm just working in the
 abstract sense of a heirarchy or classes, as opposed to the specific
 sense supported by this language.
 
 Branch-node isa node. Why shouldn't that relationship be known to the
 compiler?
I'm not sure what you mean here.... <snip>
 And then of course...
 
   struct s_Branch
   {
     at 0: mixin (s_Common);  //  s_Common being a struct
   }
 
 Now we're getting somewhere!
 
 Each 'at' clause would be a signal to either add whatever padding to
 the struct is needed, or to report an error if that offset has already
 been passed.
Hmm....
 Could be useful for general file/protocol layout stuff anyway. It can
 be easy to mess up with 'align', for instance. Especially if the
 default align size platform dependent?
<snip> It probably is. But if you make sure the alignment of a struct is fully specified, when this matters, then you should be OK. Except that the size of a real is also platform dependent. As is endianness.... Stewart. -- -----BEGIN GEEK CODE BLOCK----- Version: 3.1 GCS/M d- s:- C++ a->--- UB P+ L E W++ N+++ o K- w++ O? M V? PS- PE- Y? PGP- t- 5? X? R b DI? D G e++++ h-- r-- !y ------END GEEK CODE BLOCK------ My e-mail is valid but not my primary mailbox. Please keep replies on the 'group where everyone may benefit.
Sep 09 2006
parent Steve Horne <stephenwantshornenospam100 aol.com> writes:
On Sat, 09 Sep 2006 23:58:34 +0100, Stewart Gordon
<smjg_1998 yahoo.com> wrote:

But a branch_node isn't a node, if a node is only the little bit that 
two or more types have in common.
Next you'll be telling me that apes, dolphins, bats and platypusses etc aren't mammals, because 'mammal' is just the tiny little bit that the various types have in common. After all, none of those is a D-style class either ;-) There's no reason why shared characteristics shouldn't be limited to shared member fields in a family of struct layouts, if that's what happens to be useful. Inheritance is an abstract concept - an idea, a metaphor. It is not a specific implementation. Anyway, in D, structs already have one object-oriented feature. They have encapsulation. They allow member functions. Personally, if roles reversed, this is exactly where I'd go on the attack... """ With inheritance and methods, you're most of the way to C++ classes! If you inherit methods, you need to make sure that they still make sense. That means overriding. And now we have to worry about late binding and virtual tables. Hell, why not just add multiple inheritance and virtual inheritance and be done with it!!!! """ But methods aren't a fundamental feature of structs. Supporting member functions is a convenience - a shorthand and a bit of encapsulation. So why not take the same view as if those functions weren't members at all? If you don't inherit the methods and you don't allow casting, they can't do any damage. Of course there are details, but there's always details. Is it useful in D? That's the question. I'll put you down as a "no" ;-)
without any data loss involved in the assignment operation.
I never said there should be an implicit cast between related struct types. I never said that D should be C++.
 Could be useful for general file/protocol layout stuff anyway. It can
 be easy to mess up with 'align', for instance. Especially if the
 default align size platform dependent?
<snip> It probably is. But if you make sure the alignment of a struct is fully specified, when this matters, then you should be OK.
I've seen a lot of documentation that give offsets and sizes for fields within blocks. When you code that, it's easy to make a mistake. If the offsets were explicit in the code, it would be even easier to be sure you'd got it right.
Except that the size of a real is also platform dependent.
IEEE 754. Even if the D spec doesn't guarantee it, it's a fairly safe bet most of the time. And in any case...
  As is 
endianness....
So no-one should ever write an endian-swap function - after all, struct layouts are platform dependent anyway. Damn - I broke that law too ;-) -- Remove 'wants' and 'nospam' from e-mail.
Sep 10 2006
prev sibling next sibling parent reply Georg Wrede <georg.wrede nospam.org> writes:
Steve Horne wrote:
 In C++, 'struct' is (almost) a synonym for 'class'. More a declaration
 of intent than a different thing. One useful side-effect of this is
 that you can declare structs as extensions of other structs. This can
 be useful even for plain-old-data. e.g. data structures with several
 node types, but some shared fields. Inheritance doesn't always imply
 virtual tables and stuff.
 
 Of course you can handle this using...
 
   struct s_Branch
   {
     s_Common m_Common;
 
     ... (branch specific stuff)
   }
 
 But all that 'pointer.m_Common.actual_member' is a pain.
 
 Adding a union...
 
   union u_Variant
   {
     s_Common m_Common;
     s_Branch m_Branch;
     s_Leaf   m_Leaf;
   }
 
 just means you have to specify 'm_Branch' or 'm_Leaf' for the
 non-shared fields too.
 
 Anonymous structs and unions can save on these extra dots and
 identifiers, but they do a different job. They can't give you a family
 of structs. Only a single struct/union combo - a variant record.
 
 Plus, one thing I have in mind is templates that build up in layers
 (mix-in layers pattern), and there will be an arbitrary number of
 extensions applied. For example, if you want ordered keys, you apply
 the 'gimme-keys' template as a mixin layer, and it extends whatever
 structures, classes and methods it needs to.
 
 This can still be handled - you just compose an access class in
 parallel along with the structs. But it's a hassle.
 
 Now, truth told, this mix-in layers bit isn't important. 'static if'
 probably means I'm better off specifying things using mix-in layers
 (setting up flags and aliases), but putting most of the final code in
 one big template. It will be a lot more readable and maintainable that
 way. The mix-in layers pattern is then mostly a way of avoiding having
 too many parameters for one template.
 
 I could use a D mixin to define the common fields, of course. And
 whatever approach I take, there's pointer casting based on the
 run-time type so that's not a big deal, though the C++ approach is
 nice in that casting to the 'base struct' is implicit.
 
 What bothers me is the chance of this happening...
 
   struct c_Common
   {
     mixin(common fields)
   }
 
   struct c_Leaf
   {
     int m_Misplaced_Field;  //  whoops!
     mixin(common fields)
   }
 
 Or, for that matter, the same just using nested structs.
 
   struct s_Branch
   {
     int      m_Misplaced_Field;  //  whoops!
     s_Common m_Common;
   }
 
 That is, there is no rule forcing the shared part into matched
 locations in all structures, so when you do the union/pointer
 casts/whatever you can end up looking at the wrong memory.
 
 So - am I being paranoid?
 
 It's a small thing, especially given the amount of work that D has
 already saved me. And I'm not even convinced it's real. The node
 layouts above, for instance, will all be part of the same module and
 maintained together anyway. The odds of a misplaced field like that
 should be next to zero, and the same probably applies in any family
 tree of related structs.
 
 I just thought I'd raise it and see what others think.
I suspect this is a perfect case of the Square Triangle. Happens to me too. The solution sought being just a notch off the problem, concepts fighting for neurons, and the goal but a mirage, elusive and yet so tempting. And then of course, I may be misunderstanding the whole issue, for all I know. Anyway, as I understood it, you have struct instances (from somewhere, like a C library routine or a file, etc., let's call them ForeignInstances) and you need to glue some new fields to them so that you can process them without needing to write reams of code that keeps track of what YourProperties belong to which item. And this has to work with several (more or less) different, but still conceptually related kinds of ForeignInstances. One could use a concoction of templates, mixins, unions and inheritance to create a module (or a library) which then lets one handle the situation simply and cleanly in main code. (Either in current D, or after Walter makes some needed tweaks.) Carefully writing the module would let one be reasonably sure that the fields align right, maybe even have suitable dynamic and/or static checks and asserts, to (almost) enforce integrity. The end result, or the goal, being that one ends up with in-memory (let's call them) prints, the beginning of which is exactly the same as in YourProperties and the rest the same as ForeignInstances. Having got this far, one can then use functions from the module to handle the non-instance-type-specific manipulation of the ForeignInstances. Presumably one would either already have, or else write specific routines to do all the actual instance type specific stuff (mostly access and assignment). You make it more difficult by bringing up the issue of physical field alignment. (See http://en.wikipedia.org/wiki/Fragile_binary_interface_problem which incidentally is on a wrong page(!!) since it discusses the Fragile Base Class problem. Oh well.) I suspect you wanted to have all this more ambitious? Like having several alternate sets of YourProperties (and of course matching code sets), so that each set could be used for a different purpose, like sorting, selecting, serializing, combining, printing, etc. of the "prints". --- If I got a task like this, I'd probably do it a lot simpler. First, these ForeignInstances have to enter our process somehow. At that point we either have to recognize the specific type of each, or we know it from the context. In both cases, if we wanted to "slap on" our own fields to them, we need to copy them somewhere else in memory. (If we only have a couple of such instances coming one at a time all this effort is a waste, and if they come from a stream or a file then they're too near each other to have room for our fields anyhow.) Thus, one of the main points of physically having our own fields attached to the ForeignInstances disappears: speed. And with sorting, unless they're very small, it will be more practical to sort linked lists of just pointers to them. So it seems some of the performance wins just vanish. The other point for having our and their fields together was "kinda integrity", in other words, we wouldn't have to separately keep track of our and their data. Now, from above, it seems that having our and their data together at all times may actually incur more work for us than more traditional programming. This all means we simply have to have routines for import and export. Now, the issues of machine word width and endianness, only exist when carrying the data (or porting the program) between different architectures. As to the data, simply exactly specifying the record layout takes care of both word size and endianness. (Hey, a .gif file is a .gif file no matter what computer you have.) As to within the program, as long as we access the fields with their names (as opposed to bit twiddling), we're okay. The process of importing or exporting converts between the "file format" and our internal representation (whatever it may be), and that process is where endianness and word size are take care of. Implementation I'd start with simple textbook OO. We (feel entitled to) assume that the various ForeignInstance types do have much in common. This automatically suggests a Base Class that has methods to store, retrieve and manipulate the common fields. It would also have abstract methods that cater for what has to be done with the instance type specific fields, more or less constituting an interface specification in reality. Each ForeignInstance subtype would then just be a sublclass of this base class, implementing only the specifics. Instances of these could then be stored in data structures (arrays, lists, trees) and referenced to as the base type. Thanks to polymorphism, we could then "just use" these instances without worrying which specific type each is. We'd also get adequate performance, without even trying. Clear, concise and KISS. Oh, and way more robust and maintainable. Ok, your original issue (as I understood it anyway) was not one of practical implementation, rather a (in itself very intriguing) thoughlet about the relationship of mixins, structs and their manipulation, both in source code and at runtime. My point (and I apologize) was merely that I can't see a suitable problem for your solution. :-)
Sep 11 2006
parent reply Steve Horne <stephenwantshornenospam100 aol.com> writes:
On Mon, 11 Sep 2006 15:45:19 +0300, Georg Wrede
<georg.wrede nospam.org> wrote:

I suspect this is a perfect case of the Square Triangle. Happens to me 
too. The solution sought being just a notch off the problem, concepts 
fighting for neurons, and the goal but a mirage, elusive and yet so 
tempting.
Hopefully. I mentioned elsewhere that I'm chasing my own tail, refactoring to try to get my interface use patterns to be sane. Not familiar enough yet with having that tool, and not having multiple inheritance. Doesn't mean D is bad. Just means the paradigms need to shift around a bit yet before they stabilise.
Anyway, as I understood it, you have struct instances (from somewhere, 
like a C library routine or a file
No. I need struct instances. The posts have created a bit of haze, since I have covered a bunch of vaguely related issues, but I have one specific application in mind right now which is why I kept going on about branch nodes and leaf nodes. I'm working on a multiway tree container library. The tree has to be able to live in a disk file. Think of it as a database index. Since the basic data structure is essentially a B+ tree, there are two types of nodes - branch and leaf - which share some fields in common. As far as disk blocks are concerned, each block holds one node and in effect uses a union. When a node is read back from the disk, fields in the common part are used to determine the node type. It's an everyday kind of pattern when dealing with file formats and network protocols, as well. Families of block types. Families of message types. There are applications where remoting features can remove the need (server side objects and the like), but someone still has to implement the remoting protocols that achieve this. So I'm building from scratch, but I still have a family of structs that have to be structs. It may seem inappropriate in that there is an apparent polymorphic type, but the compiler cannot resolve the polymorphism for me. The 'struct extending' title comes via two routes. The lesser route is that the leaf nodes and branch nodes are both extensions of the core node. But the more important route came from the idea of using of a mix-in layers pattern to 'compose' the functionality I want. "Mix-in layers" is a template pattern. You basically add features one at a time to what you need. You get to say what requirements you have, and you don't get the overhead from features you don't need. Third parties can even create their own mix-in layers to add support for new features, without needing to start from scratch. The requirements crosscut multiple classes etc. So, in principle, you get to order a library with the options you want - "gimme a multiway tree with ordered keys and efficient subscripting support, hold the non-key data and persistable intelligent iterators. Here's an interface you can use to shove the nodes into a disk file, but there's more than one tree sharing space in the file so I'll handle the free list." kind of thing. There are a few papers available on mix-in layers in C++ - google for Yannis Smaragdakis and Don Batory. They're a lot easier in D because the template support is better. Crosscutting features seem to be a big thing at the moment - aspect orientation is another expression of this, and there are versions of Java and C++ with aspect support.
One could use a concoction of templates, mixins, unions and inheritance 
to create a module (or a library) which then lets one handle the 
situation simply and cleanly in main code.
Overkill. All the struct layouts are known in advance, and the polymorphism is resolved using ordinary data fields in the common part. All I need is a set of small access functions... int Get_Branch_XXX (s_Node_Base* p_Node) { if (Is_Branch (p_Node)) { return (cast(s_Node_Branch*) p_Node).m_XXX; } throw new Exception ("XXX not available for this node."); } Even that's overkill. I'd rather do a one-shot check-and-cast pointer type conversion in most core functions, so the checks don't repeat. Actually, just realised I'm being stupid. Will post the 'easy way' solution separately, once I've checked whether it works.
Now, the issues of machine word width and endianness, only exist when 
carrying the data (or porting the program) between different 
architectures.
Yes, but it is a potential issue for me, and an awkward one, since code can be compiled for multiple platforms and files exchanged between platforms, but my library is inherently generic. It can handle fields that it puts in the nodes (size, flags, child pointers etc) but it doesn't necessarily understand much more about key or data items than how to copy and compare them. It's a 'once the thing works on one platform I'll deal with this' kind of issue. I might even delegate the responsibility to users, along with the 'make sure you only store plain-old-data' thing. Probably I'll have some kind of callbacks called when nodes are read or written, though - kind of 'I don't know how to fix your data, but endian fixes and conversions are needed now'.
 As to the data, simply exactly specifying the record 
layout takes care of both word size and endianness.
No it doesn't, since there is no pre-existing import/export to delegate this task to. I'm writing a low level library, not using a pre-existing one. Any bit twiddling needed is my responsibility, not someone elses.
Implementation I'd start with simple textbook OO.
Yes. That's what I want to do. I just _CANNOT_ use compiler-managed polymorphism for this. Handling the polymorphism has to be my responsibility rather than the compilers, otherwise I'd just use classes. But in principle, inheritance can be useful even without polymorphism. Just as encapsulation can be useful without polymorphism or else why do D structs support methods?
Clear, concise and KISS. Oh, and way more robust and maintainable.
If only it was relevant to the job ;-) -- Remove 'wants' and 'nospam' from e-mail.
Sep 11 2006
parent Steve Horne <stephenwantshornenospam100 aol.com> writes:
OK - I have my solution. It's not perfect, but it's easy enough and
it's good enough.

For the branch nodes and leaf nodes and their common part, the branch
and leaf node can just use property getter/setter functions to give
shorthand access to the shared parts. This didn't occur to me before
because I resist adding methods to structs.

It still doesn't handle the mix-in layers issue, though. I don't know
which layers were mixed in previously, so I can't know which shortcuts
to provide.

But I can say that each member is always at the same offset in a
struct, once the struct is composed, even if that struct gets further
extensions. Non-member functions can take void pointers to the
buffers. They can be passed down through mix-in layers using the
'mixin' keyword...

  template t_Keys_Mixin_Layer (alias PL, class c_Key)
  {
    mixin PL;  //  Mix in definitions from previous layer

    struct c_Leaf
    {
      PL.c_Leaf               m_Previous;
      c_Key[PL.m_Array_Size]  m_Keys;
    }

    c_Key[] Node_Keys (void* p_Node)
    {
      if (Node_Is_Leaf (p_Node))  //  Defined in previous layer
      {
        c_Leaf* l_Leaf = cast(c_Leaf*) p_Node;

        //  Don't know what previous layers defined - depends which
        //  layers the user decided to mix in - but doesn't matter
        //  since I know what is needed now...

        return l_Leaf.m_Keys;
      }

      throw new Exception ("Node is not a leaf");
    }
  }

-- 
Remove 'wants' and 'nospam' from e-mail.
Sep 11 2006
prev sibling parent reply Argon 7 <sadjuuk homeworld.haigara.com> writes:
I think it would be cool if you could convert
a D struct to a D class and a D class to a D struct and vice versa.
Sep 15 2006
parent Mike Parker <aldacron71 yahoo.com> writes:
Argon 7 wrote:
 I think it would be cool if you could convert
 a D struct to a D class and a D class to a D struct and vice versa.
That would serve no purpose. Choosing which to use is a design decision, not a runtime decision.
Sep 15 2006