1 /** Statically allocated arrays with compile-time known lengths. 2 */ 3 module nxt.fixed_array; 4 5 @safe pure: 6 7 /** Statically allocated `T`-array of fixed pre-allocated length. 8 * 9 * Similar to C++'s `std::array<T, Capacity>` 10 * Similar to Rust's `fixedvec`: https://docs.rs/fixedvec/0.2.4/fixedvec/ 11 * Similar to `mir.small_array` at http://mir-algorithm.libmir.org/mir_small_array.html 12 * 13 * See_Also: https://en.cppreference.com/w/cpp/container/array 14 * 15 * TODO: Merge member functions with basic_*_array.d and array_ex.d 16 * TODO: Add @safe nothrow @nogc ctor from static array (of known length) 17 * TODO: use opPostMove (https://github.com/dlang/DIPs/blob/master/DIPs/accepted/DIP1014.md) 18 */ 19 struct FixedArray(T, uint capacity_, bool borrowChecked = false) 20 { 21 // pragma(msg, "T:", T, " capacity_:", capacity_, " borrowChecked:", borrowChecked); 22 import core.exception : onRangeError; 23 import core.lifetime : move, moveEmplace; 24 import std.bitmanip : bitfields; 25 import std.traits : isSomeChar, isAssignable, hasIndirections; 26 import core.internal.traits : hasElaborateDestructor; 27 import nxt.container_traits : mustAddGCRange, needsMove; 28 29 alias capacity = capacity_; // for public use 30 31 /// Store of `capacity` number of elements. 32 T[capacity] _store; // TODO: use store constructor 33 34 static if (borrowChecked) 35 { 36 /// Number of bits needed to store number of read borrows. 37 private enum readBorrowCountBits = 3; 38 39 /// Maximum value possible for `_readBorrowCount`. 40 enum readBorrowCountMax = 2^^readBorrowCountBits - 1; 41 42 static if (capacity <= 2^^(8*ubyte.sizeof - 1 - readBorrowCountBits) - 1) 43 { 44 private enum lengthMax = 2^^4 - 1; 45 alias Length = ubyte; 46 // TODO: make private: 47 mixin(bitfields!(Length, "_length", 4, /// number of defined elements in `_store` 48 bool, "_writeBorrowed", 1, 49 uint, "_readBorrowCount", readBorrowCountBits, 50 )); 51 } 52 else static if (capacity <= 2^^(8*ushort.sizeof - 1 - readBorrowCountBits) - 1) 53 { 54 alias Length = ushort; 55 private enum lengthMax = 2^^14 - 1; 56 // TODO: make private: 57 mixin(bitfields!(Length, "_length", 14, /// number of defined elements in `_store` 58 bool, "_writeBorrowed", 1, 59 uint, "_readBorrowCount", readBorrowCountBits, 60 )); 61 } 62 else 63 { 64 static assert("Too large requested capacity " ~ capacity); 65 } 66 } 67 else 68 { 69 static if (capacity <= ubyte.max) 70 { 71 static if (T.sizeof == 1) 72 alias Length = ubyte; // pack length 73 else 74 alias Length = uint; 75 } 76 else static if (capacity <= ushort.max) 77 { 78 static if (T.sizeof <= 2) 79 alias Length = uint; // pack length 80 else 81 alias Length = uint; 82 } 83 else 84 { 85 static assert("Too large requested capacity " ~ capacity); 86 } 87 Length _length; /// number of defined elements in `_store` 88 } 89 90 /// Is `true` iff `U` can be assign to the element type `T` of `this`. 91 private enum isElementAssignable(U) = isAssignable!(T, U); 92 93 /// Empty. 94 void clear() @nogc 95 { 96 releaseElementsStore(); 97 resetInternalData(); 98 } 99 100 /// Release elements and internal store. 101 private void releaseElementsStore() @trusted @nogc 102 { 103 static if (borrowChecked) { assert(!isBorrowed); } 104 foreach (immutable i; 0 .. length) 105 static if (hasElaborateDestructor!T) 106 .destroy(_store.ptr[i]); 107 else static if (hasIndirections!T) 108 _store.ptr[i] = T.init; // nullify any pointers 109 } 110 111 /// Reset internal data. 112 private void resetInternalData() @nogc 113 { 114 version(D_Coverage) {} else pragma(inline, true); 115 _length = 0; 116 } 117 118 /// Construct from element `values`. 119 this(Us...)(Us values) @trusted 120 if (Us.length <= capacity) 121 { 122 static foreach (const i, value; values) 123 static if (needsMove!(typeof(value))) 124 moveEmplace(value, _store[i]); 125 else 126 _store[i] = value; 127 _length = cast(Length)values.length; 128 static if (borrowChecked) 129 { 130 _writeBorrowed = false; 131 _readBorrowCount = 0; 132 } 133 } 134 135 /// Construct from element `values`. 136 this(U)(U[] values) @trusted 137 if (__traits(isCopyable, U)// && 138 // TODO: isElementAssignable!U 139 ) // prevent accidental move of l-value `values` in array calls 140 { 141 version(assert) if (values.length > capacity) onRangeError(); // `Arguments don't fit in array` 142 _store[0 .. values.length] = values; 143 _length = cast(Length)values.length; 144 static if (borrowChecked) 145 { 146 _writeBorrowed = false; 147 _readBorrowCount = 0; 148 } 149 } 150 151 /// Construct from element `values`. 152 static typeof(this) fromValuesUnsafe(U)(U[] values) @system 153 if (__traits(isCopyable, U) && 154 isElementAssignable!U 155 ) // prevent accidental move of l-value `values` in array calls 156 { 157 typeof(return) that; // TODO: use Store constructor: 158 159 that._store[0 .. values.length] = values; 160 that._length = cast(Length)values.length; 161 162 static if (borrowChecked) 163 { 164 that._writeBorrowed = false; 165 that._readBorrowCount = 0; 166 } 167 168 return that; 169 } 170 171 static if (borrowChecked || 172 hasElaborateDestructor!T) 173 { 174 /** Destruct. */ 175 ~this() @nogc 176 { 177 releaseElementsStore(); 178 } 179 } 180 181 /** Add elements `es` to the back. 182 * Throws when array becomes full. 183 * NOTE: doesn't invalidate any borrow 184 */ 185 void insertBack(Es...)(Es es) @trusted 186 if (Es.length <= capacity) // TODO: use `isAssignable` 187 { 188 version(assert) if (_length + Es.length > capacity) onRangeError(); // `Arguments don't fit in array` 189 static foreach (const i, e; es) 190 { 191 static if (needsMove!T) 192 moveEmplace(e, _store[_length + i]); // TODO: remove `move` when compiler does it for us 193 else 194 _store[_length + i] = e; 195 } 196 _length = cast(Length)(_length + Es.length); // TODO: better? 197 } 198 /// ditto 199 alias put = insertBack; // `OutputRange` support 200 201 /** Try to add elements `es` to the back. 202 * NOTE: doesn't invalidate any borrow 203 * Returns: `true` iff all `es` were pushed, `false` otherwise. 204 */ 205 bool insertBackMaybe(Es...)(Es es) @trusted 206 if (Es.length <= capacity) // TODO: use `isAssignable` 207 { 208 version(LDC) pragma(inline, true); 209 if (_length + Es.length > capacity) { return false; } 210 insertBack(es); 211 return true; 212 } 213 /// ditto 214 alias putMaybe = insertBackMaybe; 215 216 /** Add elements `es` to the back. 217 * NOTE: doesn't invalidate any borrow 218 */ 219 void opOpAssign(string op, Us...)(Us values) 220 if (op == "~" && 221 values.length >= 1 && 222 allSatisfy!(isElementAssignable, Us)) 223 { 224 insertBack(values.move()); // TODO: remove `move` when compiler does it for 225 } 226 227 import std.traits : isMutable; 228 static if (isMutable!T) 229 { 230 /** Pop first (front) element. */ 231 auto ref popFront() 232 { 233 assert(!empty); 234 static if (borrowChecked) { assert(!isBorrowed); } 235 // TODO: is there a reusable Phobos function for this? 236 foreach (immutable i; 0 .. _length - 1) 237 move(_store[i + 1], _store[i]); // like `_store[i] = _store[i + 1];` but more generic 238 _length = cast(typeof(_length))(_length - 1); // TODO: better? 239 return this; 240 } 241 } 242 243 /** Pop last (back) element. */ 244 void popBack()() @trusted // template-lazy 245 { 246 assert(!empty); 247 static if (borrowChecked) { assert(!isBorrowed); } 248 _length = cast(Length)(_length - 1); // TODO: better? 249 static if (hasElaborateDestructor!T) 250 .destroy(_store.ptr[_length]); 251 else static if (mustAddGCRange!T) 252 _store.ptr[_length] = T.init; // avoid GC mark-phase dereference 253 } 254 255 T backPop()() @trusted // template-lazy 256 { 257 assert(!empty); 258 _length -= 1; 259 static if (needsMove!T) 260 return move(_store.ptr[length]); // move is indeed need here 261 else 262 return _store.ptr[length]; // no move needed 263 } 264 265 /** Pop the `n` last (back) elements. */ 266 void popBackN()(size_t n) @trusted // template-lazy 267 { 268 assert(length >= n); 269 static if (borrowChecked) { assert(!isBorrowed); } 270 _length = cast(Length)(_length - n); // TODO: better? 271 static if (hasElaborateDestructor!T) 272 foreach (const i; 0 .. n) 273 .destroy(_store.ptr[_length + i]); 274 else static if (mustAddGCRange!T) // avoid GC mark-phase dereference 275 foreach (const i; 0 .. n) 276 _store.ptr[_length + i] = T.init; 277 } 278 279 /** Move element at `index` to return. */ 280 static if (isMutable!T) 281 { 282 /** Pop element at `index`. */ 283 void popAt()(size_t index) // template-lazy 284 @trusted 285 @("complexity", "O(length)") 286 { 287 assert(index < this.length); 288 .destroy(_store.ptr[index]); 289 shiftToFrontAt(index); 290 _length = cast(Length)(_length - 1); 291 } 292 293 T moveAt()(size_t index) // template-lazy 294 @trusted 295 @("complexity", "O(length)") 296 { 297 assert(index < this.length); 298 auto value = _store.ptr[index].move(); 299 shiftToFrontAt(index); 300 _length = cast(Length)(_length - 1); 301 return value; 302 } 303 304 private void shiftToFrontAt()(size_t index) // template-lazy 305 @trusted 306 { 307 foreach (immutable i; 0 .. this.length - (index + 1)) 308 { 309 immutable si = index + i + 1; // source index 310 immutable ti = index + i; // target index 311 moveEmplace(_store.ptr[si], 312 _store.ptr[ti]); 313 } 314 } 315 } 316 317 /** Index operator. */ 318 ref inout(T) opIndex(size_t i) inout return 319 { 320 pragma(inline, true); 321 return _store[i]; 322 } 323 324 /** First (front) element. */ 325 ref inout(T) front() inout return 326 { 327 pragma(inline, true); 328 return _store[0]; 329 } 330 331 /** Last (back) element. */ 332 ref inout(T) back() inout return 333 { 334 pragma(inline, true); 335 return _store[_length - 1]; 336 } 337 338 static if (borrowChecked) 339 { 340 import nxt.borrowed : ReadBorrowed, WriteBorrowed; 341 342 /// Get read-only slice in range `i` .. `j`. 343 auto opSlice(size_t i, size_t j) const return scope 344 { 345 pragma(inline, true); 346 return sliceRO(i, j); 347 } 348 /// Get read-write slice in range `i` .. `j`. 349 auto opSlice(size_t i, size_t j) return scope 350 { 351 pragma(inline, true); 352 return sliceRW(i, j); 353 } 354 355 /// Get read-only full slice. 356 auto opSlice() const return scope 357 { 358 pragma(inline, true); 359 return sliceRO(); 360 } 361 /// Get read-write full slice. 362 auto opSlice() return scope 363 { 364 pragma(inline, true); 365 return sliceRW(); 366 } 367 368 /// Get full read-only slice. 369 ReadBorrowed!(T[], typeof(this)) sliceRO() const @trusted return scope 370 { 371 import core.internal.traits : Unqual; 372 assert(!_writeBorrowed, "Already write-borrowed"); 373 return typeof(return)(_store[0 .. _length], 374 cast(Unqual!(typeof(this))*)(&this)); // trusted unconst cast 375 } 376 377 /// Get read-only slice in range `i` .. `j`. 378 ReadBorrowed!(T[], typeof(this)) sliceRO(size_t i, size_t j) const @trusted return scope 379 { 380 import core.internal.traits : Unqual; 381 assert(!_writeBorrowed, "Already write-borrowed"); 382 return typeof(return)(_store[i .. j], 383 cast(Unqual!(typeof(this))*)(&this)); // trusted unconst cast 384 } 385 386 /// Get full read-write slice. 387 WriteBorrowed!(T[], typeof(this)) sliceRW() @trusted return scope // TODO: remove @trusted? 388 { 389 assert(!_writeBorrowed, "Already write-borrowed"); 390 assert(_readBorrowCount == 0, "Already read-borrowed"); 391 return typeof(return)(_store[0 .. _length], &this); 392 } 393 394 /// Get read-write slice in range `i` .. `j`. 395 WriteBorrowed!(T[], typeof(this)) sliceRW(size_t i, size_t j) @trusted return scope // TODO: remove @trusted? 396 { 397 assert(!_writeBorrowed, "Already write-borrowed"); 398 assert(_readBorrowCount == 0, "Already read-borrowed"); 399 return typeof(return)(_store[0 .. j], &this); 400 } 401 402 @property pragma(inline, true) 403 { 404 /// Returns: `true` iff `this` is either write or read borrowed. 405 bool isBorrowed() const { return _writeBorrowed || _readBorrowCount >= 1; } 406 407 /// Returns: `true` iff `this` is write borrowed. 408 bool isWriteBorrowed() const { return _writeBorrowed; } 409 410 /// Returns: number of read-only borrowers of `this`. 411 uint readBorrowCount() const { return _readBorrowCount; } 412 } 413 } 414 else 415 { 416 /// Get slice in range `i` .. `j`. 417 inout(T)[] opSlice(size_t i, size_t j) inout @trusted return // TODO: remove @trusted? 418 { 419 pragma(inline, true); 420 // assert(i <= j); 421 // assert(j <= _length); 422 return _store[i .. j]; 423 } 424 425 /// Get full slice. 426 inout(T)[] opSlice() inout @trusted return // TODO: remove @trusted? 427 { 428 pragma(inline, true); 429 return _store[0 .. _length]; 430 } 431 } 432 433 @property pragma(inline, true) 434 { 435 /** Returns: `true` iff `this` is empty, `false` otherwise. */ 436 bool empty() const { return _length == 0; } 437 438 /** Returns: `true` iff `this` is full, `false` otherwise. */ 439 bool full() const { return _length == capacity; } 440 441 /** Get length. */ 442 auto length() const { return _length; } 443 alias opDollar = length; /// ditto 444 445 static if (isSomeChar!T) 446 { 447 /** Get as `string`. */ 448 scope const(T)[] toString() const return 449 { 450 version(DigitalMars) pragma(inline, false); 451 return opSlice(); 452 } 453 } 454 } 455 456 /** Comparison for equality. */ 457 bool opEquals()(const scope auto ref typeof(this) rhs) const 458 { 459 return this[] == rhs[]; 460 } 461 /// ditto 462 bool opEquals(U)(const scope U[] rhs) const 463 if (is(typeof(T[].init == U[].init))) 464 { 465 return this[] == rhs; 466 } 467 } 468 469 /** Stack-allocated string of maximum length of `capacity.` 470 * 471 * Similar to `mir.small_string` at http://mir-algorithm.libmir.org/mir_small_string.html. 472 */ 473 alias StringN(uint capacity, bool borrowChecked = false) = FixedArray!(immutable(char), capacity, borrowChecked); 474 475 /** Stack-allocated wstring of maximum length of `capacity.` */ 476 alias WStringN(uint capacity, bool borrowChecked = false) = FixedArray!(immutable(wchar), capacity, borrowChecked); 477 478 /** Stack-allocated dstring of maximum length of `capacity.` */ 479 alias DStringN(uint capacity, bool borrowChecked = false) = FixedArray!(immutable(dchar), capacity, borrowChecked); 480 481 /** Stack-allocated mutable string of maximum length of `capacity.` */ 482 alias MutableStringN(uint capacity, bool borrowChecked = false) = FixedArray!(char, capacity, borrowChecked); 483 484 /** Stack-allocated mutable wstring of maximum length of `capacity.` */ 485 alias MutableWStringN(uint capacity, bool borrowChecked = false) = FixedArray!(char, capacity, borrowChecked); 486 487 /** Stack-allocated mutable dstring of maximum length of `capacity.` */ 488 alias MutableDStringN(uint capacity, bool borrowChecked = false) = FixedArray!(char, capacity, borrowChecked); 489 490 /// construct from array may throw 491 @safe pure unittest 492 { 493 enum capacity = 3; 494 alias T = int; 495 alias A = FixedArray!(T, capacity); 496 static assert(!mustAddGCRange!A); 497 498 auto a = A([1, 2, 3].s[]); 499 assert(a[] == [1, 2, 3].s); 500 } 501 502 /// unsafe construct from array 503 @trusted pure nothrow @nogc unittest 504 { 505 enum capacity = 3; 506 alias T = int; 507 alias A = FixedArray!(T, capacity); 508 static assert(!mustAddGCRange!A); 509 510 auto a = A.fromValuesUnsafe([1, 2, 3].s); 511 assert(a[] == [1, 2, 3].s); 512 } 513 514 /// construct from scalars is nothrow 515 @safe pure nothrow @nogc unittest 516 { 517 enum capacity = 3; 518 alias T = int; 519 alias A = FixedArray!(T, capacity); 520 static assert(!mustAddGCRange!A); 521 522 auto a = A(1, 2, 3); 523 assert(a[] == [1, 2, 3].s); 524 525 static assert(!__traits(compiles, { auto _ = A(1, 2, 3, 4); })); 526 } 527 528 /// scope checked string 529 @safe pure unittest 530 { 531 enum capacity = 15; 532 foreach (StrN; AliasSeq!(StringN// , WStringN, DStringN 533 )) 534 { 535 alias String15 = StrN!(capacity); 536 537 typeof(String15.init[0])[] xs; 538 assert(xs.length == 0); 539 auto x = String15("alphas"); 540 541 assert(x[0] == 'a'); 542 assert(x[$ - 1] == 's'); 543 544 assert(x[0 .. 2] == "al"); 545 assert(x[] == "alphas"); 546 547 const y = String15("åäö_åäöå"); // fits in 15 chars 548 assert(y.length == capacity); 549 } 550 } 551 552 /// scope checked string 553 pure unittest 554 { 555 enum capacity = 15; 556 foreach (Str; AliasSeq!(StringN!capacity, 557 WStringN!capacity, 558 DStringN!capacity)) 559 { 560 static assert(!mustAddGCRange!Str); 561 static if (isDIP1000) 562 { 563 static assert(!__traits(compiles, { 564 auto f() @safe pure 565 { 566 auto x = Str("alphas"); 567 auto y = x[]; 568 return y; // errors with -dip1000 569 } 570 })); 571 } 572 } 573 } 574 575 @safe pure unittest 576 { 577 static assert(mustAddGCRange!(FixedArray!(string, 1, false))); 578 static assert(mustAddGCRange!(FixedArray!(string, 1, true))); 579 static assert(mustAddGCRange!(FixedArray!(string, 2, false))); 580 static assert(mustAddGCRange!(FixedArray!(string, 2, true))); 581 } 582 583 /// 584 @safe pure unittest 585 { 586 import std.exception : assertNotThrown; 587 588 alias T = char; 589 enum capacity = 3; 590 591 alias A = FixedArray!(T, capacity, true); 592 static assert(!mustAddGCRange!A); 593 static assert(A.sizeof == T.sizeof*capacity + 1); 594 595 import std.range.primitives : isOutputRange; 596 static assert(isOutputRange!(A, T)); 597 598 auto ab = A("ab"); 599 assert(!ab.empty); 600 assert(ab[0] == 'a'); 601 assert(ab.front == 'a'); 602 assert(ab.back == 'b'); 603 assert(ab.length == 2); 604 assert(ab[] == "ab"); 605 assert(ab[0 .. 1] == "a"); 606 assertNotThrown(ab.insertBack('_')); 607 assert(ab[] == "ab_"); 608 ab.popBack(); 609 assert(ab[] == "ab"); 610 assert(ab.toString == "ab"); 611 612 ab.popBackN(2); 613 assert(ab.empty); 614 assertNotThrown(ab.insertBack('a', 'b')); 615 616 const abc = A("abc"); 617 assert(!abc.empty); 618 assert(abc.front == 'a'); 619 assert(abc.back == 'c'); 620 assert(abc.length == 3); 621 assert(abc[] == "abc"); 622 assert(ab[0 .. 2] == "ab"); 623 assert(abc.full); 624 static assert(!__traits(compiles, { const abcd = A('a', 'b', 'c', 'd'); })); // too many elements 625 626 assert(ab[] == "ab"); 627 ab.popFront(); 628 assert(ab[] == "b"); 629 630 const xy = A("xy"); 631 assert(!xy.empty); 632 assert(xy[0] == 'x'); 633 assert(xy.front == 'x'); 634 assert(xy.back == 'y'); 635 assert(xy.length == 2); 636 assert(xy[] == "xy"); 637 assert(xy[0 .. 1] == "x"); 638 639 const xyz = A("xyz"); 640 assert(!xyz.empty); 641 assert(xyz.front == 'x'); 642 assert(xyz.back == 'z'); 643 assert(xyz.length == 3); 644 assert(xyz[] == "xyz"); 645 assert(xyz.full); 646 static assert(!__traits(compiles, { const xyzw = A('x', 'y', 'z', 'w'); })); // too many elements 647 } 648 649 /// 650 @safe pure unittest 651 { 652 static void testAsSomeString(T)() 653 { 654 enum capacity = 15; 655 alias A = FixedArray!(immutable(T), capacity); 656 static assert(!mustAddGCRange!A); 657 auto a = A("abc"); 658 assert(a[] == "abc"); 659 660 import std.conv : to; 661 const x = "a".to!(T[]); 662 } 663 664 foreach (T; AliasSeq!(char// , wchar, dchar 665 )) 666 { 667 testAsSomeString!T(); 668 } 669 } 670 671 /// equality 672 @safe pure unittest 673 { 674 enum capacity = 15; 675 alias S = FixedArray!(int, capacity); 676 static assert(!mustAddGCRange!S); 677 678 assert(S([1, 2, 3].s[]) == 679 S([1, 2, 3].s[])); 680 assert(S([1, 2, 3].s[]) == 681 [1, 2, 3]); 682 } 683 684 @safe pure unittest 685 { 686 class C { int value; } 687 alias S = FixedArray!(C, 2); 688 static assert(mustAddGCRange!S); 689 } 690 691 /// `insertBackMaybe` is nothrow @nogc. 692 @safe pure nothrow @nogc unittest 693 { 694 alias S = FixedArray!(int, 2); 695 S s; 696 assert(s.insertBackMaybe(42)); 697 assert(s.insertBackMaybe(43)); 698 assert(!s.insertBackMaybe(0)); 699 assert(s.length == 2); 700 } 701 702 /// equality 703 @system pure nothrow @nogc unittest 704 { 705 enum capacity = 15; 706 alias S = FixedArray!(int, capacity); 707 708 assert(S.fromValuesUnsafe([1, 2, 3].s) == 709 S.fromValuesUnsafe([1, 2, 3].s)); 710 711 const ax = [1, 2, 3].s; 712 assert(S.fromValuesUnsafe([1, 2, 3].s) == ax); 713 assert(S.fromValuesUnsafe([1, 2, 3].s) == ax[]); 714 715 const cx = [1, 2, 3].s; 716 assert(S.fromValuesUnsafe([1, 2, 3].s) == cx); 717 assert(S.fromValuesUnsafe([1, 2, 3].s) == cx[]); 718 719 immutable ix = [1, 2, 3].s; 720 assert(S.fromValuesUnsafe([1, 2, 3].s) == ix); 721 assert(S.fromValuesUnsafe([1, 2, 3].s) == ix[]); 722 } 723 724 /// assignment from `const` to `immutable` element type 725 @safe pure unittest 726 { 727 enum capacity = 15; 728 alias String15 = StringN!(capacity); 729 static assert(!mustAddGCRange!String15); 730 731 const char[4] _ = ['a', 'b', 'c', 'd']; 732 auto x = String15(_[]); 733 assert(x.length == 4); 734 assert(x[] == "abcd"); 735 } 736 737 /// borrow checking 738 @system pure unittest 739 { 740 enum capacity = 15; 741 alias String15 = StringN!(capacity, true); 742 static assert(String15.readBorrowCountMax == 7); 743 static assert(!mustAddGCRange!String15); 744 745 auto x = String15("alpha"); 746 747 assert(x[] == "alpha"); 748 749 { 750 auto xw1 = x[]; 751 assert(x.isWriteBorrowed); 752 assert(x.isBorrowed); 753 } 754 755 auto xr1 = (cast(const)x)[]; 756 assert(x.readBorrowCount == 1); 757 758 auto xr2 = (cast(const)x)[]; 759 assert(x.readBorrowCount == 2); 760 761 auto xr3 = (cast(const)x)[]; 762 assert(x.readBorrowCount == 3); 763 764 auto xr4 = (cast(const)x)[]; 765 assert(x.readBorrowCount == 4); 766 767 auto xr5 = (cast(const)x)[]; 768 assert(x.readBorrowCount == 5); 769 770 auto xr6 = (cast(const)x)[]; 771 assert(x.readBorrowCount == 6); 772 773 auto xr7 = (cast(const)x)[]; 774 assert(x.readBorrowCount == 7); 775 776 assertThrown!AssertError((cast(const)x)[]); 777 } 778 779 version(unittest) 780 { 781 import std.meta : AliasSeq; 782 import std.exception : assertThrown; 783 import core.exception : AssertError; 784 785 import nxt.array_help : s; 786 import nxt.container_traits : mustAddGCRange; 787 import nxt.dip_traits : isDIP1000; 788 }