Steven Schveighoffer wrote:
"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.

Yah, exactly. One thing I'm rather sad about is that certain containers won't be implementable that way, for example sparse vectors (with most elements zero). A sparse vector would want to return by value from head() and opIndex() (so it can return 0.0 most of the time) and would also want to have control over setting slots. So in brief, sparse vectors won't be supported naturally by our paradigm. They will still be supported, just not in a manner that makes them next to indistinguishable from dense vectors.

Lately I'm getting to think (sour grapes syndrome?) that probably that's a good thing. A sparse vector is very long - e.g. millions of items, and only has a few or a few hundred that are nonzero. It would be wasteful to ever iterate over the whole one million items; I'd rather use a custom algorithm that only "sees" the nonzero elements. It would be nice to have opApply work, but I guess I can live with that too.

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.

Yah, crossed my mind too. With the third function in tow (the one that does the resource acquisition or whatnot) things are getting rather overweight and it starts looking like a full-fledged local class with a vtable and the works might be an option. Local classes have access to the frame pointer of their parent, so probably things can be arranged to work. But the cost of the whole operation becomes too high.


Andrei

Reply via email to