1 module nxt.container.hybrid_hashmap; 2 3 import core.internal.hash : hashOf; 4 import nxt.nullable_traits : isNullable; 5 import nxt.allocator_traits : isAllocator; 6 import nxt.container.common : BorrowCheckFlag; 7 import nxt.my_mallocator : Mallocator; 8 9 enum PrimeCapacityFlag : ubyte { no, yes } 10 11 @safe: 12 13 /** Hash table/map with open-addressing and hybrid storage, storing `keys` of 14 * type `K` and values of type `V`. Setting `V` to `void` turns the map into a 15 * set. 16 * 17 * Keys are immutable except for when they are `class`es in which case they are 18 * head-const (through bin reinterpretation to `KeyValueType`), This can be 19 * overridden by setting `keyEqualPred` to, for instance, `a == b` for `class` 20 * keys. 21 * 22 * Uses quadratic probing (using triangular numbers) unless `usePrimeCapacity` 23 * in which case a simpler probing is used. 24 * 25 * Deletion/Removal of elements is lazy via the sentinel bitmap `_holesPtr` or 26 * through assignment of reserved value of `KeyType` when `KeyType` has 27 * hole/tombstone-sentinel-support via trait `isHoleable`. The terms "tombstone" 28 * and "sentinel" are used at, for instance, 29 * https://engineering.fb.com/2019/04/25/developer-tools/f14/ and 30 * https://www.youtube.com/watch?v=ncHmEUmJZf4&t=2837s. 31 * 32 * Element iteration via 33 * - either `byKey`, `byValue` or `byKeyValue` over `HybridHashMap` and 34 * - `byElement` over `HybridHashSet` 35 * respects taking the container argument either as an l-value or r-value using 36 * detected using `auto ref`-qualified parameter introspected using `(__traits(isRef, y))`. 37 * In the r-value case no reference counting is needed. 38 * In the l-value case setting `borrowChecked` to `true` adds run-time 39 * support for dynamic Rust-style ownership and borrowing between the range and the container. 40 * 41 * Params: 42 * K = key type 43 * V = value type 44 * hasher = hash function or std.digest Hash 45 * Allocator = memory allocator for bin array 46 * borrowChecked = only activate when it's certain that this won't be moved via std.algorithm.mutation.move() 47 * linearSearchMaxSize = Use linear search instead of probing when `_store.sizeof <= linearSearchMaxSize` 48 * usePrimeCapacity = Use prime numbers as capacity of hash table enabling better performance of simpler hash-functions 49 * 50 * See_Also: https://engineering.fb.com/2019/04/25/developer-tools/f14/ 51 * See_Also: https://github.com/abseil/abseil-cpp/blob/master/absl/container/flat_hash_map.h 52 * See_Also: https://www.sebastiansylvan.com/post/robin-hood-hashing-should-be-your-default-hash-table-implementation/ 53 * See_Also: https://arxiv.org/abs/1605.04031 54 * See_Also: https://github.com/Tessil/robin-map 55 * See_Also: https://github.com/martinus/robin-hood-hashing 56 * See_Also: https://probablydance.com/2017/02/26/i-wrote-the-fastest-hashtable/ 57 * See_Also: https://en.wikipedia.org/wiki/Lazy_deletion 58 * See_Also: https://forum.dlang.org/post/ejqhcsvdyyqtntkgzgae@forum.dlang.org 59 * See_Also: https://gankro.github.io/blah/hashbrown-insert/ 60 * 61 * Test: dmd -vcolumns -preview=fieldwise -debug -g -unittest -checkaction=context -allinst -main -unittest -I../.. -i -run hybrid_hashmap.d 62 * 63 * TODO: Allocate _holesPtr array together with _store to reduce size of 64 * `HybridHashMap` to 3 words when element type doesn't support it 65 * TODO: Support non-nullable keys via extra bit-set _nullsPtr similar to _holesPtr 66 * TODO: Don’t allocate _holesPtr when we don’t need removal 67 * 68 * TODO: Add class type erasure layer to void* for the relevant members of HashMap store and factor out that store to container/store.d 69 * 70 * TODO: Make sure key classes and pointers are shifted down by alignement as 71 * is done in AlignedAddress.toHash before hashed 72 * 73 * TODO: Replace `withCapacity` with `void capacity(size_t)` like D arrays. 74 * 75 * TODO: Tests fails when `linearSearchMaxSize` is set to 0 76 * 77 * TODO: A test fails with when -preview=fieldwise is not passed. 78 * 79 * TODO: Add support for opApply by copying opApply members in 80 * ~/.dub/packages/emsi_containers-0.8.0/emsi_containers/src/containers/hashmap.d 81 * 82 * TODO: Replace remove branching on `passElementByValue` when 83 * https://github.com/dlang/dmd/pull/11000 has been merged and use `in` instead 84 * 85 * TODO: Implement `foreach (const k, const v; _)` support. See 86 * https://forum.dlang.org/post/tj700o$1412$1@digitalmars.com 87 * 88 * TODO: Disable pragma(inline, true) and rebenchmark 89 * 90 * TODO: Robin-hood case introspect key type for storage of parts of hash 91 * alongside nullValue and holeValue. Typically for address-types that doesn't 92 * need scanning. Example is `Address` for implementation of GC. This is a D 93 * showcase for code that is difficult to write in C++. 94 * 95 * TODO: group `nxt.probing` functions in `Prober` struct given as type template 96 * param to `HybridHashMap` 97 * 98 * TODO: Make load factor dependent on current capacity or length and perhaps 99 * also type and hash-function to get memory efficiency when it matters. Similar 100 * to what is recommended in https://ticki.github.io/blog/horrible/. 101 * 102 * TODO: For copyable types replace `auto ref` with logic that only passes by 103 * `ref` when it's faster to do so. See_Also: 104 * https://github.com/dlang/dmd/pull/11000. When `-preview=in` has been made 105 * the default this complexity can be removed. Or replace `ref` with `in`. 106 * 107 * TODO: Remove use of `static if (__traits(isCopyable, ...))` and `static if 108 * (__traits(isPOD, ...))` in cases where compiler can handle more moves 109 * 110 * TODO: Use mmap allocator when `_store.sizeof` is larger than at least 8 pages 111 * 112 * TODO: Use `StoreK` in store and cast between it and `KeyType` 113 * 114 * TODO: Fix bug in `growInPlaceWithCapacity` and benchmark 115 * 116 * TODO: Modify existing unittest for `struct Rel { const string name; }` 117 * 118 * TODO: Use allocator.dispose() instead of allocator.deallocate() as in 119 * https://github.com/dlang-community/containers 120 * 121 * TODO: if hash-function is cast(size_t)(classInstance) always use prime length 122 * and shift pointer before hash based on alignof (might not be needed when 123 * module prime) to maximize memory locality when adding successively allocated 124 * pointers 125 * 126 * TODO: Add extractElement that moves it out similar to 127 * http://en.cppreference.com/w/cpp/container/unordered_set/extract 128 * 129 * TODO: Add merge or union algorithm here or into container/common.d. See also: 130 * http://en.cppreference.com/w/cpp/container/unordered_set/merge. this 131 * algorithm moves elements from source if they are not already in `this` 132 * 133 * TODO: Robin-Hood-hashing 134 * 135 * TODO: Enable `borrowChecked` unconditionally in version(debug) if and when 136 * `opPostMove` is implemented, in which case opPostMove() should assert false 137 * if this is borrowed. See: https://github.com/dlang/DIPs/pull/109 138 * 139 * TODO: Save one word by making `_store.length` be inferred by 140 * `prime_modulo.primeConstants[_primeIndex]` if this is not too costly. 141 * 142 * TODO: Only add one extra element to capacity when `assumeNonFullHaystack` is 143 * `true` 144 */ 145 struct HybridHashMap(K, V = void, 146 alias hasher = hashOf, 147 string keyEqualPred = defaultKeyEqualPredOf!(K), 148 Allocator = Mallocator, 149 BorrowCheckFlag borrowCheckFlag = BorrowCheckFlag.no, 150 uint linearSearchMaxSize = 64, // one cache-line for now 151 PrimeCapacityFlag primeCapacityFlag = PrimeCapacityFlag.no) 152 if (isNullable!K /*&& !hasAliasing!K */ && isAllocator!Allocator) 153 { 154 // pragma(msg, K.stringof, " => ", V.stringof); 155 import core.exception : onOutOfMemoryError; 156 import core.internal.traits : hasElaborateDestructor, Unqual; 157 import core.lifetime : move; 158 import std.traits : hasIndirections, hasFunctionAttributes; 159 import std.typecons : Nullable; 160 161 import nxt.nullable_traits : defaultNullKeyConstantOf, isNull, nullify; 162 import nxt.container.traits : mustAddGCRange; 163 import nxt.qcmeman : gc_addRange, gc_removeRange; 164 165 enum usePrimeCapacity = primeCapacityFlag == PrimeCapacityFlag.yes; 166 static if (usePrimeCapacity) 167 import nxt.prime_modulo : PrimeIndex, ceilingPrime, moduloPrimeIndex; 168 else 169 { 170 static if (__VERSION__ >= 2098) import std.math.algebraic : nextPow2; else import std.math : nextPow2; 171 import nxt.probing : triangularProbeFromIndex, triangularProbeFromIndexIncludingHoles, 172 triangularProbeCountFromIndex; 173 /// Setting this `true` doesn't give measurable speedups so set it to `false` for now. 174 enum bool assumeNonFullHaystack = false; 175 } 176 177 static if (is(typeof(keyEqualPred) : string)) 178 { 179 import std.functional : binaryFun; 180 alias keyEqualPredFn = binaryFun!keyEqualPred; 181 } 182 else 183 alias keyEqualPredFn = keyEqualPred; 184 185 /// Is true iff `T` is an array (slice). 186 private enum isSlice(T) = is(T : const(E)[], E); 187 /// Is true iff `T` is a pointer. 188 private enum isPointer(T) = is(T == U*, U); 189 190 static if ((is(K == class)) && 191 keyEqualPred == `a is b`) // TODO: use better predicate compare? 192 alias StoreK = void*; 193 else static if (isPointer!K && 194 // TODO: use better predicate compare? 195 (keyEqualPred == `a == b` || 196 keyEqualPred == `a is b`)) 197 alias StoreK = void*; 198 else 199 alias StoreK = K; 200 201 /// Is `true` iff `this` is borrow-checked. 202 enum isBorrowChecked = borrowCheckFlag == BorrowCheckFlag.yes; 203 204 /** In the hash map case, `V` is non-void, and a value is stored alongside 205 * the key of type `K`. 206 */ 207 enum hasValue = !is(V == void); 208 209 /** Is `true` iff `K` is an address, in which case holes/tombstones are represented by 210 * a specific value `holeKeyConstant`. 211 */ 212 enum hasAddressLikeKey = (isAddress!K || isSlice!K); 213 214 static if (hasAddressLikeKey) 215 { 216 enum hasHoleableKey = true; 217 enum holeKeyOffset = 0x1; // TODO: is this a good value? Or is 0xffff_ffff_ffff_ffff better? 218 @trusted enum holeKeyAddress = cast(void*)holeKeyOffset; 219 220 /** 221 * See_Also: https://forum.dlang.org/post/p7726n$2apd$1@digitalmars.com 222 * TODO: test if ulong.max gives better performance 223 */ 224 static K holeKeyConstant() @trusted pure nothrow @nogc 225 { 226 version(D_Coverage) {} else pragma(inline, true); 227 // TODO: note that cast(size_t*) will give address 0x8 instead of 0x1 228 static if (isSlice!K) 229 { 230 alias E = typeof(K.init[0])*; // array element type 231 auto ptr = cast(E)((cast(void*)null) + holeKeyOffset); // indicates a lazily deleted key 232 return ptr[0 .. 0]; 233 } 234 else 235 return cast(K)((cast(void*)null) + holeKeyOffset); // indicates a lazily deleted key 236 } 237 238 /** Returns: true iff `key` is a hole/tombstone key constant. */ 239 static bool isHoleKeyConstant(const scope K key) @trusted pure nothrow @nogc 240 { 241 version(D_Coverage) {} else pragma(inline, true); 242 static if (isSlice!K) // for slices 243 // suffice to compare pointer part 244 return (key.ptr is holeKeyAddress); 245 else 246 return (cast(const(void)*)key is holeKeyAddress); 247 } 248 249 /** TODO: make these work 250 */ 251 // enum K holeKey_1 = cast(K)((cast(size_t*)null)); 252 // static immutable K holeKey_2 = cast(K)((cast(size_t*)null)); 253 } 254 else static if (isHoleable!K) 255 { 256 enum hasHoleableKey = true; 257 pragma(inline, true) 258 static K holeKeyConstant() @safe pure nothrow @nogc 259 => K.holeValue; 260 static bool isHoleKeyConstant(const scope K key) @safe pure nothrow @nogc 261 { 262 version(D_Coverage) {} else pragma(inline, true); 263 static if (__traits(hasMember, K, "isHole")) 264 // typically faster by asserting value of member of aggregate `K` 265 return key.isHole; 266 else 267 return key is K.holeValue; 268 } 269 } 270 else static if (__traits(hasMember, K, "nullifier")) 271 { 272 alias Nullifier = typeof(K.init.nullifier); 273 // TODO: pragma(msg, K, " has nullifier ", Nullifier); 274 static if (isHoleable!Nullifier) 275 { 276 // TODO: pragma(msg, K, " has holeable nullifier ", Nullifier); 277 enum hasHoleableKey = true; 278 static K holeKeyConstant() @trusted pure nothrow @nogc 279 { 280 K k; 281 k.nullifier = Nullifier.holeValue; 282 return k; 283 } 284 static bool isHoleKeyConstant(const scope K key) @trusted pure nothrow @nogc 285 => key.nullfier == Nullifier.holeValue; 286 } 287 else 288 { 289 enum hasHoleableKey = false; 290 // pragma(msg, "Need explicit hole/tombstone bitset for non-address-like key: ", K); 291 import core.bitop : bts, bt, btr; 292 import nxt.array_help : makeUninitializedBitArray, makeZeroedBitArray, makeReallocatedBitArrayZeroPadded; 293 } 294 } 295 else 296 { 297 enum hasHoleableKey = false; 298 // pragma(msg, "Need explicit hole/tombstone bitset for non-address-like key: ", K); 299 import core.bitop : bts, bt, btr; 300 import nxt.array_help : makeUninitializedBitArray, makeZeroedBitArray, makeReallocatedBitArrayZeroPadded; 301 } 302 303 /// Element type. 304 static if (hasValue) 305 { 306 /** Map insertion status. 307 */ 308 enum InsertionStatus 309 { 310 added, ///< Element was added. 311 modified, ///< Value of element was changed (map only). 312 unmodified ///< Element was left unchanged. 313 } 314 315 /// Mutable element reference with mutable constant key and value. 316 struct T 317 { 318 K key; 319 V value; 320 } 321 322 /// Get key part of element. 323 pragma(inline, true) static inout(K) keyOf(SomeElement)( scope inout(SomeElement) element) => element.key; 324 pragma(inline, true) static ref inout(K) keyOf(SomeElement)(ref scope return inout(SomeElement) element) => element.key; 325 326 /// Get value part of element. 327 pragma(inline, true) static inout(V) valueOf()( scope inout(T) element) => element.value; 328 pragma(inline, true) static ref inout(V) valueOf()(ref scope return inout(T) element) => element.value; 329 330 /** Type of key stored. */ 331 public alias KeyType = K; 332 333 /** Type of value stored. */ 334 public alias ValueType = V; 335 336 enum nullKeyElement = T(defaultNullKeyConstantOf!K, V.init); 337 338 /// Key-value element reference with head-const for `class` keys and mutable value. 339 static private struct KeyValueType 340 { 341 static if (isAddress!K) // for reference types 342 { 343 K _key; // no const because 344 /** Key access is head-const. */ 345 inout(K) key() @property inout @safe pure nothrow @nogc => _key; 346 } 347 else 348 const K key; 349 V value; 350 } 351 352 /// Get key part. 353 pragma(inline, true) 354 static auto ref inout(K) keyOf()(auto ref return scope inout(KeyValueType) element) @trusted 355 => cast(typeof(return))element.key; // needed for case: `inout(const(K)) => inout(K)` 356 } 357 else // HashSet 358 { 359 /** Set insertion status. 360 */ 361 enum InsertionStatus 362 { 363 added, ///< Element was added. 364 unmodified ///< Element was left unchanged. 365 } 366 367 alias T = K; // short name for element type 368 369 /// Get key part of element. 370 pragma(inline, true) 371 static auto ref inout(SomeElement) keyOf(SomeElement)(auto ref return inout(SomeElement) element) 372 => element; 373 374 enum nullKeyElement = defaultNullKeyConstantOf!K; 375 } 376 377 /** TODO remove when https://github.com/dlang/dmd/pull/11000 has been merged 378 * 379 * @kinke: "The builtin `__argTypes` is (currently) only used/populated for Posix x64 380 * (and AArch64 for LDC), and not used by GDC at all AFAIK". 381 * 382 * See_Also: https://github.com/dlang/dmd/pull/11000#issuecomment-671103778 383 */ 384 enum passElementByValue = (__traits(isCopyable, T) && // TODO: use isPOD instead? 385 is(T U == __argTypes) && 386 U.length >= 1); 387 388 /** Is `true` if an instance of `SomeKey` that can be implictly cast to `K`. 389 * 390 * For instance `const(char)[]` can be `@trusted`ly cast to `string` in a 391 * temporary scope. 392 */ 393 template isScopedKeyType(SomeKey) 394 { 395 static if (is(SomeKey == class)) 396 enum isScopedKeyType = (is(const(SomeKey) : const(K))); 397 else 398 enum isScopedKeyType = (is(K : SomeKey) || // `K is` implicitly convertible from `SomeKey` 399 is(SomeKey : U[], U) && // is array 400 is(typeof(K(SomeKey.init)))); 401 } 402 403 alias ElementType = T; 404 405 /** Make with room for storing at least `minimumCapacity` number of elements. 406 * 407 * See_Also: 408 * https://forum.dlang.org/post/nyngzsaeqxzzuumivtze@forum.dlang.org 409 */ 410 static typeof(this) withCapacity()(in size_t minimumCapacity) /* template-lazy */ 411 { 412 static if (usePrimeCapacity) 413 { 414 PrimeIndex primeIndex; 415 immutable initialCapacity = minimumCapacity == 0 ? 1 : ceilingPrime(minimumCapacity + 1, primeIndex); 416 assert(minimumCapacity < initialCapacity); // need at least one vacancy 417 return typeof(return)(makeDefaultInitializedStoreOfCapacity(initialCapacity), primeIndex, 0); 418 } 419 else 420 { 421 immutable initialCapacity = minimumCapacity == 0 ? 1 : nextPow2(minimumCapacity); 422 assert(minimumCapacity < initialCapacity); // need at least one vacancy 423 return typeof(return)(makeDefaultInitializedStoreOfCapacity(initialCapacity), 0); 424 } 425 } 426 427 /** Make default-initialized store with `capacity` number of slots. 428 */ 429 static private T[] makeDefaultInitializedStoreOfCapacity()(in size_t capacity) @trusted pure nothrow @nogc /* template-lazy */ 430 { 431 static if (usePrimeCapacity) 432 { 433 // TODO: check that capacity is prime? 434 } 435 else 436 { 437 debug import std.math : isPowerOf2; 438 debug assert(isPowerOf2(capacity)); // quadratic probing needs power of two capacity (`_store.length`) 439 } 440 441 // TODO: cannot use makeArray here because it cannot handle uncopyable types 442 // import std.experimental.allocator : makeArray; 443 // auto store = allocator.makeArray!T(capacity, nullKeyElement); 444 445 import nxt.bit_traits : isAllZeroBits; 446 447 immutable byteCount = T.sizeof*capacity; 448 449 static if (hasAddressLikeKey || 450 (__traits(isZeroInit, K) && 451 __traits(hasMember, K, "nullifier")) || 452 // TODO: add check for __traits(isZeroInit, K) and member `K.nullValue` == `K.init` 453 (__traits(hasMember, K, `nullValue`) && // if key has a null value 454 __traits(compiles, { enum _ = isAllZeroBits!(K, K.nullValue); }) && // prevent strange error given when `K` is `knet.data.Data` 455 isAllZeroBits!(K, K.nullValue))) // check that it's zero bits only 456 { 457 // pragma(msg, "zero-allocate:", "K:", K, " V:", V); 458 // TODO: use std.experimental.allocator.makeArray instead of this which handles clever checking for isZeroInit 459 import nxt.container.traits : makeInitZeroArray; 460 auto store = makeInitZeroArray!(T, allocator)(capacity); 461 if (store.ptr is null && 462 capacity >= 1) 463 onOutOfMemoryError(); 464 } 465 else // when default null key is not represented by zeros 466 { 467 // pragma(msg, "emplace:", "K:", K, " V:", V); 468 auto store = cast(T[])allocator.allocate(byteCount); 469 if (store.ptr is null && 470 byteCount >= 1) 471 onOutOfMemoryError(); 472 foreach (ref bin; store) 473 { 474 import core.lifetime : emplace; 475 enum hasNullValueKey = __traits(hasMember, K, `nullValue`); 476 static if (hasNullValueKey && 477 !is(typeof(emplace(&keyOf(bin), K.nullValue)))) // __traits(compiles) fails here when building knet 478 pragma(msg, __FILE__, ":", __LINE__, ":warning: emplace fails for null-Value key type ", K); 479 480 // initialize key 481 static if (hasNullValueKey && 482 is(typeof(emplace(&keyOf(bin), K.nullValue)))) 483 emplace(&keyOf(bin), K.nullValue); // initialize in-place with explicit `K.nullValue` 484 else 485 { 486 emplace(&keyOf(bin)); // initialize in-place with default value 487 keyOf(bin).nullify(); // moveEmplace doesn't init source of type `Nullable` 488 } 489 490 // initialize value 491 static if (hasValue) 492 { 493 static if (hasElaborateDestructor!V) 494 emplace(&valueOf(bin)); // initialize in-place 495 else static if (mustAddGCRange!V) 496 valueOf(bin) = V.init; 497 else {} // ok for this case to have uninitialized value part 498 } 499 } 500 } 501 502 static if (mustAddGCRange!T) 503 gc_addRange(store.ptr, byteCount); 504 505 return store; 506 } 507 508 static private T[] allocateUninitializedStore()(in size_t capacity) @trusted pure nothrow @nogc /* template-lazy */ 509 { 510 immutable byteCount = T.sizeof*capacity; 511 auto store = cast(typeof(return))allocator.allocate(byteCount); 512 static if (mustAddGCRange!T) 513 gc_addRange(store.ptr, byteCount); 514 if (store.ptr is null && 515 byteCount >= 1) 516 onOutOfMemoryError(); 517 return store; 518 } 519 520 import std.range.primitives : StdElementType = ElementType; 521 import std.traits : isIterable, isAssignable; 522 523 /** Make with the element `element`. */ 524 this(T element) 525 { 526 static if (usePrimeCapacity) 527 { 528 _primeIndex = PrimeIndex.init; 529 _store = makeDefaultInitializedStoreOfCapacity(ceilingPrime(1 + 1, _primeIndex)); 530 } 531 else 532 _store = makeDefaultInitializedStoreOfCapacity(nextPow2(1)); 533 _count = 0; 534 static if (__traits(isPOD, T)) 535 insertWithoutGrowthNoStatus(element); 536 else 537 insertWithoutGrowthNoStatus(move(element)); 538 } 539 540 static if (hasHoleableKey) 541 { 542 private this(T[] store, in size_t count) 543 { 544 _store = store; 545 _count = count; 546 } 547 } 548 else 549 { 550 private this(T[] store, in size_t count, size_t* holesPtr = null) 551 { 552 _store = store; 553 _count = count; 554 _holesPtr = holesPtr; 555 } 556 } 557 558 /** Make with the elements `elements`. */ 559 static typeof(this) withElements(R)(R elements) 560 if (isIterable!R && 561 isAssignable!(T, StdElementType!R)) 562 { 563 import std.range.primitives : hasLength; 564 static if (hasLength!R) 565 { 566 typeof(this) that = withCapacity(elements.length); 567 foreach (ref element; elements) 568 that.insertWithoutGrowthNoStatus(element); 569 } 570 else 571 { 572 typeof(this) that; 573 foreach (ref element; elements) 574 that.insert(element); 575 } 576 return that; 577 } 578 579 /// Destruct. 580 ~this() nothrow @nogc 581 { 582 release(); 583 } 584 585 /// No copying. 586 @disable this(this); 587 588 /// Returns: a shallow duplicate of `this`. 589 typeof(this) dup()() const @trusted /* template-lazy */ 590 { 591 T[] storeCopy = allocateUninitializedStore(_store.length); // unsafe 592 593 foreach (immutable i, ref bin; _store) 594 if (isOccupiedAtIndex(i)) // normal case 595 { 596 static if (hasValue) // map 597 { 598 duplicateEmplace(bin.key, storeCopy[i].key); 599 duplicateEmplace(bin.value, storeCopy[i].value); 600 } 601 else // set 602 duplicateEmplace(bin, storeCopy[i]); 603 } 604 else 605 { 606 import core.lifetime : emplace; 607 emplace(&storeCopy[i]); // TODO: only emplace key and not value 608 keyOf(storeCopy[i]).nullify(); 609 } 610 611 static if (!hasHoleableKey) 612 if (_holesPtr) 613 { 614 immutable wordCount = holesWordCount(_store.length); 615 auto holesPtrCopy = makeUninitializedBitArray!Allocator(_store.length); 616 holesPtrCopy[0 .. wordCount] = _holesPtr[0 .. wordCount]; // TODO: use memcpy instead? 617 618 return typeof(return)(storeCopy, _count, holesPtrCopy); 619 } 620 621 return typeof(return)(storeCopy, _count); 622 } 623 624 /// Equality. 625 bool opEquals()(const scope auto ref typeof(this) rhs) const 626 { 627 if (_count != rhs._count) 628 return false; // quick discardal 629 630 foreach (immutable i, const ref bin; _store) 631 if (isOccupiedAtIndex(i)) 632 { 633 static if (hasValue) 634 { 635 auto valuePtr = bin.key in rhs; 636 if (!valuePtr) 637 return false; 638 // TODO: make != a parameter that can also be typically !is. TODO: ask forum about this 639 if ((*valuePtr) != bin.value) 640 return false; 641 } 642 else 643 if (!rhs.contains(bin)) 644 return false; 645 } 646 647 return true; 648 } 649 650 static if (true) 651 { 652 private: 653 654 static if (!hasHoleableKey) 655 { 656 /// Deallocate holes/tombstones. 657 void deallocateHoles() @trusted 658 { 659 if (_holesPtr) 660 { 661 static if (__traits(hasMember, Allocator, "deallocatePtr")) 662 allocator.deallocatePtr(_holesPtr); 663 else 664 allocator.deallocate(_holesPtr[0 .. holesWordCount(_store.length)]); 665 } 666 } 667 668 /// Clear holes/tombstones. 669 static size_t* reallocateHoles(size_t[] holes, in size_t byteCount) @trusted 670 { 671 auto rawHoles = cast(void[])holes; 672 immutable ok = allocator.reallocate(rawHoles, byteCount); 673 assert(ok, "couldn't reallocate holes"); 674 return cast(typeof(return))rawHoles.ptr; 675 } 676 677 /// Clear holes/tombstones. 678 void clearHoles() 679 { 680 deallocateHoles(); 681 _holesPtr = null; 682 } 683 684 /// Number of bytes in a word. 685 enum wordBytes = size_t.sizeof; 686 687 /// Number of bits in a word. 688 enum wordBits = 8*wordBytes; 689 690 /// Returns: number of words (`size_t`) needed to represent `binCount` holes. 691 pragma(inline, true) 692 static size_t holesWordCount(in size_t binCount) 693 => (binCount / wordBits + 694 (binCount % wordBits ? 1 : 0)); 695 696 pragma(inline, true) 697 static size_t binBlockBytes(in size_t binCount) => wordBytes*holesWordCount(binCount); 698 699 /// Untag hole/tombstone at index `index`. 700 void untagHoleAtIndex(in size_t index) @trusted 701 { 702 pragma(inline, true); 703 version(unittest) assert(index < _store.length); 704 if (_holesPtr !is null) 705 btr(_holesPtr, index); 706 } 707 708 pragma(inline, true) 709 static bool hasHoleAtPtrIndex(const scope size_t* holesPtr, in size_t index) @trusted 710 => (holesPtr && 711 bt(holesPtr, index) != 0); 712 } 713 714 /// Tag hole/tombstone at index `index`. 715 void tagHoleAtIndex(in size_t index) @trusted 716 { 717 pragma(inline, true); 718 version(unittest) assert(index < _store.length); 719 static if (hasHoleableKey) 720 keyOf(_store[index]) = holeKeyConstant; 721 else 722 { 723 if (_holesPtr is null) // lazy allocation 724 _holesPtr = makeZeroedBitArray!Allocator(_store.length); 725 bts(_holesPtr, index); 726 } 727 } 728 } 729 730 static if (isBorrowChecked) 731 static immutable borrowedErrorMessage = "cannot mutate this when it's borrowed"; 732 733 /// Empty. 734 void clear() 735 { 736 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 737 release(); 738 // TODO: functionize?: 739 _store = typeof(_store).init; 740 static if (usePrimeCapacity) 741 _primeIndex = 0; 742 static if (!hasHoleableKey) 743 _holesPtr = null; 744 _count = 0; 745 } 746 747 /// Release internal allocations. 748 private void release() scope 749 { 750 releaseBinElements(); 751 releaseStoreAndHolesSlices(); 752 } 753 754 /// Release bin elements. 755 private void releaseBinElements() scope 756 { 757 foreach (ref bin; _store) 758 { 759 static if (hasElaborateDestructor!T) 760 .destroy(bin); 761 else static if (mustAddGCRange!T) 762 bin = T.init; 763 } 764 } 765 766 /// Release store slice and holes. 767 private void releaseStoreAndHolesSlices() scope 768 { 769 releaseStoreSlice(_store); 770 static if (!hasHoleableKey) 771 deallocateHoles(); 772 } 773 774 /// Release store slice. 775 static private void releaseStoreSlice(T[] store) @trusted 776 { 777 if (store.ptr is null) { return; } // `gc_removeRange` fails for null input 778 static if (mustAddGCRange!T) 779 gc_removeRange(store.ptr); // `gc_removeRange` fails for null input 780 static if (__traits(hasMember, Allocator, "deallocatePtr")) 781 allocator.deallocatePtr(store.ptr); 782 else 783 allocator.deallocate(store); 784 } 785 786 /// Adjust `key`. 787 private auto adjustKey(SomeKey)(const return scope SomeKey key) const scope @trusted 788 { 789 pragma(inline, true); // must be inlined 790 static if (is(SomeKey : U[], U)) // is array (slice) 791 /* because return value is used only temporarily it's ok to cast to 792 * `immutable` to prevent GC-allocations in types such as 793 * `sso_string.SSOString` */ 794 return cast(immutable(typeof(key[0]))[])key; 795 else 796 return key; 797 } 798 799 /** Check if `element` is stored. 800 * 801 * Parameter `key` may be non-immutable, for instance const(char)[] 802 * eventhough key type `K` is `string`. 803 * 804 * Returns: `true` if element is present, `false` otherwise. 805 */ 806 bool contains(SomeKey)(const scope SomeKey key) const scope @trusted /* `auto ref` here makes things slow */ 807 if (isScopedKeyType!(typeof(key))) 808 in(!key.isNull) 809 { 810 // pragma(msg, SomeKey.stringof ~ " " ~ K.stringof, " ", is(K : SomeKey), " ", is(SomeKey : K)); 811 // debug static assert(isScopedKeyType!(typeof(key)), SomeKey.stringof ~ " " ~ K.stringof); 812 version(LDC) pragma(inline, true); 813 814 static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(const(K))adjustKey(key))); } 815 816 static if (linearSearchMaxSize != 0) 817 if (_store.length * T.sizeof <= linearSearchMaxSize) 818 return containsUsingLinearSearch(key); 819 820 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKey(key)); // cast scoped `key` is @trusted 821 return (hitIndex != _store.length && 822 isOccupiedAtIndex(hitIndex)); 823 } 824 825 /** Check if `element` is stored. 826 * 827 * Uses linear search instead of hashing plus probing and may be faster for 828 * for small tables with complicated hash functions. 829 * 830 * Parameter `key` may be non-immutable, for instance const(char)[] 831 * eventhough key type `K` is `string`. 832 * 833 * Returns: `true` if element is present, `false` otherwise. 834 */ 835 bool containsUsingLinearSearch(SomeKey)(const scope SomeKey key) const scope @trusted /* template-lazy, `auto ref` here makes things slow */ 836 if (isScopedKeyType!(typeof(key))) 837 in(!key.isNull) 838 { 839 static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(const(K))adjustKey(key))); } 840 static if (is(SomeKey == Nullable!(_), _)) 841 { 842 import std.algorithm.searching : canFind; 843 import std.traits : TemplateArgsOf; 844 alias args = TemplateArgsOf!(SomeKey); 845 debug static assert(args.length == 2, 846 "linear search for Nullable without nullValue is slower than default `this.contains()` and is not allowed"); 847 alias UnderlyingType = args[0]; 848 return length >= 1 && (cast(UnderlyingType[])_store).canFind!keyEqualPredFn(key.get()); 849 } 850 else 851 { 852 foreach (const ref bin; _store) 853 if (keyEqualPredFn(keyOf(bin), key)) 854 return true; 855 return false; 856 } 857 } 858 859 /** Check if `element` is stored. Move found element to a hole if possible. 860 Returns: `true` if element is present, `false` otherwise. 861 */ 862 bool containsWithHoleMoving()(const scope K key) /* template-lazy, `auto ref` here makes things slow */ 863 in(!key.isNull) 864 { 865 version(LDC) pragma(inline, true); 866 867 static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); } 868 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 869 870 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); 871 // TODO: update holes 872 return (hitIndex != _store.length && 873 isOccupiedAtIndex(hitIndex)); 874 } 875 876 /** Insert `element`, being either a key-value (map-case) or a just a key 877 * (set-case). 878 * 879 * If `element` is a nullable type and it is null an `AssertError` is thrown. 880 */ 881 InsertionStatus insert()(const T element) @trusted /* template-lazy. need `T` to be `const` in `class` case */ 882 in(!keyOf(element).isNull) 883 { 884 version(LDC) pragma(inline, true); 885 886 static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(keyOf(element))); } 887 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 888 889 reserveExtra(1); 890 size_t hitIndex = 0; 891 static if (__traits(isPOD, T)) 892 return insertWithoutGrowth(element, hitIndex); 893 else 894 return insertWithoutGrowth(move(*cast(T*)&element), hitIndex); 895 } 896 897 /** Insert `element`, being either a key-value (map-case) or a just a key 898 * (set-case). 899 * 900 * If `element` is a nullable type and it is null an `AssertError` is thrown. 901 * 902 * Returns: reference to existing element if present, otherwise new `element`. 903 * 904 * Can be used for implementing, for instance, caching of typically strings. 905 */ 906 ref T insertAndReturnElement(SomeElement)(scope SomeElement element) return /* template-lazy */ 907 in(!keyOf(element).isNull) 908 { 909 version(LDC) pragma(inline, true); 910 911 static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(cast(K)adjustKey(keyOf(element)))); } 912 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 913 914 reserveExtra(1); 915 static if (__traits(isPOD, SomeElement)) 916 immutable hitIndex = insertWithoutGrowthNoStatus(element); 917 else 918 immutable hitIndex = insertWithoutGrowthNoStatus(move(element)); 919 return _store[hitIndex]; 920 } 921 922 /** Insert `elements`, all being either a key-value (map-case) or a just a key (set-case). 923 */ 924 void insertN(R)(R elements) @trusted 925 if (isIterable!R && 926 __traits(isCopyable, T)) // TODO: support uncopyable T? 927 { 928 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 929 import std.range.primitives : hasLength; 930 static if (hasLength!R) 931 reserveExtra(elements.length); // might create unused space in `_store` store 932 foreach (ref element; elements) 933 { 934 static if (!hasLength!R) 935 reserveExtra(1); 936 static if (hasIndirections!T) 937 insertWithoutGrowthNoStatus(element); 938 else 939 insertWithoutGrowthNoStatus(*cast(Unqual!T*)&element); 940 } 941 } 942 943 /// Is `true` iff in-place rehashing during growth should be performed. 944 enum bool growInPlaceFlag = false; // TODO: warning `growInPlaceWithCapacity` is buggy 945 946 /// Numerator for grow scale. 947 enum growScaleP = 3; 948 /// Denominator for grow scale. 949 enum growScaleQ = 2; 950 951 /** Reserve rom for `extraCapacity` number of extra buckets. */ 952 void reserveExtra(in size_t extraCapacity) // not template-lazy 953 { 954 version(LDC) pragma(inline, true); 955 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 956 immutable newCapacity = (_count + extraCapacity)*growScaleP/growScaleQ; 957 if (newCapacity > _store.length) 958 growWithNewCapacity(newCapacity); 959 } 960 961 /// Grow (rehash) to make for `newCapacity` number of elements. 962 private void growWithNewCapacity()(in size_t newCapacity) /* template-lazy */ 963 { 964 version(unittest) assert(newCapacity > _store.length); 965 static if (__traits(hasMember, Allocator, "reallocate")) 966 { 967 static if (growInPlaceFlag) 968 growInPlaceWithCapacity(newCapacity); 969 else 970 growStandardWithNewCapacity(newCapacity); 971 } 972 else 973 growStandardWithNewCapacity(newCapacity); 974 } 975 976 /// Grow (rehash) store to make room for `newCapacity` number of elements. 977 private void growStandardWithNewCapacity()(in size_t newCapacity) /* template-lazy */ 978 { 979 version(unittest) assert(newCapacity > _store.length); 980 auto next = typeof(this).withCapacity(newCapacity); 981 foreach (immutable i, ref bin; _store) 982 if (isOccupiedAtIndex(i)) 983 { 984 next.insertMoveWithoutGrowth(bin); // value is zeroed but 985 static if (!hasHoleableKey) 986 keyOf(bin).nullify(); // keyC must zeroed 987 } 988 move(next, this); 989 } 990 991 /// Tag as lazily delete element at index `index`.` 992 private void tagAsLazilyDeletedElementAtIndex(in size_t index) 993 { 994 version(LDC) pragma(inline, true); 995 996 // key 997 static if (linearSearchMaxSize != 0) 998 if (_store.length * T.sizeof <= linearSearchMaxSize) 999 { 1000 keyOf(_store[index]).nullify(); 1001 goto done; 1002 } 1003 1004 static if (hasHoleableKey) 1005 keyOf(_store[index]) = holeKeyConstant; 1006 else 1007 { 1008 keyOf(_store[index]).nullify(); 1009 tagHoleAtIndex(index); 1010 } 1011 1012 done: 1013 1014 // value 1015 static if (hasValue) 1016 { 1017 static if (hasElaborateDestructor!V) // if we should clear all 1018 .destroy(valueOf(_store[index])); 1019 static if (mustAddGCRange!V) // if we should clear all 1020 valueOf(_store[index]) = V.init; // avoid GC mark-phase dereference 1021 } 1022 } 1023 1024 /// Insert `element` at `index`. 1025 private void insertElementAtIndex(SomeElement)(scope SomeElement element, in size_t index) @trusted /* template-lazy */ 1026 { 1027 version(LDC) pragma(inline, true); 1028 static if (isSlice!SomeElement && 1029 !is(typeof(SomeElement.init[0]) == immutable)) 1030 { 1031 /* key is an array of non-`immutable` elements which cannot safely 1032 * be stored because keys must be immutable for hashing to work 1033 * properly, therefore duplicate */ 1034 keyOf(_store[index]) = element.idup; 1035 } 1036 else 1037 { 1038 static if (__traits(isPOD, SomeElement)) 1039 _store[index] = element; 1040 else 1041 { 1042 static if (__traits(isPOD, K)) 1043 keyOf(_store[index]) = keyOf(element); 1044 else 1045 move(keyOf(element), 1046 keyOf(_store[index])); 1047 1048 static if (hasValue) 1049 { 1050 import core.lifetime : moveEmplace; 1051 moveEmplace(valueOf(element), 1052 valueOf(_store[index])); 1053 } 1054 } 1055 } 1056 } 1057 1058 /// Rehash elements in-place. 1059 private void rehashInPlace()() @trusted /* template-lazy */ 1060 { 1061 import core.bitop : bts, bt; 1062 import nxt.array_help : makeZeroedBitArray, wordCountOfBitCount; 1063 1064 size_t* dones = makeZeroedBitArray!Allocator(_store.length); 1065 1066 foreach (immutable doneIndex; 0 .. _store.length) 1067 { 1068 if (bt(dones, doneIndex)) { continue; } // if _store[doneIndex] continue 1069 if (isOccupiedAtIndex(doneIndex)) 1070 { 1071 import core.lifetime : moveEmplace; 1072 T currentElement = void; 1073 1074 // TODO: functionize: 1075 moveEmplace(_store[doneIndex], currentElement); 1076 static if (is(K == Nullable!(_), _)) 1077 keyOf(_store[doneIndex]).nullify(); // `moveEmplace` doesn't init source of type Nullable 1078 1079 while (true) 1080 { 1081 // TODO remove param `element` 1082 static if (passElementByValue) 1083 alias pred = (immutable scope index, 1084 const scope element) => (!isOccupiedAtIndex(index) || // free slot 1085 !bt(dones, index)); // or a not yet replaced element 1086 else 1087 alias pred = (immutable scope index, 1088 const scope ref element) => (!isOccupiedAtIndex(index) || // free slot 1089 !bt(dones, index)); // or a not yet replaced element 1090 static if (usePrimeCapacity) 1091 immutable hitIndex = xxx; 1092 else 1093 immutable hitIndex = _store[].triangularProbeFromIndex!(pred, assumeNonFullHaystack)(keyToIndex(keyOf(currentElement))); 1094 assert(hitIndex != _store.length, "no free slot"); 1095 1096 bts(dones, hitIndex); // _store[hitIndex] will be at it's correct position 1097 1098 if (isOccupiedAtIndex(doneIndex)) 1099 { 1100 T nextElement = void; 1101 1102 // TODO: functionize: 1103 moveEmplace(_store[hitIndex], nextElement); // save non-free slot 1104 static if (is(K == Nullable!(_), _)) 1105 keyOf(_store[hitIndex]).nullify(); // `moveEmplace` doesn't init source of type Nullable 1106 1107 moveEmplace(currentElement, _store[hitIndex]); 1108 moveEmplace(nextElement, currentElement); 1109 } 1110 else // if no free slot 1111 { 1112 moveEmplace(currentElement, _store[hitIndex]); 1113 break; // inner iteration is finished 1114 } 1115 } 1116 } 1117 bts(dones, doneIndex); // _store[doneIndex] is at it's correct position 1118 } 1119 1120 allocator.deallocate(cast(void[])(dones[0 .. wordCountOfBitCount(_store.length)])); 1121 1122 static if (!hasHoleableKey) 1123 clearHoles(); 1124 } 1125 1126 /** Grow (with rehash) store in-place making room for `minimumCapacity` number of elements. */ 1127 private void growInPlaceWithCapacity()(in size_t minimumCapacity) @trusted /* template-lazy */ 1128 { 1129 assert(minimumCapacity > _store.length); 1130 1131 static if (usePrimeCapacity) 1132 immutable newCapacity = ceilingPrime(minimumCapacity, _primeIndex); 1133 else 1134 immutable newCapacity = nextPow2(minimumCapacity); 1135 1136 immutable newByteCount = T.sizeof*newCapacity; 1137 1138 const oldStorePtr = _store.ptr; 1139 immutable oldLength = _store.length; 1140 1141 auto rawStore = cast(void[])_store; 1142 if (allocator.reallocate(rawStore, newByteCount)) 1143 { 1144 _store = cast(T[])rawStore; 1145 static if (mustAddGCRange!T) 1146 { 1147 if (oldStorePtr !is null) 1148 gc_removeRange(oldStorePtr); // `gc_removeRange` fails for null input 1149 gc_addRange(_store.ptr, newByteCount); 1150 } 1151 1152 static if (!hasHoleableKey) 1153 if (_holesPtr) 1154 _holesPtr = makeReallocatedBitArrayZeroPadded!Allocator(_holesPtr, 1155 oldLength, 1156 _store.length); 1157 1158 // TODO: make this an array operation `nullifyAll` or `nullifyN` 1159 foreach (ref bin; _store[oldLength .. newCapacity]) 1160 keyOf(bin).nullify(); // move this `init` to reallocate() above? 1161 1162 rehashInPlace(); 1163 } 1164 else 1165 assert(0, "couldn't reallocate bin"); 1166 } 1167 1168 /** Insert (without growth) `element` at `hitIndex`. */ 1169 private InsertionStatus insertWithoutGrowth(SomeElement)(const scope SomeElement element, /* template-lazy */ 1170 out size_t hitIndex) @trusted 1171 { 1172 version(LDC) pragma(inline, true); 1173 version(unittest) 1174 { 1175 assert(!keyOf(element).isNull); 1176 static if (hasHoleableKey) { assert(!isHoleKeyConstant(adjustKey(keyOf(element)))); } 1177 } 1178 1179 size_t holeIndex = size_t.max; // first hole index to written to if hole found 1180 immutable hitIndexPrel = indexOfKeyOrVacancyAndFirstHole(keyOf(element), holeIndex); 1181 if (hitIndexPrel == _store.length || // keys miss and holes may have filled all empty slots 1182 keyOf(_store[hitIndexPrel]).isNull) // just key miss but a hole may have been found on the way 1183 { 1184 immutable hasHole = holeIndex != size_t.max; // hole was found along the way 1185 1186 if (hasHole) 1187 hitIndex = holeIndex; // pick hole instead 1188 else 1189 hitIndex = hitIndexPrel; // normal hit 1190 1191 version(unittest) assert(hitIndex != _store.length, "no null or hole slot"); 1192 1193 static if (__traits(isPOD, SomeElement)) 1194 insertElementAtIndex(*cast(SomeElement*)&element, hitIndex); 1195 else 1196 insertElementAtIndex(move(*cast(SomeElement*)&element), hitIndex); 1197 1198 static if (!hasHoleableKey) 1199 if (hasHole) 1200 untagHoleAtIndex(hitIndex); 1201 1202 _count = _count + 1; 1203 return InsertionStatus.added; 1204 } 1205 else 1206 hitIndex = hitIndexPrel; 1207 1208 static if (hasValue) 1209 { 1210 static if (__traits(isStaticArray, V)) 1211 // identity comparison of static arrays implicitly coerces them 1212 // to slices, which are compared by reference, so don't use !is here 1213 immutable valueDiffers = (valueOf(element) != 1214 valueOf(_store[hitIndexPrel])); // only value changed 1215 else 1216 immutable valueDiffers = (valueOf(element) !is 1217 valueOf(_store[hitIndexPrel])); // only value changed 1218 1219 if (valueDiffers) // only value changed 1220 { 1221 move(valueOf(*cast(SomeElement*)&element), 1222 valueOf(_store[hitIndexPrel])); // value is defined so overwrite it 1223 return InsertionStatus.modified; 1224 } 1225 } 1226 return InsertionStatus.unmodified; 1227 } 1228 1229 /** Insert (without growth) `element` and return index to bin where insertion happended. */ 1230 private size_t insertWithoutGrowthNoStatus(SomeElement)(const scope SomeElement element) @trusted /* template-lazy */ 1231 { 1232 version(LDC) pragma(inline, true); 1233 version(unittest) 1234 { 1235 assert(!keyOf(element).isNull); 1236 static if (hasHoleableKey) { assert(!isHoleKeyConstant(adjustKey(keyOf(element)))); } 1237 } 1238 1239 size_t hitIndex = 0; 1240 size_t holeIndex = size_t.max; // first hole index to written to if hole found 1241 immutable hitIndexPrel = indexOfKeyOrVacancyAndFirstHole(adjustKey(keyOf(element)), holeIndex); 1242 if (hitIndexPrel == _store.length || // keys miss and holes may have filled all empty slots 1243 keyOf(_store[hitIndexPrel]).isNull) // just key miss but a hole may have been found on the way 1244 { 1245 immutable hasHole = holeIndex != size_t.max; // hole was found along the way 1246 if (hasHole) 1247 hitIndex = holeIndex; // pick hole instead 1248 else 1249 hitIndex = hitIndexPrel; // normal hit 1250 1251 version(unittest) assert(hitIndex != _store.length, "no null or hole slot"); 1252 1253 static if (__traits(isPOD, SomeElement)) 1254 insertElementAtIndex(*cast(SomeElement*)&element, hitIndex); 1255 else 1256 insertElementAtIndex(move(*cast(SomeElement*)&element), hitIndex); 1257 1258 static if (!hasHoleableKey) 1259 if (hasHole) { untagHoleAtIndex(hitIndex); } 1260 1261 _count = _count + 1; 1262 return hitIndex; 1263 } 1264 else 1265 hitIndex = hitIndexPrel; 1266 1267 static if (hasValue) 1268 // modify existing value 1269 move(valueOf(*cast(SomeElement*)&element), 1270 valueOf(_store[hitIndexPrel])); // value is defined so overwrite it 1271 1272 return hitIndex; 1273 } 1274 1275 /** Insert `element`, being either a key-value (map-case) or a just a key (set-case). 1276 */ 1277 private InsertionStatus insertMoveWithoutGrowth()(ref T element) /* template-lazy */ 1278 { 1279 version(LDC) pragma(inline, true); 1280 size_t hitIndex = 0; 1281 return insertWithoutGrowth(move(element), hitIndex); 1282 } 1283 1284 static if (hasValue) 1285 { 1286 /** Insert or replace `value` at `key`. */ 1287 InsertionStatus insert()(K key, V value) /* template-lazy */ 1288 { 1289 pragma(inline, true); // LDC must have this 1290 static if (__traits(isPOD, K)) 1291 { 1292 static if (__traits(isPOD, V)) 1293 return insert(T(key, value)); 1294 else 1295 return insert(T(key, move(value))); 1296 } 1297 else 1298 { 1299 static if (__traits(isPOD, V)) 1300 return insert(T(move(key), value)); 1301 else 1302 return insert(T(move(key), move(value))); 1303 } 1304 } 1305 } 1306 1307 static if (!hasValue) // HashSet 1308 { 1309 scope const(K)* opBinaryRight(string op, SomeKey)(const scope SomeKey key) const return @trusted 1310 if (op == `in` && 1311 isScopedKeyType!(typeof(key))) 1312 in(!key.isNull) 1313 { 1314 version(D_Coverage) {} else pragma(inline, true); 1315 static if (hasHoleableKey) { assert(!isHoleKeyConstant(cast(K)adjustKey(key))); } 1316 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted 1317 immutable match = hitIndex != _store.length && isOccupiedAtIndex(hitIndex); 1318 if (match) 1319 return &_store[hitIndex]; 1320 else 1321 return null; 1322 } 1323 1324 ref typeof(this) opOpAssign(string op, SomeKey)(const scope SomeKey key) return @trusted 1325 if (op == `~` && // binary assignment operator `~=` 1326 isScopedKeyType!(typeof(key))) 1327 { 1328 version(LDC) pragma(inline, true); 1329 reserveExtra(1); 1330 immutable hitIndex = insertWithoutGrowthNoStatus(key); 1331 return this; 1332 } 1333 1334 /** Try to retrieve `class`-element of type `Class` constructed with 1335 * parameters `params`. 1336 * 1337 * Typically used to implement (polymorphic) caching of class-types 1338 * without the need for GG-allocating a temporary instance of a 1339 * `class`-element potentially already stored in `this` set. 1340 * 1341 * Polymorphic caching can be realized by setting `hasher` to 1342 * `hash_functions.hashOfPolymorphic`. 1343 */ 1344 scope const(Class) tryGetElementFromCtorParams(Class, Params...)(scope Params params) const return @trusted 1345 if (is(Class : K)) 1346 { 1347 import core.lifetime : emplace; 1348 void[__traits(classInstanceSize, Class)] tempNode_ = void; 1349 scope Class temp = emplace!(Class)(tempNode_, params); 1350 Class* hit = cast(Class*)(temp in this); 1351 1352 static if (__traits(hasMember, Class, "__dtor")) 1353 temp.__dtor(); 1354 1355 if (hit) 1356 { 1357 auto typedHit = cast(typeof(return))*hit; 1358 assert(typedHit, "Expected class " ~ Class.stringof ~ " but got hit was of other type"); // TODO: give warning or throw 1359 return typedHit; 1360 } 1361 return null; 1362 } 1363 } 1364 1365 static if (hasValue) // HashMap 1366 { 1367 scope inout(V)* opBinaryRight(string op, SomeKey)(const scope SomeKey key) inout return @trusted // `auto ref` here makes things slow 1368 if (op == `in` && 1369 isScopedKeyType!(SomeKey)) 1370 { 1371 version(LDC) pragma(inline, true); 1372 // pragma(msg, SomeKey, " => ", K); 1373 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKey(key)); // cast scoped `key` is @trusted 1374 immutable match = hitIndex != _store.length && isOccupiedAtIndex(hitIndex); 1375 if (match) 1376 return cast(typeof(return))&_store[hitIndex].value; 1377 else 1378 return null; 1379 } 1380 1381 /// Indexing. 1382 scope ref inout(V) opIndex(SomeKey)(const scope SomeKey key) inout return @trusted // `auto ref` here makes things slow. 1383 if (isScopedKeyType!(typeof(key))) 1384 { 1385 import core.exception : onRangeError; 1386 version(LDC) pragma(inline, true); 1387 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted 1388 immutable match = hitIndex != _store.length && isOccupiedAtIndex(hitIndex); 1389 if (!match) 1390 onRangeError(); 1391 return _store[hitIndex].value; 1392 } 1393 1394 /** Get value of `key` or `defaultValue` if `key` not present (and 1395 * therefore `nothrow`). 1396 * 1397 * Returns: value reference iff `defaultValue` is an l-value. 1398 * 1399 * TODO: make `defaultValue` `lazy` when that can be `nothrow` 1400 */ 1401 auto ref inout(V) get()(const scope K key, /* template-lazy */ 1402 auto ref inout(V) defaultValue) inout 1403 { 1404 version(LDC) pragma(inline, true); 1405 if (auto valuePtr = key in this) 1406 return *valuePtr; 1407 else 1408 return defaultValue; 1409 } 1410 1411 /** Get reference to `key`-part of stored element at `key`, if present, 1412 * otherwise return `defaultKey`. 1413 * 1414 * Used to implement caching inside the key part of a map. 1415 */ 1416 ref const(K) getKeyRef(SomeKey)(const scope SomeKey key, /* template-lazy */ 1417 ref const(K) defaultKey) const return @trusted @nogc 1418 if (isScopedKeyType!(SomeKey)) 1419 { 1420 version(LDC) pragma(inline, true); 1421 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(adjustKey(key)); // cast scoped `key` is @trusted 1422 immutable match = hitIndex != _store.length && isOccupiedAtIndex(hitIndex); 1423 if (match) 1424 return _store[hitIndex].key; 1425 return defaultKey; 1426 } 1427 1428 /** Supports the syntax `aa[key] = value;`. 1429 */ 1430 ref V opIndexAssign()(V value, K key) /* template-lazy. TODO: return scope */ 1431 in(!key.isNull) 1432 { 1433 version(LDC) pragma(inline, true); 1434 1435 static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(key)); } 1436 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1437 1438 reserveExtra(1); 1439 1440 static if (__traits(isPOD, K)) 1441 { 1442 static if (__traits(isPOD, V)) 1443 immutable hitIndex = insertWithoutGrowthNoStatus(T(key, value)); 1444 else 1445 immutable hitIndex = insertWithoutGrowthNoStatus(T(key, move(value))); 1446 } 1447 else 1448 { 1449 static if (__traits(isPOD, V)) 1450 immutable hitIndex = insertWithoutGrowthNoStatus(T(move(key), value)); 1451 else 1452 immutable hitIndex = insertWithoutGrowthNoStatus(T(move(key), move(value))); 1453 } 1454 1455 return _store[hitIndex].value; 1456 } 1457 1458 ref V opIndexOpAssign(string op, Rhs)(Rhs rhs, K key) // TODO: return scope 1459 // if (true) // TODO: pre-check that mixin will work 1460 in(!key.isNull) 1461 { 1462 // pragma(msg, "opIndexOpAssign: Key:", K, " Value:", V, " Rhs:", Rhs, " op:", op); 1463 static if (hasHoleableKey) { debug assert(!isHoleKeyConstant(key)); } 1464 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1465 1466 reserveExtra(1); 1467 1468 size_t holeIndex = size_t.max; // first hole index to written to if hole found 1469 immutable hitIndex = indexOfKeyOrVacancyAndFirstHole(key, holeIndex); 1470 if (hitIndex == _store.length || // keys miss and holes may have filled all empty slots 1471 keyOf(_store[hitIndex]).isNull) // just key miss but a hole may have been found on the way 1472 { 1473 immutable hasHole = holeIndex != size_t.max; // hole was found along the way 1474 immutable index = (hasHole ? 1475 holeIndex : // pick hole instead 1476 hitIndex); // normal hit 1477 version(unittest) assert(index != _store.length, "no null or hole slot"); 1478 static if (__traits(isCopyable, K)) 1479 { 1480 static if (op == "~" || 1481 op == "+" || 1482 op == "*") 1483 { 1484 static if (is(V : Rhs[])) // isDynamicArray of `Rhs` 1485 insertElementAtIndex(T(key, [rhs]), // TODO: if `V(rhs)` is not supported use `V.init` followed by `OP= rhs` 1486 index); 1487 else 1488 // dbg("opIndexOpAssign-new: k:", key, " rhs:", rhs); 1489 insertElementAtIndex(T(key, V(rhs)), // TODO: if `V(rhs)` is not supported use `V.init` followed by `OP= rhs` 1490 index); 1491 } 1492 else 1493 static assert(0, "Handel op " ~ op); 1494 } 1495 else 1496 static assert(0, "Handle uncopyable key " ~ K.stringof); 1497 // insertElementAtIndex(move(*cast(SomeElement*)&element), index); 1498 1499 static if (!hasHoleableKey) 1500 if (hasHole) { untagHoleAtIndex(index); } 1501 1502 _count = _count + 1; 1503 return _store[index].value; 1504 } 1505 else // `key`-hit at index `hitIndex` 1506 { 1507 // dbg("opIndexOpAssign-mod: k:", key, " rhs:", rhs); 1508 mixin(`return _store[hitIndex].value ` ~ op ~ `= rhs;`); // modify existing value 1509 } 1510 } 1511 } 1512 1513 /** Remove `element`. 1514 * Returns: `true` if element was removed, `false` otherwise. 1515 */ 1516 bool remove(SomeKey)(const scope SomeKey key) scope /* template-lazy */ 1517 if (isScopedKeyType!(typeof(key))) 1518 { 1519 version(LDC) pragma(inline, true); 1520 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1521 1522 static if (linearSearchMaxSize != 0) 1523 if (_store.length * T.sizeof <= linearSearchMaxSize) 1524 { 1525 foreach (immutable i, const ref element; _store) // linear search is faster for small arrays 1526 if (keyEqualPredFn(keyOf(element), key)) 1527 { 1528 tagAsLazilyDeletedElementAtIndex(i); 1529 _count = _count - 1; 1530 return true; 1531 } 1532 return false; 1533 } 1534 1535 immutable hitIndex = indexOfKeyOrVacancySkippingHoles(cast(const(K))adjustKey(key)); 1536 immutable match = hitIndex != _store.length && isOccupiedAtIndex(hitIndex); 1537 if (match) 1538 { 1539 tagAsLazilyDeletedElementAtIndex(hitIndex); 1540 _count = _count - 1; 1541 return true; 1542 } 1543 return false; 1544 } 1545 1546 /** Remove all elements matching `keys` followed by a rehash. 1547 * 1548 * Returns: number of elements that were removed. 1549 */ 1550 version(none) 1551 { 1552 import nxt.traits_ex : isRefIterable; 1553 import std.range.primitives : front; 1554 1555 size_t rehashingRemoveN(Keys)(const scope Keys keys) /* template-lazy */ 1556 if (isRefIterable!Keys && 1557 is(typeof(Keys.front == K.init))) 1558 { 1559 static if (isBorrowChecked) { debug assert(!isBorrowed, borrowedErrorMessage); } 1560 rehash!("!a.isNull && keys.canFind(a)")(); // TODO: make this work 1561 return 0; 1562 } 1563 } 1564 1565 /// Check if empty. 1566 bool empty() const @property => _count == 0; 1567 1568 /// Get length (read-only). 1569 @property size_t length() const => _count; 1570 1571 /// Get bin count. 1572 @property size_t binCount() const => _store.length; 1573 1574 /** Returns: get total probe count for all elements stored. */ 1575 size_t totalProbeCount()() const /* template-lazy */ 1576 { 1577 static if (hasValue) 1578 auto range = byKeyValue(this); 1579 else 1580 auto range = byElement(this); 1581 1582 typeof(return) totalCount = 0; 1583 1584 foreach (const ref currentElement; range) 1585 { 1586 static if (__traits(isCopyable, T)) // TODO why does using `passElementByValue` fail as an expression for certain element types? 1587 /* don't use `auto ref` for copyable `T`'s to prevent 1588 * massive performance drop for small elements when compiled 1589 * with LDC. TODO: remove when LDC is fixed. */ 1590 alias pred = (const scope element) => (keyEqualPredFn(keyOf(element), 1591 keyOf(currentElement))); 1592 else 1593 alias pred = (const scope ref element) => (keyEqualPredFn(keyOf(element), 1594 keyOf(currentElement))); 1595 1596 static if (usePrimeCapacity) 1597 immutable probeCount = xxx; 1598 else 1599 immutable probeCount = triangularProbeCountFromIndex!(pred)(_store[], keyToIndex(keyOf(currentElement))); 1600 1601 totalCount += probeCount; 1602 } 1603 return totalCount; 1604 } 1605 1606 /** Returns: average probe count for all elements stored. */ 1607 double averageProbeCount()() const /* template-lazy */ 1608 => (cast(typeof(return))totalProbeCount)/length; 1609 1610 /** Unsafe access to raw store. 1611 * 1612 * Needed by wrapper containers such as `SSOHybridHashSet`. 1613 */ 1614 pragma(inline, true) 1615 inout(T)[] rawStore() inout @system pure nothrow @nogc => _store; 1616 1617 static if (hasHoleableKey) 1618 { 1619 static bool isOccupiedBin(const ref T bin) 1620 => (keyOf(bin).isNull && 1621 !isHoleKeyConstant(keyOf(bin))); 1622 } 1623 1624 private: 1625 import nxt.allocator_traits : AllocatorState; 1626 mixin AllocatorState!Allocator; // put first as emsi-containers do 1627 1628 static if (hasFunctionAttributes!(allocator.allocate, "@nogc")) 1629 { 1630 import nxt.gc_traits : NoGc; 1631 @NoGc T[] _store; // one element per bin 1632 } 1633 else 1634 T[] _store; // one element per bin 1635 1636 static if (usePrimeCapacity) 1637 PrimeIndex _primeIndex = PrimeIndex.init; 1638 1639 static if (!hasHoleableKey) 1640 { 1641 static if (hasFunctionAttributes!(allocator.allocate, "@nogc")) 1642 @NoGc size_t* _holesPtr; // bit array describing which bin elements that has been removed (holes) 1643 else 1644 size_t* _holesPtr; // bit array describing which bin elements that has been removed (holes) 1645 } 1646 1647 static if (isBorrowChecked) 1648 { 1649 debug // use Rust-style borrow checking at run-time 1650 { 1651 size_t _count; 1652 size_t _borrowCount; 1653 1654 /// Number of bits needed to store number of read borrows. 1655 enum borrowCountBits = 8*isBorrowChecked.sizeof; 1656 1657 /// Maximum value possible for `_borrowCount`. 1658 enum borrowCountMax = 2^^borrowCountBits - 1; 1659 1660 version(none) 1661 { 1662 /// Number of bits needed to store number of read borrows. 1663 enum borrowCountBits = 24; 1664 1665 /// Maximum value possible for `_borrowCount`. 1666 enum borrowCountMax = 2^^borrowCountBits - 1; 1667 1668 import std.bitmanip : bitfields; 1669 mixin(bitfields!(size_t, "_count", 8*size_t.sizeof - borrowCountBits, 1670 uint, "_borrowCount", borrowCountBits, 1671 )); 1672 } 1673 1674 pragma(inline, true): 1675 @safe pure nothrow @nogc: 1676 1677 @property 1678 { 1679 /// Returns: `true` iff `this` is borrowed (either read or write). 1680 bool isBorrowed() const => _borrowCount >= 1; 1681 1682 /// Returns: number of borrowers of `this` (both read and write). 1683 auto borrowCount() const => _borrowCount; 1684 } 1685 1686 /// Increase borrow count. 1687 void incBorrowCount() 1688 in(_borrowCount + 1 != borrowCountMax) 1689 { 1690 _borrowCount = _borrowCount + 1; 1691 } 1692 1693 /// Decrease borrow count. 1694 void decBorrowCount() 1695 in(_borrowCount != 0) 1696 { 1697 _borrowCount = _borrowCount - 1; 1698 } 1699 } 1700 else 1701 { 1702 size_t _count; // total number of non-null elements stored in `_store` 1703 } 1704 } 1705 else 1706 { 1707 size_t _count; // total number of non-null elements stored in `_store` 1708 } 1709 1710 /** Returns: bin index of `key`. */ 1711 private size_t keyToIndex(SomeKey)(const scope SomeKey key) const @trusted 1712 { 1713 version(LDC) pragma(inline, true); // TODO: inline always 1714 1715 /** Returns: current index mask from bin count `_store.length`. 1716 * TODO: Inline this and check for speed-up. 1717 */ 1718 static size_t powerOf2Mask(in size_t length) @safe pure nothrow @nogc 1719 { 1720 version(unittest) 1721 { 1722 // TODO: move to in contract: 1723 debug import std.math : isPowerOf2; 1724 debug assert(isPowerOf2(length)); 1725 } 1726 else 1727 { 1728 version(D_Coverage) {} else pragma(inline, true); 1729 } 1730 return length - 1; 1731 } 1732 1733 static if (is(typeof(hasher(key)) == hash_t)) // for instance when hasher being `hashOf` 1734 immutable hash = hasher(key); 1735 else static if (is(hasher == struct) || // such as `FNV` 1736 is(hasher == class)) 1737 { 1738 import nxt.digestion : hashOf2; 1739 immutable hash = hashOf2!(hasher)(key); 1740 } 1741 else 1742 static assert(false, "Unsupported hasher of type " ~ typeof(hasher).stringof); 1743 1744 static if (usePrimeCapacity) 1745 return moduloPrimeIndex(hash, _primeIndex); 1746 else 1747 return hash & powerOf2Mask(_store.length); 1748 } 1749 1750 /** Find index to `key` if it exists or to first empty slot found, skipping 1751 * (ignoring) lazily deleted slots. 1752 */ 1753 private size_t indexOfKeyOrVacancySkippingHoles(const scope K key) const @trusted scope // `auto ref` here makes things slow 1754 // TODO: if (...) 1755 { 1756 version(LDC) pragma(inline, true); 1757 version(unittest) 1758 { 1759 assert(!key.isNull); 1760 static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); } 1761 } 1762 1763 static if (linearSearchMaxSize != 0) 1764 if (_store.length * T.sizeof <= linearSearchMaxSize) 1765 { 1766 foreach (immutable i, const ref element; _store) // linear search is faster for small arrays 1767 if ((keyOf(element).isNull || 1768 keyEqualPredFn(keyOf(element), key))) 1769 return i; 1770 return _store.length; 1771 } 1772 1773 static if (passElementByValue) // Ref: https://github.com/dlang/dmd/pull/11000#issuecomment-671103778 1774 { 1775 static if (hasHoleableKey) 1776 alias pred = (const scope element) => (keyOf(element).isNull || 1777 keyEqualPredFn(keyOf(element), key)); 1778 else 1779 alias pred = (immutable scope index, 1780 const scope element) => (!hasHoleAtPtrIndex(_holesPtr, index) && 1781 (keyOf(element).isNull || 1782 keyEqualPredFn(keyOf(element), key))); 1783 } 1784 else 1785 { 1786 static if (hasHoleableKey) 1787 alias pred = (const scope auto ref element) => (keyOf(element).isNull || 1788 keyEqualPredFn(keyOf(element), key)); 1789 else 1790 alias pred = (immutable scope index, 1791 const scope auto ref element) => (!hasHoleAtPtrIndex(_holesPtr, index) && 1792 (keyOf(element).isNull || 1793 keyEqualPredFn(keyOf(element), key))); 1794 } 1795 1796 static if (usePrimeCapacity) 1797 return xxx; 1798 else 1799 return _store[].triangularProbeFromIndex!(pred, assumeNonFullHaystack)(keyToIndex(key)); 1800 } 1801 1802 private size_t indexOfKeyOrVacancyAndFirstHole(const scope K key, // `auto ref` here makes things slow 1803 ref size_t holeIndex) const @trusted scope 1804 { 1805 version(LDC) pragma(inline, true); 1806 version(unittest) 1807 { 1808 assert(!key.isNull); 1809 static if (hasHoleableKey) { assert(!isHoleKeyConstant(key)); } 1810 } 1811 1812 static if (linearSearchMaxSize != 0) 1813 if (_store.length * T.sizeof <= linearSearchMaxSize) 1814 { 1815 foreach (immutable i, const ref element; _store) // linear search is faster for small arrays 1816 if ((keyOf(element).isNull || 1817 keyEqualPredFn(keyOf(element), key))) 1818 return i; 1819 return _store.length; 1820 } 1821 1822 static if (passElementByValue) // Ref: https://github.com/dlang/dmd/pull/11000#issuecomment-671103778 1823 { 1824 static if (hasHoleableKey) 1825 { 1826 alias hitPred = (const scope element) => (keyOf(element).isNull || 1827 keyEqualPredFn(keyOf(element), key)); 1828 alias holePred = (const scope element) => (isHoleKeyConstant(keyOf(element))); 1829 } 1830 else 1831 { 1832 alias hitPred = (immutable scope index, 1833 const scope element) => (!hasHoleAtPtrIndex(_holesPtr, index) && 1834 (keyOf(element).isNull || 1835 keyEqualPredFn(keyOf(element), key))); 1836 alias holePred = (immutable scope index, // TODO: use only index 1837 const scope element) => (hasHoleAtPtrIndex(_holesPtr, index)); 1838 } 1839 } 1840 else 1841 { 1842 static if (hasHoleableKey) 1843 { 1844 alias hitPred = (const scope auto ref element) => (keyOf(element).isNull || 1845 keyEqualPredFn(keyOf(element), key)); 1846 alias holePred = (const scope auto ref element) => (isHoleKeyConstant(keyOf(element))); 1847 } 1848 else 1849 { 1850 alias hitPred = (immutable scope index, 1851 const scope auto ref element) => (!hasHoleAtPtrIndex(_holesPtr, index) && 1852 (keyOf(element).isNull || 1853 keyEqualPredFn(keyOf(element), key))); 1854 alias holePred = (immutable scope index, // TODO: use only index 1855 const scope auto ref element) => (hasHoleAtPtrIndex(_holesPtr, index)); 1856 } 1857 } 1858 1859 static if (usePrimeCapacity) 1860 return xxx; 1861 else 1862 return _store[].triangularProbeFromIndexIncludingHoles!(hitPred, holePred, assumeNonFullHaystack)(keyToIndex(key), holeIndex); 1863 } 1864 1865 /// Returns: `true` iff `index` indexes a non-null element, `false` otherwise. 1866 private bool isOccupiedAtIndex(in size_t index) const 1867 { 1868 version(LDC) pragma(inline, true); 1869 version(unittest) assert(index < _store.length); 1870 if (keyOf(_store[index]).isNull) { return false; } 1871 static if (hasHoleableKey) 1872 return !isHoleKeyConstant(keyOf(_store[index])); 1873 else 1874 return !hasHoleAtPtrIndex(_holesPtr, index); 1875 } 1876 } 1877 1878 /** Duplicate `src` into uninitialized `dst` ignoring prior destruction of `dst`. 1879 * 1880 * TODO: Move to a more generic place either in phobos-next or Phobos. 1881 */ 1882 static private void duplicateEmplace(T)(const scope ref T src, 1883 scope ref T dst) @system 1884 { 1885 version(D_Coverage) {} else pragma(inline, true); 1886 import core.internal.traits : hasElaborateCopyConstructor; 1887 import std.traits : isBasicType; 1888 static if (!hasElaborateCopyConstructor!T) 1889 { 1890 import std.typecons : Nullable; 1891 static if (is(T == class) || 1892 is(T == string)) 1893 dst = cast(T)src; 1894 else static if (isBasicType!T || is(T == Nullable!(_), _)) // `Nullable` types cannot be emplaced 1895 dst = src; 1896 else // TODO: can this case occur? 1897 { 1898 import core.internal.traits : Unqual; 1899 import core.lifetime : emplace; 1900 emplace(&dst, cast(Unqual!T)src); 1901 } 1902 } 1903 else static if (__traits(hasMember, T, "dup")) 1904 { 1905 import core.lifetime : emplace; 1906 // TODO: when `emplace` can handle src being an r-value of uncopyable types replace with: `emplace(&dst, src.dup);` 1907 emplace(&dst); 1908 dst = src.dup; 1909 } 1910 else 1911 debug static assert(0, "cannot duplicate a " ~ T.stringof); 1912 } 1913 1914 /** L-value element reference (and in turn range iterator). 1915 */ 1916 static private struct LvalueElementRef(SomeMap) 1917 { 1918 import std.traits : isMutable; 1919 debug static assert(isMutable!SomeMap, "SomeMap type must be mutable"); 1920 1921 private SomeMap* _table; // scoped access 1922 private size_t _binIndex; // index to bin inside `table` 1923 private size_t _hitCounter; // counter over number of elements popped (needed for length) 1924 1925 this(SomeMap* table) @trusted 1926 { 1927 version(D_Coverage) {} else pragma(inline, true); 1928 this._table = table; 1929 static if (SomeMap.isBorrowChecked) 1930 debug { _table.incBorrowCount(); } 1931 } 1932 1933 ~this() nothrow @nogc @trusted 1934 { 1935 version(D_Coverage) {} else pragma(inline, true); 1936 static if (SomeMap.isBorrowChecked) 1937 debug { _table.decBorrowCount(); } 1938 } 1939 1940 this(this) @trusted 1941 { 1942 version(D_Coverage) {} else pragma(inline, true); 1943 static if (SomeMap.isBorrowChecked) 1944 debug 1945 { 1946 assert(_table._borrowCount != 0); 1947 _table.incBorrowCount(); 1948 } 1949 } 1950 1951 /// Check if empty. 1952 bool empty() const @property @safe pure nothrow @nogc 1953 { 1954 version(D_Coverage) {} else pragma(inline, true); 1955 return _binIndex == _table.binCount; 1956 } 1957 1958 /// Get number of element left to pop. 1959 @property size_t length() const @safe pure nothrow @nogc 1960 { 1961 version(D_Coverage) {} else pragma(inline, true); 1962 return _table.length - _hitCounter; 1963 } 1964 1965 @property typeof(this) save() // ForwardRange 1966 { 1967 version(D_Coverage) {} else pragma(inline, true); 1968 return this; 1969 } 1970 1971 void popFront() in(!empty) 1972 { 1973 version(LDC) pragma(inline, true); 1974 _binIndex += 1; 1975 findNextNonEmptyBin(); 1976 _hitCounter += 1; 1977 } 1978 1979 private void findNextNonEmptyBin() 1980 { 1981 version(D_Coverage) {} else pragma(inline, true); 1982 while (_binIndex != (*_table).binCount && 1983 !(*_table).isOccupiedAtIndex(_binIndex)) 1984 _binIndex += 1; 1985 } 1986 } 1987 1988 /** R-value element reference (and in turn range iterator). 1989 * 1990 * Does need to do borrow-checking. 1991 */ 1992 static private struct RvalueElementRef(SomeMap) 1993 { 1994 debug import std.traits : isMutable; 1995 debug static assert(isMutable!SomeMap, "SomeMap type must be mutable"); 1996 1997 SomeMap _table; // owned table 1998 size_t _binIndex; // index to bin inside table 1999 size_t _hitCounter; // counter over number of elements popped 2000 2001 /// Check if empty. 2002 bool empty() const @property @safe pure nothrow @nogc 2003 { 2004 version(D_Coverage) {} else pragma(inline, true); 2005 return _binIndex == _table.binCount; 2006 } 2007 2008 /// Get number of element left to pop. 2009 @property size_t length() const @safe pure nothrow @nogc 2010 { 2011 version(D_Coverage) {} else pragma(inline, true); 2012 return _table.length - _hitCounter; 2013 } 2014 2015 void popFront() in(!empty) 2016 { 2017 version(LDC) pragma(inline, true); 2018 _binIndex += 1; 2019 findNextNonEmptyBin(); 2020 _hitCounter += 1; 2021 } 2022 2023 private void findNextNonEmptyBin() 2024 { 2025 version(D_Coverage) {} else pragma(inline, true); 2026 while (_binIndex != _table.binCount && 2027 !_table.isOccupiedAtIndex(_binIndex)) 2028 _binIndex += 1; 2029 } 2030 } 2031 2032 /** Hash set with in-place open-addressing, storing keys (elements) of type `K`. 2033 * 2034 * Reuse `HybridHashMap` with its V-type set to `void`. 2035 * 2036 * See_Also: `HybridHashMap`. 2037 */ 2038 alias HybridHashSet(K, 2039 alias hasher = hashOf, 2040 string keyEqualPred = defaultKeyEqualPredOf!K, 2041 Allocator = Mallocator, 2042 BorrowCheckFlag borrowCheckFlag = BorrowCheckFlag.no, 2043 uint linearSearchMaxSize = 64, // one cache-line for now 2044 PrimeCapacityFlag primeCapacityFlag = PrimeCapacityFlag.no) = 2045 HybridHashMap!(K, void, hasher, keyEqualPred, Allocator, borrowCheckFlag, 2046 linearSearchMaxSize, primeCapacityFlag); 2047 2048 import std.functional : unaryFun; 2049 2050 /** Remove all elements in `x` matching `pred`. 2051 * 2052 * TODO: make this generic for all iterable containers and move to 2053 * container/common.d. 2054 */ 2055 size_t removeAllMatching(alias pred, SomeMap)(auto ref SomeMap x) @trusted 2056 if (is(SomeMap == HybridHashMap!(_), _...) && // TODO: generalize to `isSetOrMap` 2057 is(typeof((unaryFun!pred)))) 2058 { 2059 import nxt.nullable_traits : nullify; 2060 size_t removalCount = 0; 2061 foreach (immutable i, ref bin; x._store) 2062 { 2063 // TODO: 2064 // move to SomeMap.removeRef(bin) // uses: `offset = &bin - _store.ptr` 2065 // move to SomeMap.inplaceRemove(bin) // uses: `offset = &bin - _store.ptr` 2066 // or to SomeMap.removeAtIndex(i) 2067 if (x.isOccupiedAtIndex(i) && 2068 unaryFun!pred(bin)) 2069 { 2070 x.tagAsLazilyDeletedElementAtIndex(i); 2071 removalCount += 1; 2072 } 2073 } 2074 x._count = x._count - removalCount; 2075 return removalCount; // TODO: remove this return value 2076 } 2077 2078 /** Returns: `x` eagerly filtered on `pred`. 2079 * 2080 * TODO: move to container/common.d with more generic template restrictions 2081 */ 2082 SomeMap filtered(alias pred, SomeMap)(SomeMap x) 2083 if (is(SomeMap == HybridHashMap!(_), _...)) // TODO: generalize to `isSetOrMap` 2084 { 2085 import core.lifetime : move; 2086 import std.functional : not; 2087 x.removeAllMatching!(not!pred); // `x` is a singleton (r-value) so safe to mutate 2088 return move(x); // functional 2089 } 2090 2091 /** Returns: `x` eagerly intersected with `y`. 2092 * 2093 * TODO: move to container/common.d. 2094 * TODO: Check that `ElementType`'s of `C1` and `C2` match. Look at std.algorithm.intersection for hints. 2095 */ 2096 auto intersectedWith(C1, C2)(C1 x, auto ref C2 y) 2097 if (is(C1 == HybridHashMap!(_1), _1...) && // TODO: generalize to `isSetOrMap` 2098 is(C2 == HybridHashMap!(_2), _2...)) // TODO: generalize to `isSetOrMap` 2099 { 2100 import core.lifetime : move; 2101 static if (__traits(isRef, y)) // y is l-value 2102 // @("complexity", "O(x.length)") 2103 return move(x).filtered!(_ => y.contains(_)); // only x can be reused 2104 else 2105 { 2106 /* both are r-values so reuse the shortest */ 2107 // @("complexity", "O(min(x.length), min(y.length))") 2108 if (x.length < y.length) 2109 return move(x).filtered!(_ => y.contains(_)); // functional 2110 else 2111 return move(y).filtered!(_ => x.contains(_)); // functional 2112 } 2113 } 2114 2115 /// exercise opEquals 2116 @safe pure nothrow @nogc unittest 2117 { 2118 import std.typecons : Nullable; 2119 import nxt.digestx.fnv : FNV; 2120 2121 alias K = Nullable!(ulong, ulong.max); 2122 alias X = HybridHashSet!(K, FNV!(64, true)); 2123 2124 const n = 100; 2125 2126 X a; 2127 foreach (const i_; 0 .. n) 2128 { 2129 const i = 1113*i_; // insert in order 2130 assert(!a.contains(K(i))); 2131 assert(!a.containsUsingLinearSearch(K(i))); 2132 assert(a.insertAndReturnElement(K(i)) == K(i)); 2133 assert(a.contains(K(i))); 2134 assert(a.containsUsingLinearSearch(K(i))); 2135 } 2136 2137 X b; 2138 foreach (const i_; 0 .. n) 2139 { 2140 const i = 1113*(n - 1 - i_); // insert in reverse 2141 assert(!b.contains(K(i))); 2142 assert(!b.containsUsingLinearSearch(K(i))); 2143 assert(b.insertAndReturnElement(K(i)) == K(i)); 2144 assert(b.contains(K(i))); 2145 assert(b.containsUsingLinearSearch(K(i))); 2146 } 2147 2148 assert(a == b); 2149 2150 // bin storage must be deterministic 2151 () @trusted { assert(a._store != b._store); }(); 2152 } 2153 2154 @safe pure nothrow @nogc unittest 2155 { 2156 import nxt.digestx.fnv : FNV; 2157 2158 enum Pot { noun, verb } 2159 struct ExprPot 2160 { 2161 string expr; 2162 Pot pot; 2163 2164 alias nullifier = expr; 2165 static immutable nullValue = typeof(this).init; 2166 } 2167 2168 alias X = HybridHashSet!(ExprPot, FNV!(64, true)); 2169 2170 X x; 2171 2172 const aa = "aa"; 2173 2174 // two keys with same contents but different place in memory 2175 const key1 = ExprPot(aa[0 .. 1], Pot.noun); 2176 const key2 = ExprPot(aa[1 .. 2], Pot.noun); 2177 2178 assert(key1 == key2); 2179 assert(key1 !is key2); 2180 2181 assert(!x.contains(key1)); 2182 assert(!x.contains(key2)); 2183 x.insert(key1); 2184 assert(x.contains(key1)); 2185 assert(x.containsUsingLinearSearch(key1)); 2186 assert(x.contains(key2)); 2187 /* assert(x.containsUsingLinearSearch(key2)); */ 2188 assert(key1 in x); 2189 assert(key2 in x); 2190 } 2191 2192 /// `string` as key 2193 @safe pure nothrow @nogc unittest 2194 { 2195 import nxt.container.traits : mustAddGCRange; 2196 import nxt.digestx.fnv : FNV; 2197 2198 alias X = HybridHashSet!(string, FNV!(64, true)); 2199 debug static assert(!mustAddGCRange!X); 2200 debug static assert(X.sizeof == 24); // dynamic arrays also `hasAddressLikeKey` 2201 2202 auto x = X(); 2203 2204 auto testEscapeShouldFail()() @safe pure 2205 { 2206 X x; 2207 x.insert("a"); 2208 return x.byElement; 2209 } 2210 2211 auto testEscapeShouldFailFront()() @safe pure 2212 { 2213 X x; 2214 x.insert("a"); 2215 return x.byElement.front; 2216 } 2217 2218 assert(&"a"[0] is &"a"[0]); // string literals are store in common place 2219 2220 const aa = "aa"; 2221 2222 // string slices are equal when elements are equal regardless of position 2223 // (.ptr) in memory 2224 assert(x.insertAndReturnElement(aa[0 .. 1]) !is "a"); 2225 x.insert(aa[0 .. 1]); 2226 assert(x.insertAndReturnElement(aa[0 .. 1]) is aa[0 .. 1]); 2227 assert(x.contains(aa[1 .. 2])); 2228 assert(x.containsUsingLinearSearch(aa[1 .. 2])); 2229 2230 const(char)[] aa_ = "aa"; 2231 assert(x.contains(aa_[1 .. 2])); 2232 assert(x.containsUsingLinearSearch(aa_[1 .. 2])); 2233 assert(aa_[1 .. 2] in x); 2234 2235 char[2] aa__; aa__ = "aa"; 2236 assert(x.contains(aa__[1 .. 2])); 2237 assert(x.containsUsingLinearSearch(aa__[1 .. 2])); 2238 assert(aa__[1 .. 2] in x); 2239 2240 const bb = "bb"; 2241 2242 assert(x.insertAndReturnElement(bb[0 .. 1]) is bb[0 .. 1]); // returns newly added ref 2243 assert(x.insertAndReturnElement(bb[0 .. 1]) !is "b"); // return other ref not equal new literal 2244 x.insert(bb[0 .. 1]); 2245 assert(x.contains(bb[1 .. 2])); 2246 assert(x.containsUsingLinearSearch(bb[1 .. 2])); 2247 2248 x.remove(aa[0 .. 1]); 2249 assert(!x.contains(aa[1 .. 2])); 2250 assert(!x.containsUsingLinearSearch(aa[1 .. 2])); 2251 assert(x.contains(bb[1 .. 2])); 2252 assert(x.containsUsingLinearSearch(bb[1 .. 2])); 2253 2254 x.remove(bb[0 .. 1]); 2255 assert(!x.contains(bb[1 .. 2])); 2256 assert(!x.containsUsingLinearSearch(bb[1 .. 2])); 2257 2258 x.insert("a"); 2259 x.insert("b"); 2260 assert(x.contains("a")); 2261 assert(x.containsUsingLinearSearch("a")); 2262 assert(x.contains("b")); 2263 assert(x.containsUsingLinearSearch("b")); 2264 2265 debug static assert(!__traits(compiles, { testEscapeShouldFail(); } )); 2266 // TODO: this should fail: 2267 // TODO: debug static assert(!__traits(compiles, { testEscapeShouldFailFront(); } )); 2268 } 2269 2270 /// `string` as key 2271 @safe pure nothrow unittest 2272 { 2273 import nxt.digestx.fnv : FNV; 2274 alias X = HybridHashSet!(string, FNV!(64, true)); 2275 auto x = X(); 2276 2277 char[2] cc = "cc"; // mutable chars 2278 assert(x.insertAndReturnElement(cc[]) !is cc[]); // will allocate new slice 2279 2280 const cc_ = "cc"; // immutable chars 2281 assert(x.insertAndReturnElement(cc_[]) !is cc[]); // will not allocate 2282 } 2283 2284 /// array container as value type 2285 @safe pure nothrow @nogc unittest 2286 { 2287 import std.meta : AliasSeq; 2288 import std.typecons : Nullable; 2289 import nxt.container.traits : mustAddGCRange; 2290 import nxt.digestx.fnv : FNV; 2291 import nxt.array_help : s; 2292 2293 alias K = Nullable!(uint, uint.max); 2294 2295 alias VE = Nullable!(uint, uint.max); 2296 alias V = HybridHashSet!(VE, FNV!(64, true)); 2297 2298 debug static assert(!mustAddGCRange!V); 2299 2300 foreach (X; AliasSeq!(HybridHashMap!(K, V, FNV!(64, true)))) 2301 { 2302 const VE n = 600; 2303 2304 auto x = X(); 2305 2306 { // scoped range 2307 auto xkeys = x.byKey; 2308 assert(xkeys.length == 0); 2309 foreach (ref key; xkeys) 2310 { 2311 debug static assert(is(typeof(key) == const(K))); 2312 assert(0); 2313 } 2314 foreach (ref key; X().byKey) 2315 { 2316 debug static assert(is(typeof(key) == const(K))); 2317 assert(0); 2318 } 2319 } 2320 2321 foreach (immutable i; 0 .. n) 2322 { 2323 assert(x.length == i); 2324 2325 auto key = K(i); 2326 auto value = V.withElements([VE(i)].s); 2327 2328 x[key] = value.dup; 2329 assert(x.length == i + 1); 2330 assert(x.contains(key)); 2331 // TODO: assert(x.containsUsingLinearSearch(key)); 2332 { 2333 auto valuePtr = key in x; 2334 assert(valuePtr && *valuePtr == value); 2335 } 2336 2337 x.remove(key); 2338 assert(x.length == i); 2339 assert(!x.contains(key)); 2340 assert(key !in x); 2341 2342 x[key] = value.dup; 2343 assert(x.length == i + 1); 2344 assert(x.contains(key)); 2345 { 2346 auto valuePtr = key in x; 2347 assert(valuePtr && *valuePtr == value); 2348 } 2349 } 2350 2351 assert(x is x); 2352 2353 x = x.dup; 2354 2355 auto y = x.dup; 2356 assert(x !is y); 2357 assert(x.length == y.length); 2358 2359 assert(y == x); 2360 assert(x == y); 2361 2362 foreach (ref key; x.byKey) 2363 { 2364 assert(x.contains(key)); 2365 } 2366 2367 foreach (ref keyValue; x.byKeyValue) 2368 { 2369 assert(x.contains(keyValue.key)); 2370 auto keyValuePtr = keyValue.key in x; 2371 assert(keyValuePtr && 2372 *keyValuePtr == keyValue.value); 2373 } 2374 2375 foreach (immutable i; 0 .. n) 2376 { 2377 assert(x.length == n - i); 2378 2379 auto key = K(i); 2380 auto value = V.withElements([VE(i)].s); 2381 2382 assert(x.contains(key)); 2383 { 2384 auto valuePtr = key in x; 2385 assert(valuePtr && *valuePtr == value); 2386 } 2387 2388 x.remove(key); 2389 assert(!x.contains(key)); 2390 assert(key !in x); 2391 } 2392 2393 auto z = y.dup; 2394 assert(y == z); 2395 2396 /* remove all elements in `y` using `removeAllMatching` and all elements 2397 * in `z` using `removeAllMatching` */ 2398 foreach (immutable i; 0 .. n) 2399 { 2400 assert(y.length == n - i); 2401 assert(z.length == n - i); 2402 2403 auto key = K(i); 2404 auto value = V.withElements([VE(i)].s); 2405 2406 assert(y.contains(key)); 2407 { 2408 auto valuePtr = key in y; 2409 assert(valuePtr && *valuePtr == value); 2410 } 2411 assert(z.contains(key)); 2412 { 2413 auto valuePtr = key in z; 2414 assert(valuePtr && *valuePtr == value); 2415 } 2416 2417 y.remove(key); 2418 assert(z.removeAllMatching!((const scope ref element) => element.key is key) == 1); 2419 assert(y == z); 2420 2421 assert(!y.contains(key)); 2422 assert(!z.contains(key)); 2423 2424 assert(key !in y); 2425 assert(key !in z); 2426 } 2427 } 2428 } 2429 2430 /// r-value and l-value intersection 2431 @safe pure nothrow @nogc unittest 2432 { 2433 import core.lifetime : move; 2434 import std.typecons : Nullable; 2435 import nxt.digestx.fnv : FNV; 2436 import nxt.array_help : s; 2437 2438 alias K = Nullable!(uint, uint.max); 2439 alias X = HybridHashSet!(K, FNV!(64, true)); 2440 2441 auto x = X(); 2442 2443 { // scoped range 2444 foreach (ref _; x.byElement) { assert(0); } 2445 } 2446 2447 auto x0 = X.init; 2448 assert(x0.length == 0); 2449 assert(x0._store.length == 0); 2450 assert(!x0.contains(K(1))); 2451 2452 auto x1 = X.withElements([K(12)].s); 2453 assert(x1.length == 1); 2454 assert(x1.contains(K(12))); 2455 2456 auto x2 = X.withElements([K(10), K(12)].s); 2457 assert(x2.length == 2); 2458 assert(x2.contains(K(10))); 2459 assert(x2.contains(K(12))); 2460 2461 auto x3 = X.withElements([K(12), K(13), K(14)].s); 2462 assert(x3.length == 3); 2463 assert(x3.contains(K(12))); 2464 assert(x3.contains(K(13))); 2465 assert(x3.contains(K(14))); 2466 2467 auto z = X.withElements([K(10), K(12), K(13), K(15)].s); 2468 assert(z.length == 4); 2469 assert(z.contains(K(10))); 2470 assert(z.contains(K(12))); 2471 assert(z.contains(K(13))); 2472 assert(z.contains(K(15))); 2473 2474 auto y = move(z).intersectedWith(x2); 2475 assert(y.length == 2); 2476 assert(y.contains(K(10))); 2477 assert(y.contains(K(12))); 2478 assert(y.containsUsingLinearSearch(K(10))); 2479 assert(y.containsUsingLinearSearch(K(12))); 2480 } 2481 2482 /// r-value and r-value intersection 2483 @safe pure nothrow @nogc unittest 2484 { 2485 import std.typecons : Nullable; 2486 import nxt.digestx.fnv : FNV; 2487 import nxt.array_help : s; 2488 2489 alias K = Nullable!(uint, uint.max); 2490 alias X = HybridHashSet!(K, FNV!(64, true)); 2491 2492 auto y = X.withElements([K(10), K(12), K(13), K(15)].s).intersectedWith(X.withElements([K(12), K(13)].s)); 2493 assert(y.length == 2); 2494 assert(y.contains(K(12))); 2495 assert(y.contains(K(13))); 2496 assert(y.containsUsingLinearSearch(K(12))); 2497 assert(y.containsUsingLinearSearch(K(13))); 2498 } 2499 2500 /** Returns: `x` eagerly intersected with `y`. 2501 TODO: move to container/common.d. 2502 */ 2503 auto intersectWith(C1, C2)(ref C1 x, auto ref const(C2) y) 2504 if (is(C1 == HybridHashMap!(_1), _1) && 2505 is(C2 == HybridHashMap!(_2), _2)) 2506 { 2507 return x.removeAllMatching!(_ => !y.contains(_)); 2508 } 2509 2510 /// r-value and l-value intersection 2511 @safe pure nothrow @nogc unittest 2512 { 2513 import std.typecons : Nullable; 2514 import nxt.digestx.fnv : FNV; 2515 import nxt.array_help : s; 2516 2517 alias K = Nullable!(uint, uint.max); 2518 alias X = HybridHashSet!(K, FNV!(64, true)); 2519 2520 auto x = X.withElements([K(12), K(13)].s); 2521 auto y = X.withElements([K(10), K(12), K(13), K(15)].s); 2522 y.intersectWith(x); 2523 assert(y.length == 2); 2524 assert(y.contains(K(12))); 2525 assert(y.containsUsingLinearSearch(K(12))); 2526 assert(y.contains(K(13))); 2527 assert(y.containsUsingLinearSearch(K(13))); 2528 } 2529 2530 /// Range over elements of l-value instance of this. 2531 static struct ByLvalueElement(SomeMap) // public for now because this is needed in `knet.zing.Zing.EdgesOfRels` 2532 { 2533 pragma(inline, true): 2534 // TODO: functionize 2535 static if (isAddress!(SomeMap.ElementType)) // for reference types 2536 { 2537 /// Get reference to front element. 2538 @property scope SomeMap.ElementType front()() return @trusted 2539 { 2540 // cast to head-const for class key 2541 return (cast(typeof(return))_table._store[_binIndex]); 2542 } 2543 } 2544 else 2545 { 2546 /// Get reference to front element. 2547 @property scope auto ref front() return @trusted 2548 { 2549 return *(cast(const(SomeMap.ElementType)*)&_table._store[_binIndex]); // propagate constnes 2550 } 2551 } 2552 import core.internal.traits : Unqual; 2553 // unqual to reduce number of instantations of `LvalueElementRef` 2554 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2555 alias _elementRef this; 2556 } 2557 2558 /// Range over elements of r-value instance of this. 2559 static private struct ByRvalueElement(SomeMap) 2560 { 2561 @disable this(this); 2562 pragma(inline, true): 2563 static if (isAddress!(SomeMap.ElementType)) // for reference types 2564 { 2565 /// Get reference to front element. 2566 @property scope SomeMap.ElementType front()() return @trusted 2567 { 2568 // cast to head-const for class key 2569 return cast(typeof(return))_table._store[_binIndex]; 2570 } 2571 } 2572 else 2573 { 2574 /// Get reference to front element. 2575 @property scope auto ref front() return 2576 { 2577 return *(cast(const(SomeMap.ElementType)*)&_table._store[_binIndex]); // propagate constnes 2578 } 2579 } 2580 import core.internal.traits : Unqual; 2581 public RvalueElementRef!(Unqual!SomeMap) _elementRef; 2582 alias _elementRef this; 2583 } 2584 2585 /** Returns: range that iterates through the elements of `c` in undefined order. 2586 */ 2587 auto byElement(SomeMap)(auto ref return SomeMap c) @trusted 2588 if (is(SomeMap == HybridHashMap!(_), _...) && 2589 !SomeMap.hasValue) 2590 { 2591 import core.internal.traits : Unqual; 2592 alias M = Unqual!SomeMap; 2593 alias C = const(SomeMap); // be const for now 2594 static if (__traits(isRef, c)) // `c` is an l-value 2595 auto result = ByLvalueElement!C((LvalueElementRef!(M)(cast(M*)&c))); 2596 else // `c` was is an r-value and can be moved 2597 { 2598 import core.lifetime : move; 2599 auto result = ByRvalueElement!C((RvalueElementRef!(M)(move(*(cast(M*)&c))))); // reinterpret 2600 } 2601 result.findNextNonEmptyBin(); 2602 return result; 2603 } 2604 alias range = byElement; // EMSI-container naming 2605 2606 static private struct ByKey_lvalue(SomeMap) 2607 if (is(SomeMap == HybridHashMap!(_), _...) && 2608 SomeMap.hasValue) 2609 { 2610 @property const scope auto ref front() return // key access must be const, TODO: auto ref => ref K 2611 { 2612 version(D_Coverage) {} else pragma(inline, true); 2613 return _table._store[_binIndex].key; 2614 } 2615 import core.internal.traits : Unqual; 2616 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2617 alias _elementRef this; 2618 } 2619 2620 static private struct ByKey_rvalue(SomeMap) 2621 if (is(SomeMap == HybridHashMap!(_), _...) && 2622 SomeMap.hasValue) 2623 { 2624 @property const scope auto ref front() return // key access must be const, TODO: auto ref => ref K 2625 { 2626 version(D_Coverage) {} else pragma(inline, true); 2627 return _table._store[_binIndex].key; 2628 } 2629 import core.internal.traits : Unqual; 2630 public RvalueElementRef!(Unqual!SomeMap) _elementRef; 2631 alias _elementRef this; 2632 } 2633 2634 /** Returns: range that iterates through the keys of `c` in undefined order. 2635 */ 2636 auto byKey(SomeMap)(auto ref /*TODO: return*/ SomeMap c) @trusted 2637 if (is(SomeMap == HybridHashMap!(_), _...) && 2638 SomeMap.hasValue) 2639 { 2640 import core.internal.traits : Unqual; 2641 alias M = Unqual!SomeMap; 2642 alias C = const(SomeMap); // be const 2643 static if (__traits(isRef, c)) // `c` is an l-value 2644 auto result = ByKey_lvalue!C((LvalueElementRef!(M)(cast(M*)&c))); 2645 else // `c` was is an r-value and can be moved 2646 { 2647 import core.lifetime : move; 2648 auto result = ByKey_rvalue!C((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret 2649 } 2650 result.findNextNonEmptyBin(); 2651 return result; 2652 } 2653 2654 static private struct ByValue_lvalue(SomeMap) 2655 if (is(SomeMap == HybridHashMap!(_), _...) && 2656 SomeMap.hasValue) 2657 { 2658 @property scope auto ref front() return @trusted // TODO: auto ref => ref V 2659 { 2660 version(D_Coverage) {} else pragma(inline, true); 2661 // TODO: functionize 2662 import std.traits : isMutable; 2663 static if (isMutable!(SomeMap)) // TODO: can this be solved without this `static if`? 2664 alias E = SomeMap.ValueType; 2665 else 2666 alias E = const(SomeMap.ValueType); 2667 return *(cast(E*)&_table._store[_binIndex].value); 2668 } 2669 import core.internal.traits : Unqual; 2670 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2671 alias _elementRef this; 2672 } 2673 2674 static private struct ByValue_rvalue(SomeMap) 2675 if (is(SomeMap == HybridHashMap!(_), _...) && 2676 SomeMap.hasValue) 2677 { 2678 @property scope auto ref front() return @trusted // TODO: auto ref => ref V 2679 { 2680 version(D_Coverage) {} else pragma(inline, true); 2681 // TODO: functionize 2682 import std.traits : isMutable; 2683 static if (isMutable!(SomeMap)) // TODO: can this be solved without this `static if`? 2684 alias E = SomeMap.ValueType; 2685 else 2686 alias E = const(SomeMap.ValueType); 2687 return *(cast(E*)&_table._store[_binIndex].value); 2688 } 2689 import core.internal.traits : Unqual; 2690 public RvalueElementRef!(Unqual!SomeMap) _elementRef; 2691 alias _elementRef this; 2692 } 2693 2694 /** Returns: range that iterates through the values of `c` in undefined order. 2695 */ 2696 auto byValue(SomeMap)(auto ref return SomeMap c) @trusted 2697 if (is(SomeMap == HybridHashMap!(_), _...) && 2698 SomeMap.hasValue) 2699 { 2700 import core.internal.traits : Unqual; 2701 import std.traits : isMutable; 2702 alias M = Unqual!SomeMap; 2703 alias C = const(SomeMap); 2704 static if (__traits(isRef, c)) // `c` is an l-value 2705 auto result = ByValue_lvalue!SomeMap((LvalueElementRef!(M)(cast(M*)&c))); 2706 else // `c` was is an r-value and can be moved 2707 { 2708 import core.lifetime : move; 2709 auto result = ByValue_rvalue!C((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret 2710 } 2711 result.findNextNonEmptyBin(); 2712 return result; 2713 } 2714 2715 static private struct ByKeyValue_lvalue(SomeMap) 2716 if (is(SomeMap == HybridHashMap!(_), _...) && 2717 SomeMap.hasValue) 2718 { 2719 @property scope auto ref front() return @trusted // TODO: auto ref => ref T 2720 { 2721 version(D_Coverage) {} else pragma(inline, true); 2722 // TODO: functionize 2723 import std.traits : isMutable; 2724 static if (isMutable!(SomeMap)) 2725 alias E = SomeMap.KeyValueType; 2726 else 2727 alias E = const(SomeMap.T); 2728 return *(cast(E*)&_table._store[_binIndex]); 2729 } 2730 import core.internal.traits : Unqual; 2731 public LvalueElementRef!(Unqual!SomeMap) _elementRef; 2732 alias _elementRef this; 2733 } 2734 2735 /** Returns: range that iterates through the key-value-pairs of `c` in undefined order. 2736 */ 2737 auto byKeyValue(SomeMap)(auto ref return SomeMap c) @trusted 2738 if (is(SomeMap == HybridHashMap!(_), _...) && 2739 SomeMap.hasValue) 2740 { 2741 import core.internal.traits : Unqual; 2742 alias M = Unqual!SomeMap; 2743 static if (__traits(isRef, c)) // `c` is an l-value 2744 auto result = ByKeyValue_lvalue!SomeMap((LvalueElementRef!(M)(cast(M*)&c))); 2745 else // `c` was is an r-value and can be moved 2746 { 2747 import core.lifetime : move; 2748 auto result = ByKeyValue_rvalue!SomeMap((RvalueElementRef!M(move(*(cast(M*)&c))))); // reinterpret 2749 } 2750 result.findNextNonEmptyBin(); 2751 return result; 2752 } 2753 2754 /// make range from l-value and r-value. element access is always const 2755 @safe pure unittest 2756 { 2757 import core.exception : AssertError; 2758 import std.typecons : Nullable; 2759 import nxt.digestx.fnv : FNV; 2760 import nxt.array_help : s; 2761 debug import std.exception : assertThrown; 2762 2763 import std.algorithm.searching : count; 2764 alias K = Nullable!(uint, uint.max); 2765 alias X = HybridHashSet!(K, 2766 FNV!(64, true), 2767 defaultKeyEqualPredOf!K, 2768 Mallocator, 2769 BorrowCheckFlag.yes); 2770 2771 auto k11 = K(11); 2772 auto k22 = K(22); 2773 auto k33 = K(33); 2774 auto ks = [k11, k22, k33].s; 2775 auto k44 = K(44); 2776 2777 // mutable 2778 auto x = X.withElements(ks); 2779 assert(!x.contains(k44)); 2780 assert(!x.containsUsingLinearSearch(k44)); 2781 assert(x.length == 3); 2782 2783 assert(x.byElement.count == x.length); 2784 foreach (e; x.byElement) // from l-value 2785 { 2786 debug static assert(is(typeof(e) == const(K))); // always const access 2787 2788 // range invalidation forbidden: 2789 debug 2790 { 2791 assertThrown!AssertError(x.reserveExtra(1)); // range invalidation 2792 assertThrown!AssertError(x.clear()); // range invalidation 2793 assertThrown!AssertError(x.insert(k11)); // range invalidation 2794 assertThrown!AssertError(x.insertN([k11].s)); // range invalidation 2795 assertThrown!AssertError(x.remove(k11)); // range invalidation 2796 } 2797 2798 // allowed 2799 assert(x.contains(e)); 2800 assert(x.containsUsingLinearSearch(e)); 2801 2802 const eHit = e in x; 2803 assert(eHit); // found 2804 assert(*eHit is e); // and the value equals what we searched for 2805 2806 const eDup = x.dup; // duplication is `const` and allowed 2807 assert(eDup == x); 2808 } 2809 2810 // const 2811 const y = X.withElements(ks); 2812 assert(!x.contains(k44)); 2813 assert(!x.containsUsingLinearSearch(k44)); 2814 foreach (e; y.byElement) // from l-value 2815 { 2816 auto _ = y.byElement; // ok to read-borrow again 2817 assert(y.contains(e)); 2818 assert(y.containsUsingLinearSearch(e)); 2819 debug static assert(is(typeof(e) == const(K))); 2820 } 2821 2822 foreach (e; X.withElements([K(11)].s).byElement) // from r-value 2823 { 2824 assert(e == K(11)); 2825 debug static assert(is(typeof(e) == const(K))); // always const access 2826 } 2827 } 2828 2829 /// range checking 2830 @safe pure unittest 2831 { 2832 import core.exception : RangeError; 2833 import std.typecons : Nullable; 2834 import nxt.digestx.fnv : FNV; 2835 debug import std.exception : assertThrown, assertNotThrown; 2836 immutable n = 11; 2837 2838 alias K = Nullable!(uint, uint.max); 2839 alias V = uint; 2840 2841 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2842 2843 auto s = X.withCapacity(n); 2844 2845 void dummy(ref V value) {} 2846 2847 debug assertThrown!RangeError(dummy(s[K(0)])); 2848 2849 foreach (immutable i; 0 .. n) 2850 { 2851 const k = K(i); 2852 s[k] = V(i); 2853 debug assertNotThrown!RangeError(dummy(s[k])); 2854 } 2855 2856 foreach (immutable i; 0 .. n) 2857 { 2858 const k = K(i); 2859 assert(s.remove(k)); 2860 debug assertThrown!RangeError(dummy(s[k])); 2861 } 2862 2863 s[K(0)] = V.init; 2864 auto vp = K(0) in s; 2865 debug static assert(is(typeof(vp) == V*)); 2866 assert((*vp) == V.init); 2867 2868 assert(s.remove(K(0))); 2869 assert(K(0) !in s); 2870 2871 X t; 2872 t.reserveExtra(4096); 2873 2874 t.clear(); 2875 } 2876 2877 /// class as value 2878 @safe pure unittest 2879 { 2880 import core.exception : RangeError; 2881 import std.typecons : Nullable; 2882 debug import std.exception : assertThrown, assertNotThrown; 2883 import nxt.digestx.fnv : FNV; 2884 2885 immutable n = 11; 2886 2887 alias K = Nullable!(uint, uint.max); 2888 class V 2889 { 2890 this(uint data) { this.data = data; } 2891 uint data; 2892 } 2893 2894 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2895 2896 auto s = X.withCapacity(n); 2897 2898 void dummy(ref V value) {} 2899 2900 debug assertThrown!RangeError(dummy(s[K(0)])); 2901 2902 foreach (immutable i; 0 .. n) 2903 { 2904 const k = K(i); 2905 s[k] = new V(i); 2906 debug assertNotThrown!RangeError(dummy(s[k])); 2907 } 2908 2909 // test range 2910 { 2911 auto sr = s.byKeyValue; // scoped range 2912 assert(sr.length == n); 2913 foreach (immutable i; 0 .. n) 2914 { 2915 sr.popFront(); 2916 assert(sr.length == n - i - 1); 2917 } 2918 } 2919 2920 foreach (immutable i; 0 .. n) 2921 { 2922 const k = K(i); 2923 assert(s.remove(k)); 2924 debug assertThrown!RangeError(dummy(s[k])); 2925 } 2926 2927 s[K(0)] = V.init; 2928 auto vp = K(0) in s; 2929 debug static assert(is(typeof(vp) == V*)); 2930 2931 assert(s.remove(K(0))); 2932 assert(K(0) !in s); 2933 2934 X t; 2935 t.reserveExtra(4096); 2936 } 2937 2938 /// constness inference of ranges 2939 pure nothrow unittest 2940 { 2941 import std.typecons : Nullable; 2942 import nxt.digestx.fnv : FNV; 2943 2944 alias K = Nullable!(uint, uint.max); 2945 class V 2946 { 2947 this(uint data) { this.data = data; } 2948 uint data; 2949 } 2950 2951 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2952 const x = X(); 2953 2954 foreach (const e; x.byKey) 2955 { 2956 debug static assert(is(typeof(e) == const(X.KeyType))); 2957 } 2958 2959 foreach (const e; x.byValue) 2960 { 2961 debug static assert(is(typeof(e) == const(X.ValueType))); 2962 } 2963 2964 foreach (const e; X.init.byValue) 2965 { 2966 debug static assert(is(typeof(e) == const(X.ValueType))); 2967 } 2968 2969 foreach (const e; x.byKeyValue) 2970 { 2971 debug static assert(is(typeof(e.key) == const(X.KeyType))); 2972 debug static assert(is(typeof(e.value) == const(X.ValueType))); 2973 debug static assert(is(typeof(e) == const(X.ElementType))); 2974 } 2975 } 2976 2977 /// range key constness and value mutability with `class` value 2978 pure nothrow unittest 2979 { 2980 import std.typecons : Nullable; 2981 import nxt.digestx.fnv : FNV; 2982 2983 struct S 2984 { 2985 uint value; 2986 } 2987 alias K = Nullable!(S, S(uint.min)); // use uint.min to trigger use of faster `allocator.allocateZeroed` 2988 2989 class V 2990 { 2991 this(uint data) { this.data = data; } 2992 uint data; 2993 } 2994 2995 alias X = HybridHashMap!(K, V, FNV!(64, true)); 2996 auto x = X(); 2997 2998 x[K(S(42))] = new V(43); 2999 3000 assert(x.length == 1); 3001 3002 foreach (e; x.byValue) // `e` is auto ref 3003 { 3004 debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value 3005 assert(e.data == 43); 3006 3007 // value mutation side effects 3008 e.data += 1; 3009 assert(e.data == 44); 3010 e.data -= 1; 3011 assert(e.data == 43); 3012 } 3013 3014 foreach (ref e; x.byKeyValue) // `e` is auto ref 3015 { 3016 debug static assert(is(typeof(e.key) == const(X.KeyType))); // const access to key 3017 debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 3018 3019 assert(e.key.value == 42); 3020 assert(e.value.data == 43); 3021 3022 // key cannot be mutated 3023 debug static assert(!__traits(compiles, { e.key.value += 1; })); 3024 3025 // value mutation side effects 3026 e.value.data += 1; 3027 assert(e.value.data == 44); 3028 e.value.data -= 1; 3029 assert(e.value.data == 43); 3030 } 3031 } 3032 3033 /// range key constness and value mutability with `class` key and `class` value 3034 pure nothrow unittest 3035 { 3036 import nxt.digestx.fnv : FNV; 3037 3038 class K 3039 { 3040 this(uint value) 3041 { 3042 this.value = value; 3043 } 3044 3045 @property bool opEquals(const scope typeof(this) rhs) const 3046 { 3047 return value == rhs.value; 3048 } 3049 3050 uint value; 3051 } 3052 3053 class V 3054 { 3055 this(uint data) { this.data = data; } 3056 uint data; 3057 } 3058 3059 alias X = HybridHashMap!(K, V, FNV!(64, true)); 3060 auto x = X(); 3061 3062 x[new K(42)] = new V(43); 3063 3064 assert(x.length == 1); 3065 3066 foreach (e; x.byValue) // `e` is auto ref 3067 { 3068 debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value 3069 assert(e.data == 43); 3070 3071 // value mutation side effects 3072 e.data += 1; 3073 assert(e.data == 44); 3074 e.data -= 1; 3075 assert(e.data == 43); 3076 } 3077 3078 foreach (ref e; x.byKeyValue) // `e` is auto ref 3079 { 3080 debug static assert(is(typeof(e.key) == X.KeyType)); // mutable access to class key 3081 debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 3082 3083 assert(e.key.value == 42); 3084 assert(e.value.data == 43); 3085 3086 // class key itself should not be mutable 3087 debug static assert(!__traits(compiles, { e.key = null; })); 3088 3089 // members of key can be mutated 3090 debug static assert(__traits(compiles, { e.key.value += 1; })); 3091 3092 // value mutation side effects 3093 e.value.data += 1; 3094 assert(e.value.data == 44); 3095 e.value.data -= 1; 3096 assert(e.value.data == 43); 3097 } 3098 } 3099 3100 /// range key constness and value mutability with `class` key and `class` value 3101 pure nothrow unittest 3102 { 3103 import nxt.digestx.fnv : FNV; 3104 class K 3105 { 3106 this(uint value) 3107 { 3108 this.value = value; 3109 } 3110 uint value; 3111 } 3112 3113 struct V 3114 { 3115 this(uint data) { this.data = data; } 3116 @disable this(this); 3117 uint data; 3118 } 3119 3120 alias X = HybridHashMap!(K, V, FNV!(64, true)); 3121 auto x = X(); 3122 3123 scope key42 = new K(42); 3124 x[key42] = V(43); 3125 3126 assert(x.length == 1); 3127 3128 foreach (ref e; x.byValue) // `e` is auto ref 3129 { 3130 debug static assert(is(typeof(e) == X.ValueType)); // mutable access to value 3131 assert(e.data == 43); 3132 3133 // value mutation side effects 3134 e.data += 1; 3135 assert(e.data == 44); 3136 e.data -= 1; 3137 assert(e.data == 43); 3138 } 3139 3140 foreach (ref e; x.byKeyValue) // `e` is auto ref 3141 { 3142 debug static assert(is(typeof(e.key) == X.KeyType)); // mutable access to class key 3143 debug static assert(is(typeof(e.value) == X.ValueType)); // mutable access to value 3144 3145 assert(e.key.value == 42); 3146 assert(e.value.data == 43); 3147 3148 // value mutation side effects 3149 e.value.data += 1; 3150 assert(e.value.data == 44); 3151 e.value.data -= 1; 3152 assert(e.value.data == 43); 3153 } 3154 3155 assert(x.length == 1); 3156 3157 assert(x.remove(key42)); 3158 assert(x.length == 0); 3159 3160 x[key42] = V(43); 3161 assert(x.length == 1); 3162 } 3163 3164 version(unittest) 3165 { 3166 T make(T)(ulong value) 3167 { 3168 static if (is(T == class)) 3169 return new T(value); 3170 else 3171 return T(value); 3172 } 3173 } 3174 3175 /// test various things 3176 @trusted unittest 3177 { 3178 import std.meta : AliasSeq; 3179 import std.typecons : Nullable; 3180 import std.algorithm.comparison : equal; 3181 import nxt.container.traits : mustAddGCRange; 3182 import nxt.digestx.fnv : FNV; 3183 import nxt.array_help : s; 3184 3185 const n = 100; 3186 3187 void testEmptyAll(K, V, X)(ref X x, size_t n, 3188 scope K[] keys) 3189 { 3190 assert(x.length == n); 3191 foreach (key; keys) 3192 { 3193 static if (X.hasValue) 3194 const element = X.ElementType(key, V.init); 3195 else 3196 alias element = key; 3197 3198 assert(x.length == n - key.get); 3199 3200 const hitPtr = key in x; 3201 static if (X.hasValue) 3202 assert(hitPtr && *hitPtr is element.value); 3203 else 3204 assert(hitPtr && *hitPtr is element); 3205 3206 assert(x.remove(key)); 3207 assert(x.length == n - key.get - 1); 3208 3209 static if (!X.hasValue) 3210 { 3211 assert(!x.contains(key)); 3212 assert(!x.containsUsingLinearSearch(key)); 3213 } 3214 assert(key !in x); 3215 assert(!x.remove(key)); 3216 assert(x.length == n - key.get - 1); 3217 } 3218 3219 assert(x.length == 0); 3220 3221 x.clear(); 3222 assert(x.length == 0); 3223 } 3224 3225 X testDup(X)(scope ref X x, size_t n) 3226 { 3227 typeof(return) y = x.dup; 3228 3229 assert(x._store.ptr !is y._store.ptr); 3230 assert(x.length == y.length); 3231 assert(y.length == n); 3232 // non-symmetric algorithm so both are needed 3233 assert(y == x); 3234 assert(x == y); 3235 3236 static if (X.hasValue) 3237 { 3238 assert(equal(x.byKey, 3239 y.byKey)); 3240 assert(equal(x.byValue, 3241 y.byValue)); 3242 auto a = x.byKeyValue; 3243 auto b = y.byKeyValue; 3244 size_t i = 0; 3245 while (!a.empty && 3246 !b.empty) 3247 { 3248 a.popFront(); 3249 b.popFront(); 3250 i++; 3251 } 3252 auto xR = x.byKeyValue; 3253 auto yR = y.byKeyValue; 3254 assert(xR.length == yR.length); 3255 size_t ix = 0; 3256 while (!xR.empty && 3257 !yR.empty) 3258 { 3259 auto xK = xR.front.key; 3260 auto yK = yR.front.key; 3261 auto xV = xR.front.value; 3262 auto yV = yR.front.value; 3263 // import std.stdio : writeln; 3264 // writeln("ix:", ix, " xV:", xV, " yV:", yV); 3265 assert(xK == yK); 3266 assert(xV == yV); 3267 assert(xR.front == yR.front); 3268 xR.popFront(); 3269 yR.popFront(); 3270 ix++; 3271 } 3272 assert(equal(x.byKeyValue, 3273 y.byKeyValue)); 3274 } 3275 else 3276 assert(equal(x.byElement, 3277 y.byElement)); 3278 3279 debug static assert(!__traits(compiles, { const _ = x < y; })); // no ordering 3280 3281 return y; 3282 } 3283 3284 alias NullableUlong = Nullable!(ulong, ulong.max); 3285 3286 static class SomeSimpleClass 3287 { 3288 @safe pure nothrow @nogc 3289 this(ulong value) 3290 { 3291 this._value = value; 3292 } 3293 3294 @safe pure nothrow @nogc 3295 ulong get() const 3296 { 3297 return _value; 3298 } 3299 3300 void toString(Sink)(ref scope Sink sink) const 3301 { 3302 import std.format : formattedWrite; 3303 sink.formattedWrite(typeof(this).stringof, "(%s)", _value); 3304 } 3305 3306 @property bool opEquals(const scope typeof(this) rhs) const 3307 { 3308 return _value == rhs._value; 3309 } 3310 3311 private ulong _value; 3312 } 3313 3314 debug static assert(mustAddGCRange!string); 3315 3316 foreach (K; AliasSeq!(SomeSimpleClass, 3317 NullableUlong)) 3318 { 3319 foreach (V; AliasSeq!(string, int, void)) 3320 { 3321 alias X = HybridHashMap!(K, V, FNV!(64, true)); 3322 3323 static if (!X.hasValue) 3324 { 3325 auto k11 = make!K(11); 3326 auto k12 = make!K(12); 3327 auto k13 = make!K(13); 3328 3329 auto x = X.withElements([k11, k12, k13].s); 3330 3331 import std.algorithm : count; 3332 3333 // ByLvalueElement 3334 auto xr = x.byElement; 3335 3336 alias R = typeof(xr); 3337 import std.range.primitives : isInputRange; 3338 import std.traits : ReturnType; 3339 debug static assert(is(typeof(R.init) == R)); 3340 debug static assert(is(ReturnType!((R xr) => xr.empty) == bool)); 3341 3342 // TODO: Is this needed? debug static assert(!__traits(compiles, { xr.front == K.init; })); // always head-const 3343 auto f = xr.front; 3344 static if (is(K == class)) 3345 { 3346 debug static assert(is(typeof(f) == K)); // tail-mutable 3347 } 3348 else 3349 { 3350 debug static assert(is(typeof(f) == const(K))); // tail-const 3351 } 3352 3353 debug static assert(is(typeof((R xr) => xr.front))); 3354 debug static assert(!is(ReturnType!((R xr) => xr.front) == void)); 3355 debug static assert(is(typeof((R xr) => xr.popFront))); 3356 3357 debug static assert(isInputRange!(typeof(xr))); 3358 3359 assert(x.byElement.count == 3); 3360 3361 X y; 3362 size_t ix = 0; 3363 foreach (ref e; x.byElement) 3364 { 3365 assert(x.contains(e)); 3366 assert(x.containsUsingLinearSearch(e)); 3367 assert(!y.contains(e)); 3368 assert(!y.containsUsingLinearSearch(e)); 3369 static if (is(K == class)) 3370 y.insert(cast(K)e); // ugly but ok in tests 3371 else 3372 y.insert(e); 3373 assert(y.contains(e)); 3374 assert(y.containsUsingLinearSearch(e)); 3375 ix++; 3376 } 3377 3378 assert(y.byElement.count == 3); 3379 assert(x == y); 3380 3381 const z = X(); 3382 assert(z.byElement.count == 0); 3383 3384 immutable w = X(); 3385 assert(w.byElement.count == 0); 3386 3387 { 3388 auto xc = X.withElements([k11, k12, k13].s); 3389 assert(xc.length == 3); 3390 assert(xc.contains(k11)); 3391 assert(xc.containsUsingLinearSearch(k11)); 3392 3393 // TODO: http://forum.dlang.org/post/kvwrktmameivubnaifdx@forum.dlang.org 3394 xc.removeAllMatching!(_ => _ == k11); 3395 3396 assert(xc.length == 2); 3397 assert(!xc.contains(k11)); 3398 assert(!xc.containsUsingLinearSearch(k11)); 3399 3400 xc.removeAllMatching!(_ => _ == k12); 3401 assert(!xc.contains(k12)); 3402 assert(!xc.containsUsingLinearSearch(k12)); 3403 assert(xc.length == 1); 3404 3405 xc.removeAllMatching!(_ => _ == k13); 3406 assert(!xc.contains(k13)); 3407 assert(!xc.containsUsingLinearSearch(k13)); 3408 assert(xc.length == 0); 3409 3410 // this is ok 3411 foreach (_; xc.byElement) {} 3412 } 3413 3414 { // ByRvalueElement 3415 auto k = X.withElements([k11, k12].s).filtered!(_ => _ != k11).byElement; 3416 debug static assert(isInputRange!(typeof(k))); 3417 assert(k.front == k12); 3418 3419 debug static assert(!__traits(compiles, { k.front = K.init; })); // head-const 3420 static if (is(K == class)) 3421 { 3422 debug static assert(is(typeof(k.front) == K)); // tail-mutable 3423 } 3424 else 3425 { 3426 debug static assert(is(typeof(k.front) == const(K))); // tail-const 3427 } 3428 3429 k.popFront(); 3430 assert(k.empty); 3431 } 3432 3433 { 3434 X q; 3435 auto qv = [make!K(11U), make!K(12U), make!K(13U), make!K(14U)].s; 3436 q.insertN(qv[]); 3437 foreach (e; qv[]) 3438 { 3439 assert(q.contains(e)); 3440 assert(q.containsUsingLinearSearch(e)); 3441 } 3442 q.clear(); 3443 assert(q.empty); 3444 } 3445 } 3446 3447 static if (is(V == string)) 3448 { 3449 debug static assert(mustAddGCRange!V); 3450 debug static assert(mustAddGCRange!(V[1])); 3451 debug static assert(mustAddGCRange!(X.T)); 3452 } 3453 3454 auto x1 = X(); // start empty 3455 3456 // fill x1 3457 3458 import std.array : Appender; 3459 Appender!(K[]) keys; 3460 3461 foreach (immutable key_; 0 .. n) 3462 { 3463 auto key = make!K(key_); 3464 keys.put(key); 3465 3466 // create elements 3467 static if (X.hasValue) 3468 { 3469 auto value = V.init; 3470 auto element = X.ElementType(key, value); 3471 } 3472 else 3473 // no assignment because Nullable.opAssign may leave rhs in null state 3474 auto element = key; 3475 3476 assert(key !in x1); 3477 3478 assert(x1.length == key.get); 3479 assert(x1.insert(element) == X.InsertionStatus.added); 3480 assert(x1.length == key.get + 1); 3481 3482 static if (X.hasValue) 3483 { 3484 import std.conv : to; 3485 auto e2 = X.ElementType(key, (42 + key_).to!V); 3486 assert(x1.insert(e2) == X.InsertionStatus.modified); 3487 assert(x1.contains(key)); 3488 assert(x1.get(key, V.init) == (42 + key_).to!V); 3489 3490 assert(x1.remove(key)); 3491 assert(!x1.contains(key)); 3492 3493 x1[key] = value; // restore value 3494 assert(x1.contains(key)); 3495 } 3496 3497 assert(x1.length == key.get + 1); 3498 3499 const hitPtr = key in x1; 3500 static if (X.hasValue) 3501 assert(hitPtr && *hitPtr == value); 3502 else 3503 assert(hitPtr && *hitPtr is key); 3504 3505 auto status = x1.insert(element); 3506 assert(status == X.InsertionStatus.unmodified); 3507 static if (X.hasValue) 3508 assert(x1.insert(key, value) == X.InsertionStatus.unmodified); 3509 assert(x1.length == key.get + 1); 3510 3511 assert(key in x1); 3512 } 3513 3514 static if (X.hasValue) 3515 { 3516 import nxt.container.dynamic_array : Array = DynamicArray; 3517 Array!(X.ElementType) a1; // remember the keys 3518 3519 foreach (const ref key; x1.byKey) 3520 { 3521 auto keyPtr = key in x1; 3522 assert(keyPtr); 3523 a1 ~= X.ElementType(cast(K)key, (*keyPtr)); 3524 } 3525 3526 assert(x1.length == a1.length); 3527 3528 foreach (ae; a1[]) 3529 { 3530 auto keyPtr = ae.key in x1; 3531 assert(keyPtr); 3532 assert((*keyPtr) is ae.value); 3533 } 3534 } 3535 3536 assert(x1.length == n); 3537 3538 auto x2 = testDup(x1, n); 3539 3540 testEmptyAll!(K, V)(x1, n, keys.data); 3541 3542 testEmptyAll!(K, V)(x2, n, keys.data); // should be not affected by emptying of x1 3543 } 3544 } 3545 } 3546 3547 /// 3548 @safe pure nothrow @nogc unittest 3549 { 3550 import std.typecons : Nullable; 3551 import nxt.digestx.fnv : FNV; 3552 3553 alias X = HybridHashMap!(Nullable!(size_t, size_t.max), size_t, FNV!(64, true)); 3554 X x; 3555 assert(x.empty); 3556 // import nxt.container.dynamic_array : Array = DynamicArray; 3557 // TODO: these segfault: 3558 // TODO: auto a = Array!(X.KeyType).withElementsOfRange_untested(x.byKey); // l-value byKey 3559 // TODO: auto b = Array!(X.KeyType).withElementsOfRange_untested(X().byKey); // r-value byKey 3560 } 3561 3562 /// manual Nullable type 3563 @safe pure unittest 3564 { 3565 import nxt.digestx.fnv : FNV; 3566 3567 static class Zing 3568 { 3569 @safe pure nothrow @nogc: 3570 this(ulong value) { this._value = value; } 3571 private ulong _value; 3572 } 3573 debug static assert(isNullable!Zing); 3574 3575 enum Alt 3576 { 3577 unknown, 3578 a, 3579 b, 3580 c, 3581 d 3582 } 3583 3584 struct ZingRelation 3585 { 3586 Zing zing; 3587 Alt alts; 3588 3589 alias nullifier = zing; 3590 static immutable nullValue = typeof(this).init; 3591 3592 bool opEquals(const scope typeof(this) that) const @safe pure nothrow @nogc 3593 { 3594 return (this.zing is that.zing && 3595 this.alts == that.alts); 3596 } 3597 } 3598 debug static assert(isNullable!ZingRelation); 3599 3600 alias X = HybridHashSet!(ZingRelation, FNV!(64, true)); 3601 debug static assert(X.sizeof == 32); // TODO: fix hole handling and change to 24 3602 X x; 3603 3604 scope e = ZingRelation(new Zing(42), Alt.init); 3605 3606 assert(!x.contains(e)); 3607 assert(!x.containsUsingLinearSearch(e)); 3608 assert(x.insert(e) == X.InsertionStatus.added); 3609 assert(x.contains(e)); 3610 assert(x.containsUsingLinearSearch(e)); 3611 } 3612 3613 /// abstract class value type 3614 @safe unittest 3615 { 3616 static abstract class Zing 3617 { 3618 @safe pure nothrow @nogc: 3619 } 3620 static class Node : Zing 3621 { 3622 @safe pure nothrow @nogc: 3623 } 3624 3625 alias X = HybridHashSet!(Zing); 3626 X x; 3627 3628 const Zing cz = new Node(); 3629 x.insert(cz); // ok to insert const 3630 3631 Zing z = new Node(); 3632 x.insert(z); // ok to insert mutable because hashing is on address by default 3633 } 3634 3635 /// class type with default hashing 3636 @safe unittest 3637 { 3638 static class Base 3639 { 3640 static size_t dtorCount = 0; // number of calls to this destructor 3641 @safe nothrow @nogc: 3642 ~this() nothrow @nogc { dtorCount += 1; } 3643 pure: 3644 this(ulong value) { this._value = value; } 3645 @property bool opEquals(const scope typeof(this) rhs) const 3646 { 3647 return _value == rhs._value; 3648 } 3649 override hash_t toHash() const 3650 { 3651 return hashOf(_value); 3652 } 3653 private ulong _value; 3654 } 3655 3656 /** Node containing same data members but different type. */ 3657 static class Node : Base 3658 { 3659 @safe pure nothrow @nogc: 3660 this(ulong value) { super(value); } 3661 } 3662 debug static assert(is(Node : Base)); 3663 3664 import nxt.hash_functions : hashOfPolymorphic; // neede to separate hash of `Base(N)` from `Node(N)` 3665 alias X = HybridHashSet!(Base, hashOfPolymorphic, "a && b && (typeid(a) is typeid(b)) && a.opEquals(b)"); 3666 debug static assert(X.sizeof == 24); 3667 X x; 3668 3669 // top-class 3670 scope b42 = new Base(42); 3671 assert(!x.contains(b42)); 3672 assert(!x.containsUsingLinearSearch(b42)); 3673 assert(x.insert(b42) == X.InsertionStatus.added); 3674 assert(x.contains(b42)); 3675 assert(x.containsUsingLinearSearch(b42)); 3676 assert(x.tryGetElementFromCtorParams!Base(42) !is null); 3677 assert(Base.dtorCount == 1); 3678 assert(x.tryGetElementFromCtorParams!Base(42)._value == 42); 3679 assert(Base.dtorCount == 2); 3680 assert(x.tryGetElementFromCtorParams!Base(41) is null); 3681 assert(Base.dtorCount == 3); 3682 3683 // top-class 3684 scope b43 = new Base(43); 3685 assert(!x.contains(b43)); 3686 assert(!x.containsUsingLinearSearch(b43)); 3687 assert(x.insert(b43) == X.InsertionStatus.added); 3688 assert(x.contains(b43)); 3689 assert(x.containsUsingLinearSearch(b43)); 3690 assert(x.tryGetElementFromCtorParams!Base(43) !is null); 3691 assert(Base.dtorCount == 4); 3692 assert(x.tryGetElementFromCtorParams!Base(43)._value == 43); 3693 assert(Base.dtorCount == 5); 3694 3695 // sub-class 3696 assert(x.tryGetElementFromCtorParams!Node(42) is null); 3697 assert(Base.dtorCount == 6); 3698 immutable n42 = new Node(42); 3699 assert(!x.contains(n42)); // mustn't equal to `b42` 3700 assert(!x.containsUsingLinearSearch(n42)); // mustn't equal to `b42` 3701 assert(x.insert(n42) == X.InsertionStatus.added); // added as separate type 3702 assert(x.contains(n42)); 3703 assert(x.containsUsingLinearSearch(n42)); 3704 assert(x.tryGetElementFromCtorParams!Node(42) !is null); 3705 assert(Base.dtorCount == 7); 3706 assert(x.tryGetElementFromCtorParams!Node(42)._value == 42); 3707 assert(Base.dtorCount == 8); 3708 3709 assert(hashOf(b42) == hashOf(n42)); 3710 3711 // sub-class 3712 assert(x.tryGetElementFromCtorParams!Node(43) is null); 3713 assert(Base.dtorCount == 9); 3714 auto n43 = new Node(43); 3715 assert(!x.contains(n43)); // mustn't equal to `b43` 3716 assert(!x.containsUsingLinearSearch(n43)); // mustn't equal to `b43` 3717 assert(x.insert(n43) == X.InsertionStatus.added); // added as separate type 3718 assert(x.contains(n43)); 3719 assert(x.containsUsingLinearSearch(n43)); 3720 assert(x.tryGetElementFromCtorParams!Node(43) !is null); 3721 assert(Base.dtorCount == 10); 3722 assert(x.tryGetElementFromCtorParams!Node(43)._value == 43); 3723 assert(Base.dtorCount == 11); 3724 3725 assert(hashOf(b43) == hashOf(n43)); 3726 } 3727 3728 /// enumeration key 3729 @safe pure unittest 3730 { 3731 import nxt.digestx.fnv : FNV; 3732 3733 enum Alt 3734 { 3735 nullValue, // trait 3736 a, b, c, d 3737 } 3738 alias X = HybridHashSet!(Alt, FNV!(64, true)); 3739 X x; 3740 assert(!x.contains(Alt.a)); 3741 3742 assert(x.insert(Alt.a) == X.InsertionStatus.added); 3743 3744 assert(x.contains(Alt.a)); 3745 assert(x.containsUsingLinearSearch(Alt.a)); 3746 assert(!x.contains(Alt.b)); 3747 assert(!x.contains(Alt.c)); 3748 assert(!x.contains(Alt.d)); 3749 assert(!x.containsUsingLinearSearch(Alt.b)); 3750 assert(!x.containsUsingLinearSearch(Alt.c)); 3751 assert(!x.containsUsingLinearSearch(Alt.d)); 3752 3753 assert(x.remove(Alt.a)); 3754 assert(!x.contains(Alt.a)); 3755 assert(!x.containsUsingLinearSearch(Alt.a)); 3756 } 3757 3758 /// 3759 @safe pure nothrow unittest 3760 { 3761 import nxt.digestx.fnv : FNV; 3762 static struct Rel 3763 { 3764 static immutable nullValue = typeof(this).init; 3765 string name; // relation name. WARNING compiler crashes when qualified with `package` 3766 } 3767 alias X = HybridHashSet!(Rel, FNV!(64, true)); 3768 X x; 3769 foreach (const i; 0 .. 100) 3770 { 3771 const char[1] ch = ['a' + i]; 3772 assert(!x.contains(Rel(ch.idup))); 3773 assert(!x.containsUsingLinearSearch(Rel(ch.idup))); 3774 x.insert(Rel(ch.idup)); 3775 assert(x.contains(Rel(ch.idup))); 3776 /* TODO: assert(x.containsUsingLinearSearch(Rel(ch.idup))); */ 3777 } 3778 } 3779 3780 /// `SSOString` as set key type 3781 @safe pure nothrow @nogc unittest 3782 { 3783 import nxt.sso_string : SSOString; 3784 import nxt.digestx.fnv : FNV; 3785 3786 alias K = SSOString; 3787 static assert(isHoleable!K); 3788 alias X = HybridHashSet!(K, FNV!(64, true)); 3789 const n = 100; 3790 3791 X a; 3792 foreach (const i; 0 .. n) 3793 { 3794 const char[1] ch = ['a' + i]; 3795 const k = K(ch); // @nogc 3796 3797 assert(!a.contains(k)); 3798 assert(!a.containsUsingLinearSearch(k)); 3799 3800 assert(a.insert(K(ch)) == X.InsertionStatus.added); 3801 // TODO: assert(a.insertAndReturnElement(K(ch)) == k); 3802 assert(a.contains(k)); 3803 assert(a.containsUsingLinearSearch(k)); 3804 3805 assert(a.remove(k)); 3806 assert(!a.contains(k)); 3807 assert(a.insert(K(ch)) == X.InsertionStatus.added); 3808 3809 assert(a.remove(ch[])); 3810 assert(!a.contains(k)); 3811 assert(a.insert(K(ch)) == X.InsertionStatus.added); 3812 } 3813 3814 X b; 3815 foreach (const i; 0 .. n) 3816 { 3817 const char[1] ch = ['a' + (n - 1 - i)]; 3818 const k = K(ch); // @nogc 3819 3820 assert(!b.contains(k)); 3821 assert(!b.containsUsingLinearSearch(k)); 3822 3823 assert(b.insert(K(ch)) == X.InsertionStatus.added); 3824 // TODO: assert(b.insertAndReturnElement(K(ch)) == k); 3825 3826 assert(b.contains(k)); 3827 assert(b.containsUsingLinearSearch(k)); 3828 3829 assert(b.remove(k)); 3830 assert(!b.contains(k)); 3831 3832 assert(b.insert(K(ch)) == X.InsertionStatus.added); 3833 } 3834 3835 assert(a == b); 3836 3837 const us = K("_"); 3838 assert(!a.contains(us)); 3839 a ~= us; 3840 assert(a.contains(us)); 3841 } 3842 3843 /// test `opIndexOpAssign` 3844 @safe pure nothrow unittest 3845 { 3846 import nxt.sso_string : SSOString; 3847 import nxt.digestx.fnv : FNV; 3848 3849 alias K = SSOString; 3850 alias V = long; 3851 alias X = HybridHashMap!(K, V, FNV!(64, true)); 3852 3853 X x; 3854 3855 const a = K("a"); 3856 const b = K("b"); 3857 3858 x[a] = 17; 3859 assert(x[a] == 17); 3860 3861 x[a] += 10; // opIndexOpAssign!("+=") with existing key 3862 assert(x[a] == 27); 3863 3864 x[b] += 10; // opIndexOpAssign!("+=") with non-existing key 3865 assert(x[b] == 10); 3866 3867 x[b] *= 10; // opIndexOpAssign!("*=") with non-existing key 3868 assert(x[b] == 100); 3869 3870 assert(x.length == 2); 3871 3872 assert(x.contains(a)); 3873 assert(x.contains(a[])); 3874 assert(a in x); 3875 assert(a[] in x); 3876 3877 assert(x.contains(b)); 3878 assert(x.contains(b[])); 3879 assert(b in x); 3880 assert(b[] in x); 3881 3882 const c = K("c"); 3883 assert(!x.contains(c)); 3884 assert(!x.contains(c[])); 3885 assert(c !in x); 3886 assert(c[] !in x); 3887 } 3888 3889 /// use prime numbers as capacity 3890 version(none) // TODO: enable 3891 @safe pure unittest 3892 { 3893 import nxt.address : AlignedAddress; 3894 alias K = AlignedAddress!1; 3895 alias V = size_t; 3896 alias M = HybridHashMap!(K, V, hashOf, defaultKeyEqualPredOf!K, Mallocator, 3897 BorrowCheckFlag.no, true, PrimeCapacityFlag.yes); 3898 M x; 3899 assert(x.empty); 3900 } 3901 3902 /// `SSOString` as map key type 3903 @safe pure nothrow @nogc unittest 3904 { 3905 import nxt.sso_string : SSOString; 3906 import nxt.digestx.fnv : FNV; 3907 alias K = SSOString; 3908 alias V = long; 3909 alias X = HybridHashMap!(K, V, FNV!(64, true)); 3910 const n = 100; 3911 3912 immutable default_k = K("miss"); 3913 3914 X a; 3915 3916 // insert all 3917 foreach (const i; 0 .. n) 3918 { 3919 const char[1] ch = ['a' + i]; 3920 const k = K(ch); // @nogc 3921 assert(k[] == ch[]); 3922 3923 assert(!a.contains(k)); 3924 assert(!a.contains(ch[])); // @nogc 3925 assert(a.getKeyRef(k, default_k)[] is default_k[]); // on miss use `default_k` 3926 // TODO: assert(a.getKeyRef(ch, default_k)[] is default_k[]); // on miss use `default_k` 3927 3928 a[k] = V.init; 3929 3930 assert(a.contains(k)); 3931 assert(a.contains(ch[])); // @nogc 3932 assert(a.getKeyRef(k, default_k)[] !is k[]); // on hit doesn't use `default_k` 3933 assert(a.getKeyRef(k, default_k)[] == ch); 3934 // TODO: assert(a.getKeyRef(ch, default_k)[] !is k[]); // on hit doesn't use `default_k` 3935 // assert(a.getKeyRef(ch, default_k)[] == ch); 3936 } 3937 assert(a.length == n); 3938 3939 // remove all 3940 foreach (const i; 0 .. n) 3941 { 3942 const char[1] ch = ['a' + i]; 3943 const k = K(ch); // @nogc 3944 assert(a.contains(k)); 3945 assert(a.remove(k)); 3946 assert(!a.contains(k)); 3947 } 3948 assert(a.length == 0); 3949 3950 // insert all again 3951 foreach (const i; 0 .. n) 3952 { 3953 const char[1] ch = ['a' + i]; 3954 const k = K(ch); // @nogc 3955 assert(k[] == ch[]); 3956 3957 assert(!a.contains(k)); 3958 assert(!a.contains(ch[])); // @nogc 3959 assert(a.getKeyRef(k, default_k)[] is default_k[]); // on miss use `default_k` 3960 // TODO: assert(a.getKeyRef(ch, default_k)[] is default_k[]); // on miss use `default_k` 3961 3962 a[k] = V.init; 3963 } 3964 assert(a.length == n); 3965 3966 X b; 3967 foreach (const i; 0 .. n) 3968 { 3969 const char[1] ch = ['a' + (n - 1 - i)]; 3970 const k = K(ch); // @nogc 3971 3972 assert(!b.contains(k)); 3973 3974 b[k] = V.init; 3975 3976 assert(b.contains(k)); 3977 } 3978 3979 assert(a == b); 3980 } 3981 3982 /// 3983 @safe pure nothrow @nogc unittest 3984 { 3985 import nxt.address : AlignedAddress; 3986 alias A = AlignedAddress!1; 3987 HybridHashMap!(A, A) m; 3988 static assert(m.sizeof == 3*size_t.sizeof); // assure that hole bitmap is not used 3989 foreach (const address; 1 .. 0x1000) 3990 { 3991 const key = address; 3992 const value = 2*address; 3993 assert(A(key) !in m); 3994 m[A(key)] = A(value); 3995 const eq = m[A(key)] == A(value); 3996 assert(eq); 3997 assert(A(key) in m); 3998 } 3999 } 4000 4001 /// 4002 @safe pure nothrow @nogc unittest 4003 { 4004 import nxt.sso_string : SSOString; 4005 alias K = SSOString; 4006 alias V = long; 4007 alias X = HybridHashMap!(K, V, hashOf, defaultKeyEqualPredOf!(K), Mallocator, BorrowCheckFlag.no); 4008 X x; 4009 } 4010 4011 /** Is `true` iff `T` is a memory address (either a `class` or a pointer). */ 4012 enum bool isAddress(T) = (is(T == class) || 4013 (is(T == U*, U) && 4014 // exclude alias this: 4015 !(is(T == struct) || 4016 is(T == union) || 4017 is(T == interface)))); 4018 4019 /** Is `true` iff `T` has a specific value dedicated to representing holes 4020 * (removed/erase) values. 4021 */ 4022 enum isHoleable(T) = (// __traits(hasMember, T, "isHole") && 4023 // __traits(hasMember, T, "holeify") && 4024 __traits(hasMember, T, "holeValue")); 4025 4026 /** Default key equality/equivalence predicate for the type `T`. 4027 */ 4028 template defaultKeyEqualPredOf(T) 4029 { 4030 static if (is(T == class)) 4031 // static assert(__traits(hasMember, T, "opEquals"), 4032 // "Type" ~ T.stringof ~ " doesn't have local opEquals() defined"); 4033 // enum defaultKeyEqualPredOf = "a && b && a.opEquals(b)"; 4034 enum defaultKeyEqualPredOf = "a is b"; 4035 // (const T a, const T b) => ((a !is null) && (b !is null) && a.opEquals(b)); 4036 else 4037 enum defaultKeyEqualPredOf = "a == b"; 4038 } 4039 4040 /// 4041 @safe pure nothrow unittest 4042 { 4043 class C 4044 { 4045 @safe pure nothrow @nogc: 4046 this(int x) 4047 { 4048 this.x = x; 4049 } 4050 @property bool opEquals(const scope typeof(this) rhs) const 4051 { 4052 return x == rhs.x; 4053 } 4054 @property override bool opEquals(const scope Object rhs) const @trusted 4055 { 4056 C rhs_ = cast(C)rhs; 4057 return rhs_ && x == rhs_.x; 4058 } 4059 int x; 4060 } 4061 static assert(defaultKeyEqualPredOf!(C) == "a is b"); 4062 }