#!/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, bool cow = false) { // We allocate payload and counter together. private struct DataBlock { uint refs; T payload; this(uint _refs, T _payload) { refs = _refs; payload = _payload; } } // This is the entire state private DataBlock * 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. If cow, also make sure it is not shared. void ensureUnique() { if (!data) { data = new DataBlock(1, T.init); } else static if (cow) { if (data.refs > 1) { data = new DataBlock(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 is 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, bool cow = false) { alias RefCounted!(DListImpl!T, cow) DList; } unittest { DList!(int, true) lst; assert(lst.empty); lst.insertFront(42); assert(lst.front == 42); auto p = &lst.front; assert(*p == 42); // Assignment lst = lst; DList!(int, true) lst2; lst2 = lst; lst2.insertFront(43); assert(lst.front == 42); assert(lst2.front == 43); lst = lst2; } void main(){}