diff --git a/std/container.d b/std/container.d index af48167ce1d..be89bdf7e76 100644 --- a/std/container.d +++ b/std/container.d @@ -2063,6 +2063,9 @@ Array type with deterministic control of memory. The memory allocated for the array is reclaimed as soon as possible; there is no reliance on the garbage collector. $(D Array) uses $(D malloc) and $(D free) for managing its own memory. + +Array.Range can provide better performance than Array itself to +access or modify elements. */ struct Array(T) if (!is(T : const(bool))) { @@ -2216,6 +2219,7 @@ struct Array(T) if (!is(T : const(bool))) } private alias RefCounted!(Payload, RefCountedAutoInitialize.no) Data; private Data _data; + private static T[] _emptyDataPayload; //Dummy range to use when _data is not initialized. this(U)(U[] values...) if (isImplicitlyConvertible!(U, T)) { @@ -2256,130 +2260,175 @@ Defines the container's primary range, which is a random-access range. private Array _outer; private size_t _a, _b; + //Validates the range, and returns a clean slice to operate on + private @property nothrow + T[] get() + { + assert(_b <= _outer.length, "Array.Range: range has become invalid"); + return _outer._data._payload[_a .. _b]; + } + + //Validates the range, and returns a clean slice to operate on + private @property nothrow + const(T)[] get() const + { + assert(_b <= _outer.length, "Array.Range: range has become invalid"); + return _outer._data._payload[_a .. _b]; + } + this(Array data, size_t a, size_t b) { _outer = data; _a = a; _b = b; + get; //Asserts range validity, and validates a <= b via slicing + } + + @property nothrow + bool empty() const + { + return get.empty; } - @property bool empty() const + @property nothrow + size_t length() const { - assert(_outer.length >= _b); - return _a >= _b; + return get.length; } @property Range save() { - return this; + //All validations done by constructor + return Range(_outer, _a, _b); + } + + typeof(this) opSlice(size_t a, size_t b) + { + //All validations done by constructor + return typeof(this)(_outer, _a + a, _a + b); } @property T front() { - enforce(!empty); - return _outer[_a]; + return get.front; } @property T back() { - enforce(!empty); - return _outer[_b - 1]; + return get.back; } @property void front(T value) { - enforce(!empty); - _outer[_a] = move(value); + get.front = (value); } @property void back(T value) { - enforce(!empty); - _outer[_b - 1] = move(value); + get.back = (value); } - void popFront() + void popFront() nothrow { - enforce(!empty); + auto dummy = get; + dummy.popFront(); //Dummy call, but asserts the range is valid, and checks not empty ++_a; } - void popBack() + void popBack() nothrow { - enforce(!empty); + auto dummy = get; + dummy.popBack(); //Dummy call, but asserts the range is valid, and checks not empty --_b; } T moveFront() { - enforce(!empty); - return move(_outer._data._payload[_a]); + return move(get.front); } T moveBack() { - enforce(!empty); - return move(_outer._data._payload[_b - 1]); + return move(get.back); } T moveAt(size_t i) { - i += _a; - enforce(i < _b && !empty); - return move(_outer._data._payload[_a + i]); + return move(get[i]); + } + + void opUnary(string op)() + if(op == "++" || op == "--") + { + mixin(op~"get[];"); + } + + //Only works for ++ and -- + //For +, this doesn't work with raw arrays anyways + //for -, ~, there is no way to implement it. + void opUnary(string op)() + if(op != "++" && op != "--") + { + mixin(op~"get[];"); + } + + void opOpAssign(string op)(T value) + if(op != "~") + { + mixin("get[] "~op~"= value;"); } T opIndex(size_t i) { - i += _a; - enforce(i < _b && _b <= _outer.length); - return _outer[i]; + return get[i]; } void opIndexAssign(T value, size_t i) { - i += _a; - enforce(i < _b && _b <= _outer.length); - _outer[i] = value; + get[i] = value; } - typeof(this) opSlice(size_t a, size_t b) + void opIndexUnary(string op)(size_t i) + if(op == "++" || op == "--") { - return typeof(this)(_outer, a + _a, b + _a); + mixin(op~" get[i];"); } - void opIndexOpAssign(string op)(T value, size_t i) + T opIndexUnary(string op)(size_t i) + if(op != "++" && op != "--") { - enforce(_outer && _a + i < _b && _b <= _outer._payload.length); - mixin("_outer._payload.ptr[_a + i] "~op~"= value;"); + mixin("return "~op~" get[i];"); } - @property size_t length() const { - return _b - _a; + T opIndexOpAssign(string op)(T value, size_t i) + { + mixin("return get[i] "~op~"= value;"); } } -/** -Property returning $(D true) if and only if the container has no -elements. - -Complexity: $(BIGOH 1) - */ - @property bool empty() const + //Returns a valid slice, either to the payload itself, or to static empty range + private @property nothrow + T[] get() + { + return _data.RefCounted.isInitialized ? _data._payload : _emptyDataPayload; + } + //Returns a valid slice, either to the payload itself, or to static empty range + private @property nothrow + const(T)[] get() const { - return !_data.RefCounted.isInitialized || _data._payload.empty; + return _data.RefCounted.isInitialized ? _data._payload : _emptyDataPayload; } /** -Duplicates the container. The elements themselves are not transitively -duplicated. +Property returning $(D true) if and only if the container has no +elements. -Complexity: $(BIGOH n). +Complexity: $(BIGOH 1) */ - @property Array dup() + @property nothrow + bool empty() const { - if (!_data.RefCounted.isInitialized) return this; - return Array(_data._payload); + return get.empty; } /** @@ -2387,7 +2436,8 @@ Returns the number of elements in the container. Complexity: $(BIGOH 1). */ - @property size_t length() const + @property nothrow + size_t length() const { return _data.RefCounted.isInitialized ? _data._payload.length : 0; } @@ -2398,11 +2448,25 @@ Returns the maximum number of elements the container can store without Complexity: $(BIGOH 1) */ - @property size_t capacity() + @property nothrow + size_t capacity() const { + //The "get" trick doesn't work here: we have to really access the payload return _data.RefCounted.isInitialized ? _data._capacity : 0; } +/** +Duplicates the container. The elements themselves are not transitively +duplicated. + +Complexity: $(BIGOH n). + */ + @property Array dup() + { + if (!_data.RefCounted.isInitialized) return this; + return Array(_data._payload); + } + /** Ensures sufficient capacity to accommodate $(D e) elements. @@ -2438,10 +2502,8 @@ Complexity: $(BIGOH 1) */ Range opSlice() { - // Workaround for bug 4356 - Array copy; - copy._data = this._data; - return Range(copy, 0, length); + _data.RefCounted.ensureInitialized(); + return Range(this, 0, length); } /** @@ -2454,11 +2516,9 @@ Complexity: $(BIGOH 1) */ Range opSlice(size_t a, size_t b) { - enforce(a <= b && b <= length); - // Workaround for bug 4356 - Array copy; - copy._data = this._data; - return Range(copy, a, b); + _data.RefCounted.ensureInitialized(); + //Range.this will be the one enforecing a <= b + return Range(this, a, b); } /** @@ -2478,29 +2538,25 @@ Complexity: $(BIGOH 1) */ @property T front() { - enforce(!empty); - return *_data._payload.ptr; + return get.front; } /// ditto @property void front(T value) { - enforce(!empty); - *_data._payload.ptr = value; + get.front = value; } /// ditto @property T back() { - enforce(!empty); - return _data._payload[$ - 1]; + return get.back; } /// ditto @property void back(T value) { - enforce(!empty); - _data._payload[$ - 1] = value; + get.back = value; } /** @@ -2512,21 +2568,33 @@ Complexity: $(BIGOH 1) */ T opIndex(size_t i) { - enforce(_data.RefCounted.isInitialized); - return _data._payload[i]; + return get[i]; } - /// ditto +/// ditto void opIndexAssign(T value, size_t i) { - enforce(_data.RefCounted.isInitialized); - _data._payload[i] = value; + get[i] = value; } /// ditto void opIndexOpAssign(string op)(T value, size_t i) { - mixin("_data._payload[i] "~op~"= value;"); + mixin("get[i] "~op~"= value;"); + } + +/// ditto + void opIndexUnary(string op)(size_t i) + if(op == "++" || op == "--") + { + mixin(op~" get[i];"); + } + +/// ditto + T opIndexUnary(string op)(size_t i) + if(op != "++" && op != "--") + { + mixin("return "~op~" get[i];"); } /** @@ -2537,7 +2605,8 @@ define $(D opBinary). Complexity: $(BIGOH n + m), where m is the number of elements in $(D stuff) */ - Array opBinary(string op, Stuff)(Stuff stuff) if (op == "~") + Array opBinary(string op, Stuff)(Stuff stuff) + if (op == "~") { // TODO: optimize Array result; @@ -2552,7 +2621,8 @@ stuff) /** Forwards to $(D insertBack(stuff)). */ - void opOpAssign(string op, Stuff)(Stuff stuff) if (op == "~") + void opOpAssign(string op, Stuff)(Stuff stuff) + if (op == "~") { static if (is(typeof(stuff[]))) { @@ -2574,7 +2644,16 @@ Complexity: $(BIGOH n) */ void clear() { - .destroy(_data); + //destroying _data would be problematic for any existing range + //that indexe 0..0, as they *should* remain valid. + //However, destorying _data would break our forced initializaton invariant. + //We prefer setting the length to 0 instead + + //.destroy(_data); + if(_data.RefCounted.isInitialized) + { + _data.length = 0; + } } /** @@ -2752,7 +2831,7 @@ Complexity: $(BIGOH n + m), where $(D m) is the length of $(D stuff) } } - /// ditto +/// ditto size_t insertAfter(Stuff)(Range r, Stuff stuff) { enforce(r._outer._data is _data); @@ -3047,6 +3126,43 @@ unittest assertThrown(a.replace(r, [42])); assertThrown(a.linearRemove(r)); } +unittest +{ + auto a = Array!int([1, 1]); + a[1] = 0; //Check Array.opIndexAssign + assert(a[1] == 0); + a[1] += 1; //Check Array.opIndexOpAssign + assert(a[1] == 1); + + //Check Array.opIndexUnary + ++a[0]; + assert(a[0] == 2); + assert(+a[0] == +2); + assert(-a[0] == -2); + assert(~a[0] == ~2); + + auto r = a[]; + r[1] = 0; //Check Array.Range.opIndexAssign + assert(r[1] == 0); + r[1] += 1; //Check Array.Range.opIndexOpAssign + assert(r[1] == 1); + + //Check Array.Range.opIndexUnary + ++r[0]; + assert(r[0] == 3); + assert(+r[0] == +3); + assert(-r[0] == -3); + assert(~r[0] == ~3); +} +unittest +{ + //Test "range-wide" operations + auto r = Array!int([0, 1, 2])[]; + r += 5; + assert(r.equal([5, 6, 7])); + ++r; + assert(r.equal([6, 7, 8])); +} // BinaryHeap /** diff --git a/std/typecons.d b/std/typecons.d index 17abb2b2c8b..156dd016fe3 100644 --- a/std/typecons.d +++ b/std/typecons.d @@ -2398,7 +2398,8 @@ if (!is(T == class)) Returns $(D true) if and only if the underlying store has been allocated and initialized. */ - @property bool isInitialized() const + @property nothrow + bool isInitialized() const { return _store !is null; } @@ -2495,14 +2496,6 @@ Assignment operators move(rhs, RefCounted._store._payload); } -/** -Returns a reference to the payload. If (autoInit == -RefCountedAutoInitialize.yes), calls $(D -refCountedEnsureInitialized). Otherwise, just issues $(D -assert(refCountedIsInitialized)). - */ - alias refCountedPayload this; - /** Returns a reference to the payload. If (autoInit == RefCountedAutoInitialize.yes), calls $(D @@ -2511,34 +2504,54 @@ assert(refCountedIsInitialized)). Used with $(D alias refCountedPayload this;), so callers can just use the $(D RefCounted) object as a $(D T). */ - @property ref T refCountedPayload() + static if (autoInit == RefCountedAutoInitialize.yes) { - static if (autoInit == RefCountedAutoInitialize.yes) + @property + ref T refCountedPayload() { RefCounted.ensureInitialized(); + return RefCounted._store._payload; } - else + } + else + { + @property nothrow + ref T refCountedPayload() { assert(RefCounted.isInitialized); + return RefCounted._store._payload; } - return RefCounted._store._payload; } -// - @property ref const(T) refCountedPayload() const +/// ditto + static if (autoInit == RefCountedAutoInitialize.yes) { - static if (autoInit == RefCountedAutoInitialize.yes) + @property + ref const(T) refCountedPayload() const { // @@@ //refCountedEnsureInitialized(); assert(RefCounted.isInitialized); + return RefCounted._store._payload; } - else + } + else + { + @property nothrow + ref const(T) refCountedPayload() const { assert(RefCounted.isInitialized); + return RefCounted._store._payload; } - return RefCounted._store._payload; } + +/** +Returns a reference to the payload. If (autoInit == +RefCountedAutoInitialize.yes), calls $(D +refCountedEnsureInitialized). Otherwise, just issues $(D +assert(refCountedIsInitialized)). + */ + alias refCountedPayload this; } unittest