Limited Semi-PolyMorphic (LSP) structs?

Artur Skawina art.08.09 at gmail.com
Wed Aug 28 08:20:20 PDT 2013


On 08/28/13 06:45, Era Scarecrow wrote:
> On Wednesday, 28 August 2013 at 03:45:06 UTC, Andre Artus wrote:
>> Era,
>>
>> I haven't had time to go through your everything you wrote here but are you looking to create some form of discriminated union (AKA tagged union) using D structs?
>>
>> Do you have a specific problem you need to solve, or are you just exploring the language?
> 
>  The tagged union struct does sorta match the basic idea. What i have is a structure that basically is just a single struct, however it's semi-polymorphic. (I guess that's obvious by the subject). What changes is only really behavior and rarely would it actually add more data, so inheriting and OOP fits it fairly well, however since structs can't inherit it becomes an issue. I can either inherit, or i can make the function(s) large and clunky and basically do it as C.
> 
> ie:
> 
> struct AB {
>   char type;
> 
>   void A() {
>     if (type == 'A') {
>     }
>     if (type == 'B') {
>     }
>   }
> }
> 
>  Etc. One of the biggest reasons to modify the behavior is for the records/subrecords i'm working on they need to build their identifiers based on what they are. Locations give X,Y, if it's an NPC, their name, if it's a texture the texture name, if it's a Dialog it needs to create a hash and append it to the ID afterwards. These are just a few. If i can inherit all the individual creations shorten to less than 10 lines per, otherwise i have a massive function.

It's hard to tell what your actual requirements are; this discriminated union
might help. It does a bit more than what you ask for, as it also gives access
to /data/, not just methods; this shouldn't be a problem.

The data access parts could be even more efficient, but I failed to figure
out a way to check for perfectly overlapping fields at CT - offsetof doesn't
work after an alias-this conversion takes place, CTFE does not allow the
pointer arithmetic required, and when ctfe does, the result can be false
negatives. Maybe someone else has an idea how to correctly implement the 
`atSameOff` function below (which would make accesses to compatible data
members free; right now the type check is never omitted. It could also be
done using manual annotations, but those shouldn't be necessary...)

artur


   struct B { int x, y, z; auto a() { return 0; } }

   struct S1 { B super_; alias super_ this; auto a() { return x; } }
   struct S2 { B super_; alias super_ this; auto a() { return x+y; } }
   struct S3 { B super_; alias super_ this; auto a() { return x+y*z; } }
   struct S3x { int gg; B super_; alias super_ this; auto a() { return x+y*z+gg; } }

   struct DiscUnion(UE...) {
      union { UE members; }
      int type;

      mixin(evalExpMap!("%...;", q{
               this(B:UE[%d])(B b) { this = b; }
               void opAssign(B:UE[%d])(B b) {
                  scope (failure) type = 0;
                  members[%d] = b;
                  type = %d+1;
               };
            }, UE));

      ref DT as(DT)() @property {
         foreach (I, _; UE)
            static if(is(DT==UE[I])) if (type==I+1)
               return members[I];
         assert(0, "Invalid DiscUnion extraction");
      }

      template opDispatch(string M) {
         auto ref opDispatch(A...)(A a) {
            foreach (I, _; UE)
               if (type==I+1)
                  return mixin("members[I]."~M~"(a)");
            assert(0, "Invalid DiscUnion state");
         }
         
         auto ref opDispatch()() @property if (isFieldOf!(M, UE[0]) && !sameRep!(M, UE)) {
            foreach (I, _; UE)
               if (type==I+1)
                  return mixin("members[I]."~M);
            assert(0, "Invalid DiscUnion state");
         }
         
         ref opDispatch()() @property if (isFieldOf!(M, UE[0]) && sameRep!(M, UE)) {
            return mixin("members[0]."~M);
         }
      }
   }

   alias S = DiscUnion!(S1, S2, S3);
   alias SX = DiscUnion!(S3, S3x);

   void main() {
      S s = S1(B(2,3,4));
      assert (s.a()==2);
      s = S2(B(2,3,4));
      assert (s.a()==5);
      s = S3(s.super_);
      assert (s.a()==14);
      assert (s.x==2);

      auto t = s.as!S3;
      static assert (is(S3==typeof(t)));
      assert (t.y==3);
      
      SX sx = S3x(42, B(2,3,4));
      assert (sx.a()==14+42);
      assert (sx.x==2);
      sx = S3(B(2,3,4));
      assert (sx.x==2);
   }

   template isFieldOf(string M, T) {
       enum isFieldOf = is(typeof(function(T t) { return mixin("t."~M); }));
   }
   
   // XXX `offsetof` returns bogus results after an implicit conversion.
   // XXX Should be possible to fix `atSameOff`, to pass the two asserts below
   // XXX using CTFE - but that is too limited, does not allow pointer
   // XXX substraction even when both point to fields of the same object, and
   // XXX introduces other problems (like incorrect pointer equality evaluation).
   bool atSameOff(string M, A, B)() @property {
      return false; // XXX way too conservative, but safe. FIXME.
   }
   //static assert(atSameOff!("x", S1, S2)); // FIXME.
   static assert(!atSameOff!("x", S3, S3x));
   
   bool sameRep(string M, TYPES...)() @property {
      static if (is(typeof(function(TYPES t) { return mixin("t[0]."~M); })))
         return false; // only data fields can safely alias.
      else {
         foreach (I, _; TYPES[1..$])
            static if (!atSameOff!(M, TYPES[0], TYPES[I])
                   || (!is(typeof(mixin("TYPES[0]."~M)) == typeof(mixin("TYPES[I]."~M)))))
               return false;
         return true;
      }
   }
      
   template evalExpMap(string C, string F, A...) {
      enum evalExpMap = {
         import std.array, std.conv;
         string s, l;
         static if (is(typeof(A))) alias B = typeof(A);
                else               alias B = A;
         foreach (I, _; B) {
            auto r = replace( replace(F, "%s", A[I].stringof),
                                         "%d", to!string(I));
            l ~= (I?", ":"") ~ r;
            s ~=               r ~ ";\n";
         }
         return replace(replace(C, "%...;", s), "%...", l);
      }();
   }



More information about the Digitalmars-d-learn mailing list