1 /** 2 * An std::vector-like type for deterministic lifetime. 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.utils.vec; 15 16 import std.algorithm.mutation : swap, move; 17 import std.meta : allSatisfy; 18 19 package(ae): 20 21 /* 22 An array type with deterministic lifetime. 23 24 Properties: 25 - Owns its data 26 - If copied, will copy its contents 27 - Use pointers / `ref` or `opSlice` to avoid copying 28 - Use `std.typecons.RefCounted` for reference counting 29 - If destroyed, will destroy (clobber) its contents 30 - O(1) indexing 31 - Does not work with `.init`-less types 32 (wrap in `Nullable` to avoid this) 33 34 Differences from std.containers.array.Array: 35 - Memory-safe 36 - Like D arrays, has an initial null state (distinct from the empty state) 37 - No reference counting 38 - Uses the D GC heap 39 - Separates object lifetime from memory lifetime: 40 the latter is still managed by the GC, 41 so `Vec` is always memory-safe regardless of how you try to (mis-)use it 42 */ 43 struct Vec(T) 44 { 45 // Lifetime 46 47 /// Construct from a list or slice of values 48 this(scope T[] values...) 49 { 50 data = values.dup; 51 } 52 53 private enum bool isConstituent(C) = is(C == T) || is(C == T[]) || is(C == Vec!T); 54 55 /// Construct from any combination of values, slices of values, or 56 /// other `Vec` instances 57 this(Args...)(auto ref scope Args args) 58 if (allSatisfy!(isConstituent, Args)) 59 { 60 size_t length; 61 foreach (ref arg; args) 62 static if (is(typeof(arg) == T)) 63 length++; 64 else 65 length += arg.length; 66 data = new T[length]; 67 size_t p = 0; 68 foreach (ref arg; args) 69 static if (is(typeof(arg) == T)) 70 data[p++] = arg; 71 else 72 { 73 static if (is(typeof(arg) == Vec!T)) 74 data[p .. p + arg.length] = arg.data[]; 75 else 76 data[p .. p + arg.length] = arg[]; 77 p += arg.length; 78 } 79 } 80 81 /// To avoid performance pitfalls, implicit copying is disabled. 82 /// Use `.dup` instead. 83 @disable this(this); 84 85 /// Create a shallow copy of this `Vec`. 86 Vec!T dup() 87 { 88 typeof(return) result; 89 result.data = data.dup; 90 return result; 91 } 92 93 ~this() 94 { 95 data[] = T.init; 96 data = null; 97 } 98 99 /// Array primitives 100 101 @property size_t length() const { return data.length; } 102 alias opDollar = length; /// ditto 103 104 @property size_t length(size_t newLength) 105 { 106 if (newLength < data.length) 107 { 108 data[newLength .. $] = T.init; 109 data = data[0 .. newLength]; 110 } 111 else 112 if (newLength > data.length) 113 { 114 T[] newData; 115 if (newLength <= data.capacity) 116 { 117 newData = data; 118 newData.length = newLength; 119 assert(newData.ptr == data.ptr); 120 } 121 else 122 { 123 newData = new T[newLength]; 124 foreach (i; 0 .. data.length) 125 move(data[i], newData[i]); 126 } 127 data = newData; 128 } 129 return data.length; 130 } /// ditto 131 132 T opCast(T)() if (is(T == bool)) 133 { 134 return !!data; 135 } /// ditto 136 137 ref inout(T) opIndex(size_t index) inout 138 { 139 return data[index]; 140 } /// ditto 141 142 typeof(null) opAssign(typeof(null)) { data[] = T.init; data = null; return null; } /// ditto 143 144 ref Vec opOpAssign(string op : "~")(scope T[] values...) 145 { 146 auto oldLength = length; 147 length = oldLength + values.length; 148 data[oldLength .. $] = values; 149 return this; 150 } /// ditto 151 152 /// Range-like primitives 153 154 @property bool empty() const { return !data.length; } 155 156 ref inout(T) front() inout { return data[0]; } /// ditto 157 ref inout(T) back() inout { return data[$-1]; } /// ditto 158 159 void popFront() 160 { 161 data[0] = T.init; 162 data = data[1 .. $]; 163 } /// ditto 164 165 void popBack() 166 { 167 data[$-1] = T.init; 168 data = data[0 .. $-1]; 169 } /// ditto 170 171 // Other operations 172 173 /// Return a slice of the held items. 174 /// Ownership is unaffected, so this is a "view" into the contents. 175 /// Can be used to perform range operations and iteration. 176 inout(T)[] opSlice() inout 177 { 178 return data; 179 } 180 181 /// Remove the element with the given `index`, shifting all 182 /// elements after it to the left. 183 void remove(size_t index) 184 { 185 foreach (i; index + 1 .. data.length) 186 move(data[i], data[i - 1]); 187 data = data[0 .. $ - 1]; 188 } 189 190 private: 191 T[] data; 192 } 193 194 // Test object lifetime 195 unittest 196 { 197 struct S 198 { 199 static int numLive; 200 bool alive; 201 this(bool) { alive = true; numLive++; } 202 this(this) { if (alive) numLive++; } 203 ~this() { if (alive) numLive--; } 204 } 205 206 Vec!S v; 207 assert(S.numLive == 0); 208 v = Vec!S(S(true)); 209 assert(S.numLive == 1); 210 v ~= S(true); 211 assert(S.numLive == 2); 212 auto v2 = v.dup; 213 assert(S.numLive == 4); 214 v2 = null; 215 assert(S.numLive == 2); 216 v.popFront(); 217 assert(S.numLive == 1); 218 v.popBack(); 219 assert(S.numLive == 0); 220 } 221 222 // Test iteration 223 unittest 224 { 225 // Ensure iterating twice over a Vec does not consume it. 226 auto v = Vec!int(1, 2, 3); 227 foreach (i; v) {} 228 int sum; 229 foreach (i; v) sum += i; 230 assert(sum == 6); 231 }