#!/usr/bin/env rdmd -unittest
import std.stdio, std.typecons;
version = DebugRefCounted;
/**
Offers reference counting on top of any type. Currently not
implemented for classes.
*/
struct RefCounted(T) {
// We allocate payload and counter together.
private Tuple!(uint, "refs", T, "payload") * data;
// "empty" object intended for static and immutable methods when
// the data pointer is null. It won't be modified. We assume the
// empty object is equivalent to a null stored pointer.
private static immutable T _empty;
private ref immutable(T) asImmutable() immutable
{
return data ? data.payload : _empty;
}
private ref const(T) asConst() const
{
return data ? data.payload : _empty;
}
// Makes sure that the payload exists and is not shared.
void ensureUnique()
{
if (!data)
{
data = new typeof(*data)(1, T.init);
}
else if (data.refs > 1)
{
data = new typeof(*data)(1, data.payload.dup);
}
}
// Copying adds to the reference count
this(this)
{
if (!data) return;
++data.refs;
version(DebugRefCounted) writeln("Increasing refcount to ", data.refs);
}
void opAssign(ref RefCounted rhs)
{
if (data == rhs.data) return;
version(DebugRefCounted) writeln("Assigning to ", data ? "existing object." : "empty object.");
this.__dtor();
data = rhs.data;
if (data) ++data.refs;
}
// Destruction deletes the target if no more references
~this()
{
if (!data || --data.refs) return;
version(DebugRefCounted) writeln("Gone with the wind.");
data.payload.dispose();
}
// Const methods against the data
auto ref opDispatch(string fun, A...)(auto ref A args) const
{
version(DebugRefCounted) writeln("Calling ", fun, "(", args, ") const.");
return mixin("asConst()." ~ fun ~ "(args)");
}
// Immutable methods against the data
auto ref opDispatch(string fun, A...)(auto ref A args) immutable
{
version(DebugRefCounted) writeln("Calling ", fun, "(", args, ") immutable.");
return mixin("asImmutable()." ~ fun ~ "(args)");
}
// Non-const methods always ensure that the payload is unique
// prior to the call.
auto ref opDispatch(string fun, A...)(auto ref A args)
{
alias typeof(mixin("data.payload." ~ fun ~ "(args)")) Result;
static if (is(typeof(mixin("asConst()." ~ fun ~ "(args)")) ConstResult))
{
// Invoking the method as const would work! Let's see
// whether the programmer intended it to work the same as
// for non-const objects.
static if (is(Result == ConstResult))
{
// Same thing, let's save some sweat and call the const method
version(DebugRefCounted) writeln("Calling ", T.stringof, ".", fun, "(", args, ") const.");
return mixin("asConst()." ~ fun ~ "(args)");
}
else
{
// Ehm, must call the non-const version
ensureUnique();
assert(data);
version(DebugRefCounted) writeln("Calling ", T.stringof, ".", fun, "(", args, ").");
return mixin("data.payload." ~ fun ~ "(args)");
}
}
else
{
// Only mutable version is callable.
version(DebugRefCounted) writeln("Calling ", T.stringof, ".", fun, "(", args, ").");
ensureUnique();
assert(data);
return mixin("data.payload." ~ fun ~ "(args)");
}
}
}
/**
Doubly-linked list prototype.
*/
struct DListImpl(T)
{
struct Node
{
Node * prev, next;
T payload;
this(T payload)
{
this.payload = payload;
}
}
Node * root;
static void connect(Node* first, Node* second)
{
assert(first && second);
first.next = second;
second.prev = first;
}
void insertFront(T value)
{
auto n = new Node(value);
if (root)
{
connect(n, root);
}
root = n;
}
@property ref T front()
{
assert(root);
return root.payload;
}
@property bool empty() const
{
return !root;
}
void dispose()
{
for (auto n = root; n; )
{
auto goner = n;
n = n.next;
delete goner;
}
root = null;
}
DListImpl dup()
{
DListImpl result;
if (!root) return result;
result.root = new Node(root.payload);
auto target = root;
for (auto n = root.next; n; n = n.next)
{
auto dupe = new Node(n.payload);
connect(target, dupe);
target = dupe;
}
return result;
}
}
template DList(T)
{
alias RefCounted!(DListImpl!T) DList;
}
unittest
{
DList!int lst;
assert(lst.empty);
lst.insertFront(42);
assert(lst.front == 42);
auto p = &lst.front;
assert(*p == 42);
// Assignment
lst = lst;
DList!int lst2;
lst2 = lst;
lst2.insertFront(43);
assert(lst.front == 42);
assert(lst2.front == 43);
lst = lst2;
}
void main(){}