From 49d7452b3367349fd046f294318a5c422a0478b7 Mon Sep 17 00:00:00 2001 From: Eugen Wissner Date: Sat, 24 Nov 2018 06:25:55 +0100 Subject: [PATCH] Make containers work with non-copyable elements It is the first step. The containers can be at least created with non-copyable structs without compilation errors now. Fix #69. --- source/tanya/container/array.d | 45 ++++++++------- source/tanya/container/entry.d | 17 +++++- source/tanya/container/hashtable.d | 22 ++++++-- source/tanya/container/list.d | 90 ++++++++++++++++++++++-------- source/tanya/container/set.d | 20 +++++-- source/tanya/hash/lookup.d | 15 ++--- source/tanya/test/stub.d | 71 +++++++++++++++++++++-- source/tanya/typecons.d | 24 ++------ 8 files changed, 215 insertions(+), 89 deletions(-) diff --git a/source/tanya/container/array.d b/source/tanya/container/array.d index c2e35e2..0c5027e 100644 --- a/source/tanya/container/array.d +++ b/source/tanya/container/array.d @@ -22,6 +22,7 @@ import tanya.memory; import tanya.meta.trait; import tanya.meta.transform; import tanya.range; +version (unittest) import tanya.test.stub; /** * Random-access range for the $(D_PSYMBOL Array). @@ -293,7 +294,9 @@ struct Array(T) * init = Initial value to fill the array with. * allocator = Allocator. */ - this(size_t len, T init, shared Allocator allocator = defaultAllocator) + this()(size_t len, + auto ref T init, + shared Allocator allocator = defaultAllocator) { this(allocator); reserve(len); @@ -348,15 +351,19 @@ struct Array(T) (() @trusted => allocator.deallocate(slice(capacity)))(); } - /** - * Copies the array. - */ - this(this) + static if (isCopyable!T) { - auto buf = slice(this.length); - this.length_ = capacity_ = 0; - this.data = null; - insertBack(buf); + this(this) + { + auto buf = slice(this.length); + this.length_ = capacity_ = 0; + this.data = null; + insertBack(buf); + } + } + else + { + @disable this(this); } /** @@ -1027,7 +1034,7 @@ struct Array(T) } /// ditto - Range opIndexAssign(Range value) + Range opIndexAssign()(Range value) { return opSliceAssign(value, 0, length); } @@ -1321,7 +1328,7 @@ struct Array(T) } /// ditto - Range opSliceAssign(Range value, size_t i, size_t j) @trusted + Range opSliceAssign()(Range value, size_t i, size_t j) @trusted in { assert(i <= j); @@ -1575,15 +1582,10 @@ struct Array(T) assert(v7[].equal(v8[])); } +// Destructor can destroy empty arrays @nogc nothrow pure @safe unittest { - static struct SWithDtor - { - ~this() @nogc nothrow pure @safe - { - } - } - auto v = Array!SWithDtor(); // Destructor can destroy empty arrays. + auto v = Array!WithDtor(); } @nogc nothrow pure @safe unittest @@ -1594,7 +1596,6 @@ struct Array(T) A a1, a2; auto v1 = Array!A([a1, a2]); - // Issue 232: https://issues.caraus.io/issues/232. static assert(is(Array!(A*))); } @@ -1679,3 +1680,9 @@ struct Array(T) } func(array); } + +// Can have non-copyable elements +@nogc nothrow pure @safe unittest +{ + static assert(is(Array!NonCopyable)); +} diff --git a/source/tanya/container/entry.d b/source/tanya/container/entry.d index 214ba00..e976cc3 100644 --- a/source/tanya/container/entry.d +++ b/source/tanya/container/entry.d @@ -20,6 +20,7 @@ import tanya.memory.allocator; import tanya.meta.trait; import tanya.meta.transform; import tanya.typecons; +version (unittest) import tanya.test.stub; package struct SEntry(T) { @@ -59,12 +60,12 @@ package struct Bucket(K, V = void) } BucketStatus status = BucketStatus.empty; - this(ref K key) + this()(ref K key) { this.key = key; } - @property void key(ref K key) + @property void key()(ref K key) { this.key() = key; this.status = BucketStatus.used; @@ -170,7 +171,7 @@ package struct HashArray(alias hasher, K, V = void) .swap(this.length, data.length); } - void opAssign(ref typeof(this) that) + void opAssign()(ref typeof(this) that) { this.array = that.array; this.lengthIndex = that.lengthIndex; @@ -326,3 +327,13 @@ package struct HashArray(alias hasher, K, V = void) return false; } } + +// Can be constructed with non-copyable key/values +@nogc nothrow pure @safe unittest +{ + static assert(is(Bucket!NonCopyable)); + static assert(is(Bucket!(NonCopyable, NonCopyable))); + + static assert(is(HashArray!((ref NonCopyable) => 0U, NonCopyable))); + static assert(is(HashArray!((ref NonCopyable) => 0U, NonCopyable, NonCopyable))); +} diff --git a/source/tanya/container/hashtable.d b/source/tanya/container/hashtable.d index 771e0fa..b80d220 100644 --- a/source/tanya/container/hashtable.d +++ b/source/tanya/container/hashtable.d @@ -22,6 +22,7 @@ import tanya.memory; import tanya.meta.trait; import tanya.meta.transform; import tanya.range.primitive; +version (unittest) import tanya.test.stub; /** * Bidirectional range whose element type is a tuple of a key and the @@ -68,7 +69,7 @@ struct Range(T) return this.dataRange.empty(); } - @property void popFront() + void popFront() in { assert(!empty); @@ -87,7 +88,7 @@ struct Range(T) while (!empty && dataRange.front.status != BucketStatus.used); } - @property void popBack() + void popBack() in { assert(!empty); @@ -759,7 +760,7 @@ if (isHashFunction!(hasher, Key)) * * Returns: The number of the inserted elements with a unique key. */ - size_t insert(ref KeyValue keyValue) + size_t insert()(ref KeyValue keyValue) { auto e = ((ref v) @trusted => &this.data.insert(v))(keyValue.key); size_t inserted; @@ -773,7 +774,7 @@ if (isHashFunction!(hasher, Key)) } /// ditto - size_t insert(KeyValue keyValue) + size_t insert()(KeyValue keyValue) { auto e = ((ref v) @trusted => &this.data.insert(v))(keyValue.key); size_t inserted; @@ -1197,3 +1198,16 @@ if (isHashFunction!(hasher, Key)) static assert(is(typeof("asdf" in HashTable!(String, int)()))); static assert(is(typeof(HashTable!(String, int)()["asdf"]))); } + +// Can have non-copyable keys and elements +@nogc nothrow pure @safe unittest +{ + @NonCopyable @Hashable + static struct S + { + mixin StructStub; + } + static assert(is(HashTable!(S, int))); + static assert(is(HashTable!(int, S))); + static assert(is(HashTable!(S, S))); +} diff --git a/source/tanya/container/list.d b/source/tanya/container/list.d index 38bc680..39edf06 100644 --- a/source/tanya/container/list.d +++ b/source/tanya/container/list.d @@ -23,6 +23,7 @@ import tanya.meta.trait; import tanya.meta.transform; import tanya.range.array; import tanya.range.primitive; +version (unittest) import tanya.test.stub; /** * Forward range for the $(D_PSYMBOL SList). @@ -155,8 +156,9 @@ struct SList(T) * init = Initial value to fill the list with. * allocator = Allocator. */ - this(size_t len, T init, shared Allocator allocator = defaultAllocator) - @trusted + this()(size_t len, + auto ref T init, + shared Allocator allocator = defaultAllocator) { this(allocator); if (len == 0) @@ -182,7 +184,18 @@ struct SList(T) /// ditto this(size_t len, shared Allocator allocator = defaultAllocator) { - this(len, T.init, allocator); + this(allocator); + if (len == 0) + { + return; + } + + Entry* next = this.head = allocator.make!Entry(); + foreach (i; 1 .. len) + { + next.next = allocator.make!Entry(); + next = next.next; + } } /// @@ -271,14 +284,18 @@ struct SList(T) clear(); } - /** - * Copies the list. - */ - this(this) + static if (isCopyable!T) { - auto list = typeof(this)(this[], this.allocator); - this.head = list.head; - list.head = null; + this(this) + { + auto list = typeof(this)(this[], this.allocator); + this.head = list.head; + list.head = null; + } + } + else + { + @disable this(this); } /// @@ -512,7 +529,7 @@ struct SList(T) } /// ditto - size_t insertBefore(Range r, ref T el) @trusted + size_t insertBefore()(Range r, ref T el) @trusted in { assert(checkRangeBelonging(r)); @@ -1120,8 +1137,9 @@ struct DList(T) * init = Initial value to fill the list with. * allocator = Allocator. */ - this(size_t len, T init, shared Allocator allocator = defaultAllocator) - @trusted + this()(size_t len, + auto ref T init, + shared Allocator allocator = defaultAllocator) { this(allocator); if (len == 0) @@ -1150,7 +1168,20 @@ struct DList(T) /// ditto this(size_t len, shared Allocator allocator = defaultAllocator) { - this(len, T.init, allocator); + this(allocator); + if (len == 0) + { + return; + } + + Entry* next = this.head = allocator.make!Entry(); + foreach (i; 1 .. len) + { + next.next = allocator.make!Entry(); + next.next.prev = next; + next = next.next; + } + this.tail = next; } /// @@ -1242,15 +1273,19 @@ struct DList(T) clear(); } - /** - * Copies the list. - */ - this(this) + static if (isCopyable!T) { - auto list = typeof(this)(this[], this.allocator); - this.head = list.head; - this.tail = list.tail; - list.head = list .tail = null; + this(this) + { + auto list = typeof(this)(this[], this.allocator); + this.head = list.head; + this.tail = list.tail; + list.head = list .tail = null; + } + } + else + { + @disable this(this); } /// @@ -1641,7 +1676,7 @@ struct DList(T) } /// ditto - size_t insertBefore(Range r, ref T el) @trusted + size_t insertBefore()(Range r, ref T el) @trusted in { assert(checkRangeBelonging(r)); @@ -1758,7 +1793,7 @@ struct DList(T) } /// ditto - size_t insertAfter(Range r, ref T el) @trusted + size_t insertAfter()(Range r, ref T el) @trusted in { assert(checkRangeBelonging(r)); @@ -2355,3 +2390,10 @@ struct DList(T) assert(!l1.remove(r).empty); assert(l1 == l2); } + +// Can have non-copyable elements +@nogc nothrow pure @safe unittest +{ + static assert(is(SList!NonCopyable)); + static assert(is(DList!NonCopyable)); +} diff --git a/source/tanya/container/set.d b/source/tanya/container/set.d index 0c8a3f3..b95950b 100644 --- a/source/tanya/container/set.d +++ b/source/tanya/container/set.d @@ -22,6 +22,7 @@ import tanya.memory; import tanya.meta.trait; import tanya.meta.transform; import tanya.range.primitive; +version (unittest) import tanya.test.stub; /** * Bidirectional range that iterates over the $(D_PSYMBOL Set)'s values. @@ -67,7 +68,7 @@ struct Range(T) return this.dataRange.empty(); } - @property void popFront() + void popFront() in { assert(!empty); @@ -86,7 +87,7 @@ struct Range(T) while (!empty && dataRange.front.status != BucketStatus.used); } - @property void popBack() + void popBack() in { assert(!empty); @@ -459,7 +460,7 @@ if (isHashFunction!(hasher, T)) * * Returns: Amount of new elements inserted. */ - size_t insert(ref T value) + size_t insert()(ref T value) { auto e = ((ref v) @trusted => &this.data.insert(v))(value); if (e.status != BucketStatus.used) @@ -470,7 +471,7 @@ if (isHashFunction!(hasher, T)) return 0; } - size_t insert(T value) + size_t insert()(T value) { auto e = ((ref v) @trusted => &this.data.insert(v))(value); if (e.status != BucketStatus.used) @@ -773,3 +774,14 @@ if (isHashFunction!(hasher, T)) { static assert(is(Set!(int, (const ref x) => cast(size_t) x))); } + +// Can have non-copyable elements +@nogc nothrow pure @safe unittest +{ + @NonCopyable @Hashable + static struct S + { + mixin StructStub; + } + static assert(is(Set!S)); +} diff --git a/source/tanya/hash/lookup.d b/source/tanya/hash/lookup.d index a509aae..b64545c 100644 --- a/source/tanya/hash/lookup.d +++ b/source/tanya/hash/lookup.d @@ -16,6 +16,7 @@ module tanya.hash.lookup; import tanya.meta.trait; import tanya.range.primitive; +version (unittest) import tanya.test.stub; private struct FNV { @@ -146,14 +147,6 @@ version (unittest) ~ r10!x ~ r10!x ~ r10!x ~ r10!x ~ r10!x; enum string r500(string x) = r100!x ~ r100!x ~ r100!x ~ r100!x ~ r100!x; - private static struct ToHash - { - size_t toHash() const @nogc nothrow pure @safe - { - return 0; - } - } - private static struct HashRange { string fo = "fo"; @@ -178,9 +171,9 @@ version (unittest) { bool empty_; - @property ToHash front() const @nogc nothrow pure @safe + @property Hashable front() const @nogc nothrow pure @safe { - return ToHash(); + return Hashable(); } void popFront() @nogc nothrow pure @safe @@ -199,7 +192,7 @@ version (unittest) @nogc nothrow pure @safe unittest { assert(hash(null) == 0); - assert(hash(ToHash()) == 0U); + assert(hash(Hashable()) == 0U); assert(hash('a') == 'a'); } diff --git a/source/tanya/test/stub.d b/source/tanya/test/stub.d index 234e409..e1f8dcb 100644 --- a/source/tanya/test/stub.d +++ b/source/tanya/test/stub.d @@ -91,7 +91,7 @@ struct WithLvalueElements mixin template InputRangeStub(E = int) { import tanya.meta.metafunction : Alias; - import tanya.meta.trait : getUDAs, hasUDA; + import tanya.meta.trait : evalUDA, getUDAs, hasUDA; /* * Aliases for the attribute lookups to access them faster @@ -279,6 +279,9 @@ mixin template RandomAccessRangeStub(E = int) /** * Struct with a disabled postblit constructor. + * + * $(D_PSYMBOL NonCopyable) can be used as an attribute for + * $(D_PSYMBOL StructStub) or as a standalone type. */ struct NonCopyable { @@ -288,10 +291,14 @@ struct NonCopyable /** * Struct with an elaborate destructor. * - * The constructor of $(D_PSYMBOL WithDtor) accepts an additional `counter` - * argument, which is incremented by the destructor. $(D_PSYMBOL WithDtor) - * stores a pointer to the passed variable, so the variable can be - * investigated after the struct isn't available anymore. + * $(D_PSYMBOL WithDtor) can be used as an attribute for + * $(D_PSYMBOL StructStub) or as a standalone type. + * + * When used as a standalone object the constructor of $(D_PSYMBOL WithDtor) + * accepts an additional `counter` argument, which is incremented by the + * destructor. $(D_PSYMBOL WithDtor) stores a pointer to the passed variable, + * so the variable can be investigated after the struct isn't available + * anymore. */ struct WithDtor { @@ -310,3 +317,57 @@ struct WithDtor } } } + +/** + * Struct supporting hashing. + * + * $(D_PSYMBOL Hashable) can be used as an attribute for + * $(D_PSYMBOL StructStub) or as a standalone type. + * + * The constructor accepts an additional parameter, which is returned by the + * `toHash()`-function. `0U` is returned if no hash value is given. + */ +struct Hashable +{ + size_t hash; + + size_t toHash() const @nogc nothrow pure @safe + { + return this.hash; + } +} + +/** + * Generates a $(D_KEYWORD struct) with common functionality. + * + * To specify the needed functionality use user-defined attributes on the + * $(D_KEYWORD struct) $(D_PSYMBOL StructStub) is mixed in. + * + * Supported attributes are: $(D_PSYMBOL NonCopyable), $(D_PSYMBOL Hashable), + * $(D_PSYMBOL WithDtor). + */ +mixin template StructStub() +{ + import tanya.meta.trait : evalUDA, getUDAs, hasUDA; + + static if (hasUDA!(typeof(this), NonCopyable)) + { + @disable this(this); + } + + private alias Hashable = getUDAs!(typeof(this), .Hashable); + static if (Hashable.length > 0) + { + size_t toHash() const @nogc nothrow pure @safe + { + return evalUDA!(Hashable[0]).hash; + } + } + + static if (hasUDA!(typeof(this), WithDtor)) + { + ~this() @nogc nothrow pure @safe + { + } + } +} diff --git a/source/tanya/typecons.d b/source/tanya/typecons.d index becb1e3..5904a7a 100644 --- a/source/tanya/typecons.d +++ b/source/tanya/typecons.d @@ -503,30 +503,16 @@ struct Option(T) // Returns default value @nogc nothrow pure @safe unittest { - { - int i = 5; - assert(((ref e) => e)(Option!int().or(i)) == 5); - } + int i = 5; + assert(((ref e) => e)(Option!int().or(i)) == 5); } // Implements toHash() for nothing @nogc nothrow pure @safe unittest { - static struct ToHash - { - size_t toHash() const @nogc nothrow pure @safe - { - return 1U; - } - } - { - Option!ToHash toHash; - assert(toHash.toHash() == 0U); - } - { - auto toHash = Option!ToHash(ToHash()); - assert(toHash.toHash() == 1U); - } + alias OptionT = Option!Hashable; + assert(OptionT().toHash() == 0U); + assert(OptionT(Hashable(1U)).toHash() == 1U); } /**