On Friday, 8 February 2013 at 06:28:54 UTC, Chad Joan wrote:
On 02/06/2013 02:38 AM, Andrei Alexandrescu wrote:
Probably it'll need a fair amount of tweaking. Anyhow it's in
destroyable form.
http://wiki.dlang.org/DIP25
Thanks,
Andrei
A single delegate (closure) can be used to defer both reads and
writes on an arbitrary expression.
This works on naked variables, references, pointers,
properties, and probably a number of other things I haven't
thought of yet.
Here's the relevance to DIP25: closures already have
well-defined escape semantics. The downside is that they
probably allocate heap way too aggressively in the current
implementation. The upshot is that they are always
memory-safe. This makes optimization a
quality-of-implementation issue: a sufficiently intelligent
compiler should be able to remove many allocations for
non-escaping closures.
Perhaps ref parameters should be delegates under the hood
instead of pointers?
The longterm disadvantage I see with this is the extra pointer
that must be passed/returned. I wonder how hard it would be
for the compiler to instantiate specialized oldschool-pointer
versions of functions with ref parameters whenever calls are
made that do not require the guarantees that closures provide.
A working demonstration is given below.
Destroy!
The cost of passing a delegate is way higher than the cost of
passing the extra pointer. Delegate cause an opaque function call
so :
- All registers that the callee is allowed to trash must be
saved preventively (even if the callee don't trash them).
- CPU cannot really use branch prediction.
- The compiler must assume that the delegate call may have
arbitrary side effects so have to commit everything to memory and
then take everything back afterward. This prevent many
instruction reordering, register promotions, dead read/write
elimination, constant propagation and so on.
Considering passing by ref is sometime done to fasten things,
this defeat the whole point.
Note that the operation above are not dying slow, but clearly not
a good fit for ref.
import std.traits;
import std.stdio;
struct Option(T)
{
bool hasValue = false;
union
{
ubyte nope;
T value;
}
this( T value )
{
hasValue = true;
this.value = value;
}
}
Option!T none(T)()
{
Option!T result;
return result;
}
/* For some reason we need to cast to this to make template
deduction work on any functions it gets passed into. */
template DelegateRef(T)
{
alias T delegate(Option!T intake) DelegateRef;
}
template isDelegateRef(T)
{
static if ( isDelegate!T )
{
alias ReturnType!T R;
static if ( is( T == R delegate(Option!R) ) )
enum isDelegateRef = true;
else
enum isDelegateRef = false;
}
else
enum isDelegateRef = false;
}
string accessor(string expr)
{
return
`cast(DelegateRef!(typeof(`~expr~`))) (`~
`delegate typeof(`~expr~`)(Option!(typeof(`~expr~`))
intake) {`~
` if ( intake.hasValue )`~
` return (`~expr~` = intake.value);`~
` else`~
` return `~expr~`;`~
`})`;
}
// Function that accepts a reference.
auto someFunc(Q)(Q qux) if ( isDelegateRef!Q )
{
alias ReturnType!Q T;
auto x = qux(none!T);
x |= 0xF00D;
qux(Option!T(x));
return qux(none!T);
}
struct MyStruct
{
private int m_q;
@property int q()
{
writefln("get q (%x)",m_q);
return m_q;
}
@property int q(int v)
{
writefln("set q (%x = %x)", m_q, v);
return m_q = v;
}
}
void testRef( ref int foo )
{
assert(someFunc(mixin(accessor("foo"))) == 0xF00D);
}
void testPtr( int* foo )
{
assert(someFunc(mixin(accessor("*foo"))) == 0xF00D);
}
void main()
{
int abc = 0;
assert(someFunc(mixin(accessor("abc"))) == 0xF00D);
assert(abc == 0xF00D);
int foo = 0;
testRef(foo);
assert(foo == 0xF00D);
int bar = 0;
testPtr(&bar);
assert(bar == 0xF00D);
MyStruct s;
s.q = 0;
assert(someFunc(mixin(accessor("s.q"))) == 0xF00D);
assert(s.q == 0xF00D);
}