/** High-level interface for allocators. Implements bundled allocation/creation and destruction/deallocation of data including $(D struct)s and $(D class)es, and also array primitives related to allocation. --- // Allocate an int, initialize it with 42 int* p = theAllocator.make!int(42); assert(*p == 42); // Destroy and deallocate it theAllocator.dispose(p); // Allocate using the global process allocator p = processAllocator.make!int(100); assert(*p == 100); // Destroy and deallocate processAllocator.dispose(p); // Create an array of 50 doubles initialized to -1.0 double[] arr = theAllocator.makeArray!double(50, -1.0); // Append two zeros to it theAllocator.growArray(arr, 2, 0.0); // On second thought, take that back theAllocator.shrinkArray(arr, 2); // Destroy and deallocate theAllocator.dispose(arr); --- */ module std.experimental.allocator.porcelain; // Example in intro above unittest { // Allocate an int, initialize it with 42 int* p = theAllocator.make!int(42); assert(*p == 42); // Destroy and deallocate it theAllocator.dispose(p); // Allocate using the global process allocator p = processAllocator.make!int(100); assert(*p == 100); // Destroy and deallocate processAllocator.dispose(p); // Create an array of 50 doubles initialized to -1.0 double[] arr = theAllocator.makeArray!double(50, -1.0); // Append two zeros to it theAllocator.growArray(arr, 2, 0.0); // On second thought, take that back theAllocator.shrinkArray(arr, 2); // Destroy and deallocate theAllocator.dispose(arr); } import std.experimental.allocator.common; import std.traits : isPointer, hasElaborateDestructor; import std.typecons : Flag, Yes, No; import std.algorithm : min; import std.range : isInputRange, isForwardRange, walkLength, save, empty, front, popFront; /** Dynamic version of an allocator. This should be used wherever a uniform type is required for encapsulating various allocator implementations. Methods returning $(D Ternary) return $(D Ternary.yes) upon success, $(D Ternary.no) upon failure, and $(D Ternary.unknown) if the primitive is not implemented by the allocator instance. */ interface IAllocator { /** Returns the alignment offered. */ @property uint alignment(); /** Returns the good allocation size that guarantees zero internal fragmentation. */ size_t goodAllocSize(size_t s); /** Allocates memory. */ void[] allocate(size_t, TypeInfo ti = null); /** Allocates memory with specified alignment. */ Ternary alignedAllocate(size_t, uint, ref void[], TypeInfo ti = null); /** Allocates and returns all memory available to this allocator. $(COMMENT By default returns $(D null).) */ Ternary allocateAll(ref void[] result); /** Expands a memory block in place. If expansion not supported by the allocator, returns $(D Ternary.unknown). If implemented, returns $(D Ternary.yes) if expansion succeeded, $(D Ternary.no) otherwise. */ Ternary expand(ref void[], size_t); /// Reallocates a memory block. bool reallocate(ref void[], size_t); /// Reallocates a memory block with specified alignment. Ternary alignedReallocate(ref void[] b, size_t size, uint alignment); /** Returns $(D Ternary.yes) if the allocator owns $(D b), $(D Ternary.no) if the allocator doesn't own $(D b), and $(D Ternary.unknown) if ownership not supported by the allocator. $(COMMENT By default returns $(D Ternary.unknown).) */ Ternary owns(void[] b); /** Resolves an internal pointer to the full block allocated. */ Ternary resolveInternalPointer(void* p, ref void[] result); /** Deallocates a memory block. Returns $(D Ternary.unknown) if deallocation is not supported. A simple way to check that an allocator supports deallocation is to call $(D deallocate(null)). */ Ternary deallocate(void[] b); /** Deallocates all memory. Returns $(D Ternary.unknown) if deallocation is not supported. */ Ternary deallocateAll(); /** Returns $(D Ternary.yes) if no memory is currently allocated from this allocator, $(D Ternary.no) if some allocations are currently active, or $(D Ternary.unknown) if not supported. */ Ternary empty(); /** Returns $(D Ternary.yes) if memory is zeroed upon an allocation, $(D Ternary.no) if not, or $(D Ternary.unknown) if primitive not supported. */ Ternary zeroesAllocations(); } __gshared IAllocator _processAllocator; IAllocator _threadAllocator; shared static this() { assert(!_processAllocator); import std.experimental.allocator.gc_allocator : GCAllocator; _processAllocator = allocatorObject(GCAllocator.it); } static this() { assert(!_threadAllocator); _threadAllocator = _processAllocator; } /** Gets/sets the allocator for the current thread. This is the default allocator that should be used for allocating thread-local memory. For allocating memory to be shared across threads, use $(D processAllocator) (below). By default, $(D theAllocator) ultimately fetches memory from $(D processAllocator), which in turn uses the garbage collected heap. */ @property IAllocator theAllocator() { return _threadAllocator; } /// Ditto @property void theAllocator(IAllocator a) { assert(a); _threadAllocator = a; } /// unittest { // Install a new allocator that is faster for 128-byte allocations. import std.experimental.allocator.free_list, std.experimental.allocator.gc_allocator; auto oldAllocator = theAllocator; scope(exit) theAllocator = oldAllocator; theAllocator = allocatorObject(FreeList!(GCAllocator, 128)()); // Use the now changed allocator to allocate an array ubyte[] arr = theAllocator.makeArray!ubyte(128); assert(arr.ptr); //... } /** Gets/sets the allocator for the current process. This allocator must be used for allocating memory shared across threads. Objects created using this allocator can be cast to $(D shared). */ @property IAllocator processAllocator() { return _processAllocator; } /// Ditto @property void processAllocator(IAllocator a) { assert(a); _processAllocator = a; } unittest { assert(processAllocator); assert(processAllocator is theAllocator); } /** Returns a dynamically-typed $(D CAllocator) built around a given statically- typed allocator $(D a) of type $(D A). Passing a pointer to the allocator creates a dynamic allocator around the allocator pointed to by the pointer, without attempting to copy or move it. Passing the allocator by value or reference behaves as follows. $(UL $(LI If $(D A) has no state, the resulting object is allocated in static shared storage.) $(LI If $(D A) has state and is copyable, the result will store a copy of it within. The result itself is allocated in its own statically-typed allocator.) $(LI If $(D A) has state and is not copyable, the result will move the passed-in argument into the result. The result itself is allocated in its own statically-typed allocator.) ) */ CAllocatorImpl!A allocatorObject(A)(auto ref A a) if (!isPointer!A) { import std.conv : emplace; static if (stateSize!A == 0) { enum s = stateSize!(CAllocatorImpl!A).divideRoundUp(ulong.sizeof); static __gshared ulong[s] state; static __gshared CAllocatorImpl!A result; if (!result) { // Don't care about a few races result = emplace!(CAllocatorImpl!A)(state[]); } assert(result); return result; } else static if (is(typeof({ A b = a; A c = b; }))) // copyable { auto state = a.allocate(stateSize!(CAllocatorImpl!A)); import std.traits : hasMember; static if (hasMember!(A, "deallocate")) { scope(failure) a.deallocate(state); } return cast(CAllocatorImpl!A) emplace!(CAllocatorImpl!A)(state); } else // the allocator object is not copyable { // This is sensitive... create on the stack and then move enum s = stateSize!(CAllocatorImpl!A).divideRoundUp(ulong.sizeof); ulong[s] state; import std.algorithm : move; emplace!(CAllocatorImpl!A)(state[], move(a)); auto dynState = a.allocate(stateSize!(CAllocatorImpl!A)); // Bitblast the object in its final destination dynState[] = state[]; return cast(CAllocatorImpl!A) dynState.ptr; } } /// Ditto CAllocatorImpl!(A, Yes.indirect) allocatorObject(A)(A* pa) { assert(pa); import std.conv : emplace; auto state = pa.allocate(stateSize!(CAllocatorImpl!(A, Yes.indirect))); import std.traits : hasMember; static if (hasMember!(A, "deallocate")) { scope(failure) pa.deallocate(state); } return emplace!(CAllocatorImpl!(A, Yes.indirect)) (state, pa); } /// unittest { import std.experimental.allocator.mallocator; IAllocator a = allocatorObject(Mallocator.it); auto b = a.allocate(100); assert(b.length == 100); assert(a.deallocate(b) == Ternary.yes); // The in-situ region must be used by pointer import std.experimental.allocator.region; auto r = InSituRegion!1024(); a = allocatorObject(&r); b = a.allocate(200); assert(b.length == 200); // In-situ regions can't deallocate assert(a.deallocate(b) == Ternary.unknown); } /** Dynamically allocates (using $(D alloc)) and then creates in the memory allocated an object of type $(D T), using $(D args) (if any) for its initialization. Initialization occurs in the memory allocated and is otherwise semantically the same as $(D T(args)). (Note that using $(D alloc.make!(T[])) creates a pointer to an (empty) array of $(D T)s, not an array. To use an allocator to allocate and initialize an array, use $(D alloc.makeArray!T) described below.) Params: T = Type of the object being created. alloc = The allocator used for getting the needed memory. It may be an object implementing the static interface for allocators, or an $(D IAllocator) reference. args = Optional arguments used for initializing the created object. If not present, the object is default constructed. Returns: If $(D T) is a class type, returns a reference to the created $(D T) object. Otherwise, returns a $(D T*) pointing to the created object. In all cases, returns $(D null) if allocation failed. Throws: If $(D T)'s constructor throws, deallocates the allocated memory and propagates the exception. */ auto make(T, Allocator, A...)(auto ref Allocator alloc, auto ref A args) { import std.algorithm : max; import std.conv : emplace; auto m = alloc.allocate(max(stateSize!T, 1)); if (!m.ptr) return null; scope(failure) alloc.deallocate(m); static if (is(T == class)) return emplace!T(m, args); else return emplace(cast(T*) m.ptr, args); } /// unittest { // Dynamically allocate one integer int* p1 = theAllocator.make!int; // It's implicitly initialized with its .init value assert(*p1 == 0); // Dynamically allocate one double, initialize to 42.5 double* p2 = theAllocator.make!double(42.5); assert(*p2 == 42.5); // Dynamically allocate a struct static struct Point { int x, y, z; } // Use the generated constructor taking field values in order Point* p = theAllocator.make!Point(1, 2); assert(p.x == 1 && p.y == 2 && p.z == 0); // Dynamically allocate a class object static class Customer { uint id = uint.max; this() {} this(uint id) { this.id = id; } // ... } Customer cust = theAllocator.make!Customer; assert(cust.id == uint.max); // default initialized cust = theAllocator.make!Customer(42); assert(cust.id == 42); } unittest { void test(Allocator)(auto ref Allocator alloc) { int* a = alloc.make!int(10); assert(*a == 10); struct A { int x; string y; double z; } A* b = alloc.make!A(42); assert(b.x == 42); assert(b.y is null); import std.math; assert(b.z.isNaN); b = alloc.make!A(43, "44", 45); assert(b.x == 43); assert(b.y == "44"); assert(b.z == 45); static class B { int x; string y; double z; this(int _x, string _y = null, double _z = double.init) { x = _x; y = _y; z = _z; } } B c = alloc.make!B(42); assert(c.x == 42); assert(c.y is null); import std.math; assert(c.z.isNaN); c = alloc.make!B(43, "44", 45); assert(c.x == 43); assert(c.y == "44"); assert(c.z == 45); auto parray = alloc.make!(int[]); assert((*parray).empty); } import std.experimental.allocator.gc_allocator : GCAllocator; test(GCAllocator.it); test(theAllocator); } private void fillWithMemcpy(T)(void[] array, auto ref T filler) nothrow { import core.stdc.string : memcpy; if (!array.length) return; memcpy(array.ptr, &filler, T.sizeof); // Fill the array from the initialized portion of itself exponentially. for (size_t offset = T.sizeof; offset < array.length; ) { size_t extent = min(offset, array.length - offset); memcpy(array.ptr + offset, array.ptr, extent); offset += extent; } } unittest { int[] a; fillWithMemcpy(a, 42); assert(a.length == 0); a = [ 1, 2, 3, 4, 5 ]; fillWithMemcpy(a, 42); assert(a == [ 42, 42, 42, 42, 42]); } private T[] uninitializedFillDefault(T)(T[] array) nothrow { static immutable __gshared T t; fillWithMemcpy(array, t); return array; } unittest { int[] a = [1, 2, 4]; uninitializedFillDefault(a); assert(a == [0, 0, 0]); } /** Create an array of $(D T) with $(D length) elements using $(D alloc). The array is either default-initialized, filled with copies of $(D init), or initialized with values fetched from $(R range). Params: T = element type of the array being created alloc = the allocator used for getting memory length = length of the newly created array init = element used for filling the array range = range used for initializing the array elements Returns: The newly-created array, or $(D null) if either $(D length) was $(D 0) or allocation failed. Throws: The first two overloads throw only if $(T alloc)'s primitives do. The overloads that involve copy initialization deallocate memory and propagate the exception if the copy operation throws. */ T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length) { if (!length) return null; auto m = alloc.allocate(T.sizeof * length); if (!m.ptr) return null; return uninitializedFillDefault(cast(T[]) m); } unittest { void test(A)(auto ref A alloc) { int[] a = alloc.makeArray!int(0); assert(a.length == 0 && a.ptr is null); a = alloc.makeArray!int(5); assert(a.length == 5); assert(a == [ 0, 0, 0, 0, 0]); } import std.experimental.allocator.gc_allocator : GCAllocator; test(GCAllocator.it); test(theAllocator); } /// Ditto T[] makeArray(T, Allocator)(auto ref Allocator alloc, size_t length, auto ref T init) { if (!length) return null; auto m = alloc.allocate(T.sizeof * length); if (!m.ptr) return null; auto result = cast(T[]) m; import std.traits : hasElaborateCopyConstructor; static if (hasElaborateCopyConstructor!T) { scope(failure) alloc.deallocate(m); size_t i = 0; static if (hasElaborateDestructor!T) { scope (failure) { foreach (j; 0 .. i) { destroy(result[j]); } } } for (; i < length; ++i) { emplace!T(result.ptr + i, init); } } else { fillWithMemcpy(result, init); } return result; } /// unittest { int[] a = theAllocator.makeArray!int(2); assert(a == [0, 0]); a = theAllocator.makeArray!int(3, 42); assert(a == [42, 42, 42]); import std.range : only; a = theAllocator.makeArray!int(only(42, 43, 44)); assert(a == [42, 43, 44]); } unittest { void test(A)(auto ref A alloc) { long[] a = alloc.makeArray!long(0, 42); assert(a.length == 0 && a.ptr is null); a = alloc.makeArray!long(5, 42); assert(a.length == 5); assert(a == [ 42, 42, 42, 42, 42 ]); } import std.experimental.allocator.gc_allocator : GCAllocator; test(GCAllocator.it); test(theAllocator); } /// Ditto T[] makeArray(T, Allocator, R)(auto ref Allocator alloc, R range) if (isInputRange!R) { static if (isForwardRange!R) { size_t length = walkLength(range.save); if (!length) return null; auto m = alloc.allocate(T.sizeof * length); if (!m.ptr) return null; auto result = cast(T[]) m; size_t i = 0; scope (failure) { foreach (j; 0 .. i) { destroy(result[j]); } alloc.deallocate(m); } for (; !range.empty; range.popFront, ++i) { import std.conv : emplace; emplace!T(result.ptr + i, range.front); } return result; } else { // Estimated size size_t estimated = 8; auto m = alloc.allocate(T.sizeof * estimated); if (!m.ptr) return null; auto result = cast(T[]) m; size_t initialized = 0; void bailout() { foreach (i; 0 .. initialized) { destroy(result[i]); } alloc.deallocate(m); } scope (failure) bailout; for (; !range.empty; range.popFront, ++initialized) { if (initialized == estimated) { // Need to reallocate if (!alloc.reallocate(m, T.sizeof * (estimated *= 2))) { bailout; return null; } result = cast(T[]) m; } import std.conv : emplace; emplace!T(result.ptr + initialized, range.front); } // Try to shrink memory, no harm if not possible if (initialized < estimated && alloc.reallocate(m, T.sizeof * initialized)) { result = cast(T[]) m; } return result[0 .. initialized]; } } unittest { void test(A)(auto ref A alloc) { long[] a = alloc.makeArray!long((int[]).init); assert(a.length == 0 && a.ptr is null); a = alloc.makeArray!long([5, 42]); assert(a.length == 2); assert(a == [ 5, 42]); } import std.experimental.allocator.gc_allocator : GCAllocator; test(GCAllocator.it); test(theAllocator); } version(unittest) { private struct ForcedInputRange { int[]* array; bool empty() { return !array || (*array).empty; } ref int front() { return (*array)[0]; } void popFront() { *array = (*array)[1 .. $]; } } } unittest { import std.array, std.range; int[] arr = iota(10).array; void test(A)(auto ref A alloc) { ForcedInputRange r; long[] a = alloc.makeArray!long(r); assert(a.length == 0 && a.ptr is null); auto arr2 = arr; r.array = &arr2; a = alloc.makeArray!long(r); assert(a.length == 10); assert(a == iota(10).array); } import std.experimental.allocator.gc_allocator : GCAllocator; test(GCAllocator.it); test(theAllocator); } /** Grows $(D array) by appending $(D delta) more elements. The needed memory is allocated using $(D alloc). The extra elements added are either default-initialized, filled with copies of $(D init), or initialized with values fetched from $(R range). Params: T = element type of the array being created alloc = the allocator used for getting memory array = a reference to the array being grown delta = number of elements to add (upon success the new length of $(D array) is $(D array.length + delta)) init = element used for filling the array range = range used for initializing the array elements Returns: $(D true) upon success, $(D false) if memory could not be allocated. In the latter case $(D array) is left unaffected. Throws: The first two overloads throw only if $(T alloc)'s primitives do. The overloads that involve copy initialization deallocate memory and propagate the exception if the copy operation throws. */ bool growArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, size_t delta) { if (!delta) return true; immutable oldLength = array.length; void[] buf = array; if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) return false; array = cast(T[]) buf; array[oldLength .. $].uninitializedFillDefault; return true; } unittest { void test(A)(auto ref A alloc) { auto arr = alloc.makeArray!int([1, 2, 3]); assert(alloc.growArray(arr, 3)); assert(arr == [1, 2, 3, 0, 0, 0]); } import std.experimental.allocator.gc_allocator : GCAllocator; test(GCAllocator.it); test(theAllocator); } /// Ditto auto growArray(T, Allocator)(auto ref Allocator alloc, T[] array, size_t delta, auto ref T init) { if (!delta) return true; void[] buf = array; if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) return false; immutable oldLength = array.length; array = cast(T[]) buf; scope(failure) array[oldLength .. $].uninitializedFillDefault; import std.algorithm : uninitializedFill; array[oldLength .. $].uninitializedFill(init); return true; } /// Ditto auto growArray(T, Allocator, R)(auto ref Allocator alloc, ref T[] array, R range) if (isInputRange!R) { static if (isForwardRange!R) { immutable delta = walkLength(range.save); if (!delta) return true; immutable oldLength = array.length; // Reallocate support memory void[] buf = array; if (!alloc.reallocate(buf, buf.length + T.sizeof * delta)) { return false; } array = cast(T[]) buf; // At this point we're committed to the new length. auto toFill = array[oldLength .. $]; scope (failure) { // Fill the remainder with default-constructed data toFill.uninitializedFillDefault; } for (; !range.empty; range.popFront, toFill.popFront) { assert(!toFill.empty); import std.conv : emplace; emplace!T(&toFill.front, range.front); } assert(toFill.empty); } else { scope(failure) { // The last element didn't make it, fill with default array[$ - 1 .. $].uninitializedFillDefault; } void[] buf = array; for (; !range.empty; range.popFront) { if (!alloc.reallocate(buf, buf.length + T.sizeof)) { array = cast(T[]) buf; return false; } import std.conv : emplace; emplace!T(buf[$ - T.sizeof .. $], range.front); } array = cast(T[]) buf; } return true; } /// unittest { auto arr = theAllocator.makeArray!int([1, 2, 3]); assert(theAllocator.growArray(arr, 2)); assert(arr == [1, 2, 3, 0, 0]); import std.range : only; assert(theAllocator.growArray(arr, only(4, 5))); assert(arr == [1, 2, 3, 0, 0, 4, 5]); ForcedInputRange r; int[] b = [ 1, 2, 3, 4 ]; auto temp = b; r.array = &temp; assert(theAllocator.growArray(arr, r)); assert(arr == [1, 2, 3, 0, 0, 4, 5, 1, 2, 3, 4]); } /** Shrinks an array by $(D delta) elements. If $(D array.length < delta), does nothing and returns false. Otherwise, destroys the last $(D array.length - delta) elements in the array and then reallocates the array's buffer. If reallocation fails, fills the array with default-initialized data. Params: T = element type of the array being created alloc = the allocator used for getting memory array = a reference to the array being shrunk delta = number of elements to remove (upon success the new length of $(D array) is $(D array.length - delta)) Returns: $(D true) upon success, $(D false) if memory could not be reallocated. In the latter case $(D array) is left with all elements default-initialized. Throws: The first two overloads throw only if $(T alloc)'s primitives do. The overloads that involve copy initialization deallocate memory and propagate the exception if the copy operation throws. */ bool shrinkArray(T, Allocator)(auto ref Allocator alloc, ref T[] array, size_t delta) { if (delta > array.length) return false; // Destroy elements. If a destructor throws, fill the already destroyed // stuff with the default initializer. { size_t destroyed; scope(failure) { array[$ - delta .. $][0 .. destroyed].uninitializedFillDefault; } foreach (ref e; array[$ - delta .. $]) { e.destroy; ++destroyed; } } if (delta == array.length) { alloc.deallocate(array); array = null; return true; } void[] buf = array; if (!alloc.reallocate(buf, buf.length - T.sizeof * delta)) { // urgh, at least fill back with default array[$ - delta .. $].uninitializedFillDefault; return false; } array = cast(T[]) buf; return true; } /// unittest { int[] a = theAllocator.makeArray!int(100, 42); assert(a.length == 100); assert(theAllocator.shrinkArray(a, 98)); assert(a.length == 2); assert(a == [42, 42]); } unittest { void test(A)(auto ref A alloc) { long[] a = alloc.makeArray!long((int[]).init); assert(a.length == 0 && a.ptr is null); a = alloc.makeArray!long(100, 42); assert(alloc.shrinkArray(a, 98)); assert(a.length == 2); assert(a == [ 42, 42]); } import std.experimental.allocator.gc_allocator : GCAllocator; test(GCAllocator.it); test(theAllocator); } /** Destroys and then deallocates (using $(D alloc)) the object pointed to by a pointer, the class object referred to by a $(D class) or $(D interface) reference, or an entire array. It is assumed the respective entities had been allocated with the same allocator. */ void dispose(A, T)(auto ref A alloc, T* p) { static if (hasElaborateDestructor!T) { destroy(*p); } alloc.deallocate(p[0 .. T.sizeof]); } /// Ditto void dispose(A, T)(auto ref A alloc, T p) if (is(T == class) || is(T == interface)) { if (!p) return; auto support = (cast(void*) p)[0 .. typeid(p).init.length]; destroy(p); alloc.deallocate(support); } /// Ditto void dispose(A, T)(auto ref A alloc, T[] array) { static if (hasElaborateDestructor!(typeof(array[0]))) { foreach (ref e; array) { destroy(e); } } alloc.deallocate(array); } unittest { static int x; static interface I { void method(); } static class A : I { int y; override void method() { x = 21; } ~this() { x = 42; } } static class B : A { } auto a = theAllocator.make!A; a.method(); assert(x == 21); theAllocator.dispose(a); assert(x == 42); B b = theAllocator.make!B; b.method(); assert(x == 21); theAllocator.dispose(b); assert(x == 42); I i = theAllocator.make!B; i.method(); assert(x == 21); theAllocator.dispose(i); assert(x == 42); int[] arr = theAllocator.makeArray!int(43); theAllocator.dispose(arr); } /** Implementation of $(D IAllocator) using $(D Allocator). This adapts a statically-built allocator type to $(D IAllocator) that is directly usable by non-templated code. Usually $(D CAllocatorImpl) is used indirectly by calling $(LREF theAllocator). */ class CAllocatorImpl(Allocator, Flag!"indirect" indirect = No.indirect) : IAllocator { import std.traits : hasMember; /** The implementation is available as a public member. */ static if (indirect) { private Allocator* pimpl; ref Allocator impl() { return *pimpl; } this(Allocator* pa) { pimpl = pa; } } else { static if (stateSize!Allocator) Allocator impl; else alias impl = Allocator.it; } /// Returns $(D impl.alignment). override @property uint alignment() { return impl.alignment; } /** Returns $(D impl.goodAllocSize(s)). */ override size_t goodAllocSize(size_t s) { return impl.goodAllocSize(s); } /** Returns $(D impl.allocate(s)). */ override void[] allocate(size_t s, TypeInfo ti = null) { return impl.allocate(s); } /** If $(D impl.alignedAllocate) exists, calls it, puts the result in $(D r), and returns $(D Ternary.yes) or $(D Ternary.no) indicating whether allocation succeded. If $(D impl.alignedAllocate) is not defined, returns $(D Ternary.unknown). */ override Ternary alignedAllocate(size_t s, uint a, ref void[] r, TypeInfo ti = null) { static if (!hasMember!(Allocator, "alignedAllocate")) { return Ternary.unknown; } else { r = impl.alignedAllocate(s, a); return Ternary(r.ptr !is null); } } /** Overridden only if $(D Allocator) implements $(D owns). In that case, returns $(D impl.owns(b)). */ override Ternary owns(void[] b) { static if (hasMember!(Allocator, "owns")) return Ternary(impl.owns(b)); else return Ternary.unknown; } /// Returns $(D impl.expand(b, s)) if defined, $(D false) otherwise. override Ternary expand(ref void[] b, size_t s) { static if (hasMember!(Allocator, "expand")) return Ternary(impl.expand(b, s)); else return Ternary.unknown; } /// Returns $(D impl.reallocate(b, s)). override bool reallocate(ref void[] b, size_t s) { return impl.reallocate(b, s); } /// Forwards to $(D impl.alignedReallocate). Ternary alignedReallocate(ref void[] b, size_t s, uint a) { static if (!hasMember!(Allocator, "alignedAllocate")) { return Ternary.unknown; } else { return Ternary(impl.alignedReallocate(b, s, a)); } } Ternary resolveInternalPointer(void* p, ref void[] result) { static if (hasMember!(Allocator, "resolveInternalPointer")) { result = impl.resolveInternalPointer(p); return Ternary(result.ptr !is null); } else { return Ternary.unknown; } } /** If $(D impl.deallocate) is not defined, returns $(D Ternary.unknown). If $(D impl.deallocate) returns $(D void) (the common case), calls it and returns $(D Ternary.yes). If $(D impl.deallocate) returns $(D bool), calls it and returns $(D Ternary.yes) for $(D true), $(D Ternary.no) for $(D false). */ override Ternary deallocate(void[] b) { static if (hasMember!(Allocator, "deallocate")) { static if (is(typeof(impl.deallocate(b)) == bool)) { return Ternary(impl.deallocate(b)); } else { impl.deallocate(b); return Ternary.yes; } } else { return Ternary.unknown; } } /** Calls $(D impl.deallocateAll()) and returns $(D Ternary.yes) if defined, otherwise returns $(D Ternary.unknown). */ override Ternary deallocateAll() { static if (hasMember!(Allocator, "deallocateAll")) { impl.deallocateAll(); return Ternary.yes; } else { return Ternary.unknown; } } /** Forwards to $(D impl.empty()) if defined, otherwise returns $(D Ternary.unknown). */ override Ternary empty() { static if (hasMember!(Allocator, "empty")) { return Ternary(impl.empty); } else { return Ternary.unknown; } } /** Returns $(D impl.allocateAll()) if present, $(D null) otherwise. */ override Ternary allocateAll(ref void[] result) { static if (hasMember!(Allocator, "allocateAll")) { result = impl.allocateAll(); return Ternary(result.ptr !is null); } else { return Ternary.unknown; } } /** Forwards to $(D impl.zeroesAllocations) if defined, otherwise returns $(D Ternary.unknown). */ override Ternary zeroesAllocations() { static if (hasMember!(Allocator, "zeroesAllocations")) { return Ternary(impl.zeroesAllocations); } else { return Ternary.unknown; } } }