ref returns and properties

Steven Schveighoffer schveiguy at yahoo.com
Mon Jan 26 08:28:31 PST 2009


"Andrei Alexandrescu" wrote
>I realized that properties are not a complete model for actual data. The 
>current conventional wisdom about properties goes like this (T is some 
>type):
>
> class Host
> {
>     property prop
>     {
>         T get() { ... }
>         void set(T value) { ... }
>     }
> }
>
> If T is e.g. an int, it all works nicely. Now consider T is a 
> highly-structured piece of data that holds resources. In that case we want 
> to make sure T is not copied unwittingly such that the resource is managed 
> properly. This means that T has a copy constructor and a destructor.
>
> For such cases, extensive experience with C++ has shown that two 
> primitives are essential: move and swap.
>
> void move(ref T src, ref T dst);
> void swap(ref T lhs, ref T rhs);
>
> Move takes the guts of src, puts them in dst, and then clears src 
> effectively relieving it from any resource. Swap exchanges the guts of src 
> and dst without any extra resource copying.
>
> Now if "prop" were a classic data, swapping host1.prop and host2.prop is a 
> piece of cake (that is realized inside std.algorithm. Look it up, the 
> implementation has quite a few interesting quirks!)
>
> But if "prop" is a property with get and set, everything falls apart. 
> Properties effectively hide the address of the actual data and only 
> traffic in values. That's often good (and sometimes even the only 
> possibility in the case of properties computed on-the-fly), but this 
> abstraction effectively makes resource-conserving move and swap 
> impossible.
>
> I noticed this problem when dealing with ranges. The .head() function, if 
> it returns a value, cannot be swapped/moved. Same applies to opIndex when 
> implemented as a value property (via the opIndex/opIndexAssign tandem).
>
> What to do? I'd like to refine the notion of property such that moving 
> properties around is possible, without, however, complicating their 
> interface too much.

There are two possibilities that I can think of.

1. ref return from get, which gets you part-way there.  You can still do 
some useful things like which member you are returning getting calculated, 
but it doesn't make the call to set when you assign to the property, or 
allow calling get multiple times to return different values.

2. Have a "property delegate", which is like a normal delegate, but contains 
2 functions.  Calling a function which takes such a delegate makes the 
compiler generate such a delegate in the case where a simple lvalue is 
passed.  You could signify it with the terms "lazy ref" in the function 
signature.

option 1 is the simple solution, and covers some ground.  I don't think it's 
universally useful, but it could provide a mechanism for things like head() 
to work.

option 2 solves the problem, but introduces some complex, possibly 
performance hurting mechanisms.  The way to solve this last part is probably 
to allow specification of multiple functions, and have the compiler choose 
the non 'lazy-ref' version if it is passed a simple lvalue.

example usage of #2:

class C
{
   private int _x;
   private int _y;

   this(int n) { _x = n; }
   property prop
   {
      int get() { return _y; }
      void set(int x) { _y += _x * x; } // some weird semantics for set
   }
}

void foo(lazy ref z)
{
   z += 100;
}

translates to:

struct property_delegate(T)
{
   void *ptr;
   T function(void*) get;
   void function(void *, T) set;
}

void foo(property_delegate!(int) pd)
{
  pd.set(pd.ptr, pd.get(pd.ptr) + 100);
}

usage:

void bar()
{
   auto c = new C(2);
/* generates a property delegate with prop.get and prop.set as the 
functions, and c as the pointer */
   foo(c.prop);
   assert(c.prop == 200);
   int n = c.prop;
/* generates a property delegate with get and set functions being generated 
inner functions of bar which set and get n, and the stack frame as the 
pointer. */
   foo(n);
   assert(n == 300);
}

-Steve





More information about the Digitalmars-d mailing list