The OpenD Programming Language

1 /++
2 This is a submodule of $(MREF mir, ndslice).
3 
4 Safety_note:
5     User-defined iterators should care about their safety except bounds checks.
6     Bounds are checked in ndslice code.
7 
8 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
9 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments
10 Authors: Ilia Ki
11 
12 $(BOOKTABLE $(H2 Definitions),
13 $(TR $(TH Name) $(TH Description))
14 $(T2 Slice, N-dimensional slice.)
15 $(T2 SliceKind, SliceKind of $(LREF Slice) enumeration.)
16 $(T2 Universal, Alias for $(LREF .SliceKind.universal).)
17 $(T2 Canonical, Alias for $(LREF .SliceKind.canonical).)
18 $(T2 Contiguous, Alias for $(LREF .SliceKind.contiguous).)
19 $(T2 sliced, Creates a slice on top of an iterator, a pointer, or an array's pointer.)
20 $(T2 slicedField, Creates a slice on top of a field, a random access range, or an array.)
21 $(T2 slicedNdField, Creates a slice on top of an ndField.)
22 $(T2 kindOf, Extracts $(LREF SliceKind).)
23 $(T2 isSlice, Checks if the type is `Slice` instance.)
24 $(T2 Structure, A tuple of lengths and strides.)
25 )
26 
27 Macros:
28 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP)
29 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
30 T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
31 STD = $(TD $(SMALL $0))
32 +/
33 module mir.ndslice.slice;
34 
35 import mir.internal.utility : Iota;
36 import mir.math.common : fmamath;
37 import mir.ndslice.concatenation;
38 import mir.ndslice.field;
39 import mir.ndslice.internal;
40 import mir.ndslice.iterator;
41 import mir.ndslice.traits: isIterator;
42 import mir.primitives;
43 import mir.qualifier;
44 import mir.utility;
45 import std.meta;
46 import std.traits;
47 
48 ///
49 public import mir.primitives: DeepElementType;
50 
51 /++
52 Checks if type T has asSlice property and its returns a slices.
53 Aliases itself to a dimension count
54 +/
55 template hasAsSlice(T)
56 {
57     static if (__traits(hasMember, T, "asSlice"))
58         enum size_t hasAsSlice = typeof(T.init.asSlice).N;
59     else
60         enum size_t hasAsSlice = 0;
61 }
62 
63 ///
64 version(mir_ndslice_test) unittest
65 {
66     import mir.series;
67     static assert(!hasAsSlice!(int[]));
68     static assert(hasAsSlice!(SeriesMap!(int, string)) == 1);
69 }
70 
71 /++
72 Check if $(LREF toConst) function can be called with type T.
73 +/
74 enum isConvertibleToSlice(T) = isSlice!T || isDynamicArray!T || hasAsSlice!T;
75 
76 ///
77 version(mir_ndslice_test) unittest
78 {
79     import mir.series: SeriesMap;
80     static assert(isConvertibleToSlice!(immutable int[]));
81     static assert(isConvertibleToSlice!(string[]));
82     static assert(isConvertibleToSlice!(SeriesMap!(string, int)));
83     static assert(isConvertibleToSlice!(Slice!(int*)));
84 }
85 
86 /++
87 Reurns:
88     Ndslice view in the same data.
89 See_also: $(LREF isConvertibleToSlice).
90 +/
91 auto toSlice(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) val)
92 {
93     import core.lifetime: move;
94     return val.move;
95 }
96 
97 /// ditto
98 auto toSlice(Iterator, size_t N, SliceKind kind)(const Slice!(Iterator, N, kind) val)
99 {
100     return val[];
101 }
102 
103 /// ditto
104 auto toSlice(Iterator, size_t N, SliceKind kind)(immutable Slice!(Iterator, N, kind) val)
105 {
106     return val[];
107 }
108 
109 /// ditto
110 auto toSlice(T)(T[] val)
111 {
112     return val.sliced;
113 }
114 
115 /// ditto
116 auto toSlice(T)(T val)
117     if (hasAsSlice!T || __traits(hasMember, T, "moveToSlice"))
118 {
119     static if (__traits(hasMember, T, "moveToSlice"))
120         return val.moveToSlice;
121     else
122         return val.asSlice;
123 }
124 
125 /// ditto
126 auto toSlice(T)(ref T val)
127     if (hasAsSlice!T)
128 {
129     return val.asSlice;
130 }
131 
132 ///
133 template toSlices(args...)
134 {
135     static if (args.length)
136     {
137         alias arg = args[0];
138         alias Arg = typeof(arg);
139         static if (isMutable!Arg && isSlice!Arg)
140             alias slc = arg;
141         else
142         @fmamath @property auto ref slc()()
143         {
144             return toSlice(arg);
145         }
146         alias toSlices = AliasSeq!(slc, toSlices!(args[1..$]));
147     }
148     else
149         alias toSlices = AliasSeq!();
150 }
151 
152 /++
153 Checks if the type is `Slice` instance.
154 +/
155 enum isSlice(T) = is(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind);
156 
157 ///
158 @safe pure nothrow @nogc
159 version(mir_ndslice_test) unittest
160 {
161     alias A = uint[];
162     alias S = Slice!(int*);
163 
164     static assert(isSlice!S);
165     static assert(!isSlice!A);
166 }
167 
168 /++
169 SliceKind of $(LREF Slice).
170 See_also:
171     $(SUBREF topology, universal),
172     $(SUBREF topology, canonical),
173     $(SUBREF topology, assumeCanonical),
174     $(SUBREF topology, assumeContiguous).
175 +/
176 enum mir_slice_kind
177 {
178     /// A slice has strides for all dimensions.
179     universal,
180     /// A slice has >=2 dimensions and row dimension is contiguous.
181     canonical,
182     /// A slice is a flat contiguous data without strides.
183     contiguous,
184 }
185 /// ditto
186 alias SliceKind = mir_slice_kind;
187 
188 /++
189 Alias for $(LREF .SliceKind.universal).
190 
191 See_also:
192     Internal Binary Representation section in $(LREF Slice).
193 +/
194 alias Universal = SliceKind.universal;
195 /++
196 Alias for $(LREF .SliceKind.canonical).
197 
198 See_also:
199     Internal Binary Representation section in $(LREF Slice).
200 +/
201 alias Canonical = SliceKind.canonical;
202 /++
203 Alias for $(LREF .SliceKind.contiguous).
204 
205 See_also:
206     Internal Binary Representation section in $(LREF Slice).
207 +/
208 alias Contiguous = SliceKind.contiguous;
209 
210 /// Extracts $(LREF SliceKind).
211 enum kindOf(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind) = kind;
212 
213 ///
214 @safe pure nothrow @nogc
215 version(mir_ndslice_test) unittest
216 {
217     static assert(kindOf!(Slice!(int*, 1, Universal)) == Universal);
218 }
219 
220 /// Extracts iterator type from a $(LREF Slice).
221 alias IteratorOf(T : Slice!(Iterator, N, kind), Iterator, size_t N, SliceKind kind) = Iterator;
222 
223 private template SkipDimension(size_t dimension, size_t index)
224 {
225     static if (index < dimension)
226          enum SkipDimension = index;
227     else
228     static if (index == dimension)
229         static assert (0, "SkipInex: wrong index");
230     else
231          enum SkipDimension = index - 1;
232 }
233 
234 /++
235 Creates an n-dimensional slice-shell over an iterator.
236 Params:
237     iterator = An iterator, a pointer, or an array.
238     lengths = A list of lengths for each dimension
239 Returns:
240     n-dimensional slice
241 +/
242 auto sliced(size_t N, Iterator)(return scope Iterator iterator, size_t[N] lengths...)
243     if (!__traits(isStaticArray, Iterator) && N
244         && !is(Iterator : Slice!(_Iterator, _N, kind), _Iterator, size_t _N, SliceKind kind))
245 {
246     alias C = ImplicitlyUnqual!(typeof(iterator));
247     size_t[N] _lengths;
248     foreach (i; Iota!N)
249         _lengths[i] = lengths[i];
250     ptrdiff_t[1] _strides = 0;
251     static if (isDynamicArray!Iterator)
252     {
253         assert(lengthsProduct(_lengths) <= iterator.length,
254             "array length should be greater or equal to the product of constructed ndslice lengths");
255         auto ptr = iterator.length ? &iterator[0] : null;
256         return Slice!(typeof(C.init[0])*, N)(_lengths, ptr);
257     }
258     else
259     {
260         // break safety
261         if (false)
262         {
263             ++iterator;
264             --iterator;
265             iterator += 34;
266             iterator -= 34;
267         }
268         import core.lifetime: move;
269         return Slice!(C, N)(_lengths, iterator.move);
270     }
271 }
272 
273 /// Random access range primitives for slices over user defined types
274 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
275 {
276     struct MyIota
277     {
278         //`[index]` operator overloading
279         auto opIndex(size_t index) @safe nothrow
280         {
281             return index;
282         }
283 
284         auto lightConst()() const @property { return MyIota(); }
285         auto lightImmutable()() immutable @property { return MyIota(); }
286     }
287 
288     import mir.ndslice.iterator: FieldIterator;
289     alias Iterator = FieldIterator!MyIota;
290     alias S = Slice!(Iterator, 2);
291     import std.range.primitives;
292     static assert(hasLength!S);
293     static assert(hasSlicing!S);
294     static assert(isRandomAccessRange!S);
295 
296     auto slice = Iterator().sliced(20, 10);
297     assert(slice[1, 2] == 12);
298     auto sCopy = slice.save;
299     assert(slice[1, 2] == 12);
300 }
301 
302 /++
303 Creates an 1-dimensional slice-shell over an array.
304 Params:
305     array = An array.
306 Returns:
307     1-dimensional slice
308 +/
309 Slice!(T*) sliced(T)(T[] array) @trusted
310 {
311     version(LDC) pragma(inline, true);
312     return Slice!(T*)([array.length], array.ptr);
313 }
314 
315 /// Creates a slice from an array.
316 @safe pure nothrow version(mir_ndslice_test) unittest
317 {
318     auto slice = new int[10].sliced;
319     assert(slice.length == 10);
320     static assert(is(typeof(slice) == Slice!(int*)));
321 }
322 
323 /++
324 Creates an n-dimensional slice-shell over the 1-dimensional input slice.
325 Params:
326     slice = slice
327     lengths = A list of lengths for each dimension.
328 Returns:
329     n-dimensional slice
330 +/
331 Slice!(Iterator, N, kind)
332     sliced
333     (Iterator, size_t N, SliceKind kind)
334     (Slice!(Iterator, 1, kind) slice, size_t[N] lengths...)
335     if (N)
336 {
337     auto structure = typeof(return)._Structure.init;
338     structure[0] = lengths;
339     static if (kind != Contiguous)
340     {
341         import mir.ndslice.topology: iota;
342         structure[1] = structure[0].iota.strides;
343     }
344     import core.lifetime: move;
345     return typeof(return)(structure, slice._iterator.move);
346 }
347 
348 ///
349 @safe pure nothrow version(mir_ndslice_test) unittest
350 {
351     import mir.ndslice.topology : iota;
352     auto data = new int[24];
353     foreach (i, ref e; data)
354         e = cast(int)i;
355     auto a = data[0..10].sliced(10)[0..6].sliced(2, 3);
356     auto b = iota!int(10)[0..6].sliced(2, 3);
357     assert(a == b);
358     a[] += b;
359     foreach (i, e; data[0..6])
360         assert(e == 2*i);
361     foreach (i, e; data[6..$])
362         assert(e == i+6);
363 }
364 
365 /++
366 Creates an n-dimensional slice-shell over a field.
367 Params:
368     field = A field. The length of the
369         array should be equal to or less then the product of
370         lengths.
371     lengths = A list of lengths for each dimension.
372 Returns:
373     n-dimensional slice
374 +/
375 Slice!(FieldIterator!Field, N)
376 slicedField(Field, size_t N)(Field field, size_t[N] lengths...)
377     if (N)
378 {
379     static if (hasLength!Field)
380         assert(lengths.lengthsProduct <= field.length, "Length product should be less or equal to the field length.");
381     return FieldIterator!Field(0, field).sliced(lengths);
382 }
383 
384 ///ditto
385 auto slicedField(Field)(Field field)
386     if(hasLength!Field)
387 {
388     return .slicedField(field, field.length);
389 }
390 
391 /// Creates an 1-dimensional slice over a field, array, or random access range.
392 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
393 {
394     import mir.ndslice.topology : iota;
395     auto slice = 10.iota.slicedField;
396     assert(slice.length == 10);
397 }
398 
399 /++
400 Creates an n-dimensional slice-shell over an ndField.
401 Params:
402     field = A ndField. Lengths should fit into field's shape.
403     lengths = A list of lengths for each dimension.
404 Returns:
405     n-dimensional slice
406 See_also: $(SUBREF concatenation, concatenation) examples.
407 +/
408 Slice!(IndexIterator!(FieldIterator!(ndIotaField!N), ndField), N)
409 slicedNdField(ndField, size_t N)(ndField field, size_t[N] lengths...)
410     if (N)
411 {
412     static if(hasShape!ndField)
413     {
414         auto shape = field.shape;
415         foreach (i; 0 .. N)
416             assert(lengths[i] <= shape[i], "Lengths should fit into ndfield's shape.");
417     }
418     import mir.ndslice.topology: indexed, ndiota;
419     return indexed(field, ndiota(lengths));
420 }
421 
422 ///ditto
423 auto slicedNdField(ndField)(ndField field)
424     if(hasShape!ndField)
425 {
426     return .slicedNdField(field, field.shape);
427 }
428 
429 /++
430 Combination of coordinate(s) and value.
431 +/
432 struct CoordinateValue(T, size_t N = 1)
433 {
434     ///
435     size_t[N] index;
436 
437     ///
438     T value;
439 
440     ///
441     int opCmp()(scope auto ref const typeof(this) rht) const
442     {
443         return cmpCoo(this.index, rht.index);
444     }
445 }
446 
447 private int cmpCoo(size_t N)(scope const auto ref size_t[N] a, scope const auto ref size_t[N] b)
448 {
449     foreach (i; Iota!(0, N))
450         if (a[i] != b[i])
451             return a[i] > b[i] ? 1 : -1;
452     return 0;
453 }
454 
455 /++
456 Presents $(LREF .Slice.structure).
457 +/
458 struct Structure(size_t N)
459 {
460     ///
461     size_t[N] lengths;
462     ///
463     sizediff_t[N] strides;
464 }
465 
466 package(mir) alias LightConstOfLightScopeOf(Iterator) = LightConstOf!(LightScopeOf!Iterator);
467 package(mir) alias LightImmutableOfLightConstOf(Iterator) = LightImmutableOf!(LightScopeOf!Iterator);
468 package(mir) alias ImmutableOfUnqualOfPointerTarget(Iterator) = immutable(Unqual!(PointerTarget!Iterator))*;
469 package(mir) alias ConstOfUnqualOfPointerTarget(Iterator) = const(Unqual!(PointerTarget!Iterator))*;
470 
471 package(mir) template allLightScope(args...)
472 {
473     static if (args.length)
474     {
475         alias Arg = typeof(args[0]);
476         static if(!isDynamicArray!Arg)
477         {
478             static if(!is(LightScopeOf!Arg == Arg))
479             @fmamath @property auto allLightScopeMod()()
480             {
481                 import mir.qualifier: lightScope;
482                 return args[0].lightScope;
483             }
484             else alias allLightScopeMod = args[0];
485         }
486         else alias allLightScopeMod = args[0];
487         alias allLightScope = AliasSeq!(allLightScopeMod, allLightScope!(args[1..$]));
488     }
489     else
490         alias allLightScope = AliasSeq!();
491 }
492 
493 /++
494 Presents an n-dimensional view over a range.
495 
496 $(H3 Definitions)
497 
498 In order to change data in a slice using
499 overloaded operators such as `=`, `+=`, `++`,
500 a syntactic structure of type
501 `<slice to change>[<index and interval sequence...>]` must be used.
502 It is worth noting that just like for regular arrays, operations `a = b`
503 and `a[] = b` have different meanings.
504 In the first case, after the operation is carried out, `a` simply points at the same data as `b`
505 does, and the data which `a` previously pointed at remains unmodified.
506 Here, `а` and `b` must be of the same type.
507 In the second case, `a` points at the same data as before,
508 but the data itself will be changed. In this instance, the number of dimensions of `b`
509 may be less than the number of dimensions of `а`; and `b` can be a Slice,
510 a regular multidimensional array, or simply a value (e.g. a number).
511 
512 In the following table you will find the definitions you might come across
513 in comments on operator overloading.
514 
515 $(BOOKTABLE
516 $(TR $(TH Operator Overloading) $(TH Examples at `N == 3`))
517 $(TR $(TD An $(B interval) is a part of a sequence of type `i .. j`.)
518     $(STD `2..$-3`, `0..4`))
519 $(TR $(TD An $(B index) is a part of a sequence of type `i`.)
520     $(STD `3`, `$-1`))
521 $(TR $(TD A $(B partially defined slice) is a sequence composed of
522     $(B intervals) and $(B indices) with an overall length strictly less than `N`.)
523     $(STD `[3]`, `[0..$]`, `[3, 3]`, `[0..$,0..3]`, `[0..$,2]`))
524 $(TR $(TD A $(B fully defined index) is a sequence
525     composed only of $(B indices) with an overall length equal to `N`.)
526     $(STD `[2,3,1]`))
527 $(TR $(TD A $(B fully defined slice) is an empty sequence
528     or a sequence composed of $(B indices) and at least one
529     $(B interval) with an overall length equal to `N`.)
530     $(STD `[]`, `[3..$,0..3,0..$-1]`, `[2,0..$,1]`))
531 $(TR $(TD An $(B indexed slice) is syntax sugar for $(SUBREF topology, indexed) and $(SUBREF topology, cartesian).)
532     $(STD `[anNdslice]`, `[$.iota, anNdsliceForCartesian1, $.iota]`))
533 )
534 
535 See_also:
536     $(SUBREF topology, iota).
537 
538 $(H3 Internal Binary Representation)
539 
540 Multidimensional Slice is a structure that consists of lengths, strides, and a iterator (pointer).
541 
542 $(SUBREF topology, FieldIterator) shell is used to wrap fields and random access ranges.
543 FieldIterator contains a shift of the current initial element of a multidimensional slice
544 and the field itself.
545 
546 With the exception of $(MREF mir,ndslice,allocation) module, no functions in this
547 package move or copy data. The operations are only carried out on lengths, strides,
548 and pointers. If a slice is defined over a range, only the shift of the initial element
549 changes instead of the range.
550 
551 Mir n-dimensional Slices can be one of the three kinds.
552 
553 $(H4 Contiguous slice)
554 
555 Contiguous in memory (or in a user-defined iterator's field) row-major tensor that doesn't store strides because they can be computed on the fly using lengths.
556 The row stride is always equaled 1.
557 
558 $(H4 Canonical slice)
559 
560 Canonical slice as contiguous in memory (or in a user-defined iterator's field) rows of a row-major tensor, it doesn't store the stride for row dimension because it is always equaled 1.
561 BLAS/LAPACK matrices are Canonical but originally have column-major order.
562 In the same time you can use 2D Canonical Slices with LAPACK assuming that rows are columns and columns are rows.
563 
564 $(H4 Universal slice)
565 
566 A row-major tensor that stores the strides for all dimensions.
567 NumPy strides are Universal.
568 
569 $(H4 Internal Representation for Universal Slices)
570 
571 Type definition
572 
573 -------
574 Slice!(Iterator, N, Universal)
575 -------
576 
577 Schema
578 
579 -------
580 Slice!(Iterator, N, Universal)
581     size_t[N]     _lengths
582     sizediff_t[N] _strides
583     Iterator      _iterator
584 -------
585 
586 $(H5 Example)
587 
588 Definitions
589 
590 -------
591 import mir.ndslice;
592 auto a = new double[24];
593 Slice!(double*, 3, Universal) s = a.sliced(2, 3, 4).universal;
594 Slice!(double*, 3, Universal) t = s.transposed!(1, 2, 0);
595 Slice!(double*, 3, Universal) r = t.reversed!1;
596 -------
597 
598 Representation
599 
600 -------
601 s________________________
602     lengths[0] ::=  2
603     lengths[1] ::=  3
604     lengths[2] ::=  4
605 
606     strides[0] ::= 12
607     strides[1] ::=  4
608     strides[2] ::=  1
609 
610     iterator        ::= &a[0]
611 
612 t____transposed!(1, 2, 0)
613     lengths[0] ::=  3
614     lengths[1] ::=  4
615     lengths[2] ::=  2
616 
617     strides[0] ::=  4
618     strides[1] ::=  1
619     strides[2] ::= 12
620 
621     iterator        ::= &a[0]
622 
623 r______________reversed!1
624     lengths[0] ::=  2
625     lengths[1] ::=  3
626     lengths[2] ::=  4
627 
628     strides[0] ::= 12
629     strides[1] ::= -4
630     strides[2] ::=  1
631 
632     iterator        ::= &a[8] // (old_strides[1] * (lengths[1] - 1)) = 8
633 -------
634 
635 $(H4 Internal Representation for Canonical Slices)
636 
637 Type definition
638 
639 -------
640 Slice!(Iterator, N, Canonical)
641 -------
642 
643 Schema
644 
645 -------
646 Slice!(Iterator, N, Canonical)
647     size_t[N]       _lengths
648     sizediff_t[N-1] _strides
649     Iterator        _iterator
650 -------
651 
652 $(H4 Internal Representation for Contiguous Slices)
653 
654 Type definition
655 
656 -------
657 Slice!(Iterator, N)
658 -------
659 
660 Schema
661 
662 -------
663 Slice!(Iterator, N, Contiguous)
664     size_t[N]     _lengths
665     sizediff_t[0] _strides
666     Iterator      _iterator
667 -------
668 +/
669 struct mir_slice(Iterator_, size_t N_ = 1, SliceKind kind_ = Contiguous, Labels_...)
670     if (0 < N_ && N_ < 255 && !(kind_ == Canonical && N_ == 1) && Labels_.length <= N_ && isIterator!Iterator_)
671 {
672 @fmamath:
673 
674     /// $(LREF SliceKind)
675     enum SliceKind kind = kind_;
676 
677     /// Dimensions count
678     enum size_t N = N_;
679 
680     /// Strides count
681     enum size_t S = kind == Universal ? N : kind == Canonical ? N - 1 : 0;
682 
683     /// Labels count.
684     enum size_t L = Labels_.length;
685 
686     /// Data iterator type
687     alias Iterator = Iterator_;
688 
689     /// This type
690     alias This = Slice!(Iterator, N, kind);
691 
692     /// Data element type
693     alias DeepElement = typeof(Iterator.init[size_t.init]);
694 
695     ///
696     alias serdeKeysProxy = Unqual!DeepElement;
697 
698     /// Label Iterators types
699     alias Labels = Labels_;
700 
701     ///
702     template Element(size_t dimension)
703         if (dimension < N)
704     {
705         static if (N == 1)
706             alias Element = DeepElement;
707         else
708         {
709             static if (kind == Universal || dimension == N - 1)
710                 alias Element = mir_slice!(Iterator, N - 1, Universal);
711             else
712             static if (N == 2 || kind == Contiguous && dimension == 0)
713                 alias Element = mir_slice!(Iterator, N - 1);
714             else
715                 alias Element = mir_slice!(Iterator, N - 1, Canonical);
716         }
717     }
718 
719 package(mir):
720 
721     enum doUnittest = is(Iterator == int*) && (N == 1 || N == 2) && kind == Contiguous;
722 
723     enum hasAccessByRef = __traits(compiles, &_iterator[0]);
724 
725     enum PureIndexLength(Slices...) = Filter!(isIndex, Slices).length;
726 
727     enum isPureSlice(Slices...) =
728         Slices.length == 0
729         || Slices.length <= N
730         && PureIndexLength!Slices < N
731         && Filter!(isIndex, Slices).length < Slices.length
732         && allSatisfy!(templateOr!(isIndex, is_Slice), Slices);
733 
734 
735     enum isFullPureSlice(Slices...) =
736            Slices.length == 0
737         || Slices.length == N
738         && PureIndexLength!Slices < N
739         && allSatisfy!(templateOr!(isIndex, is_Slice), Slices);
740 
741     enum isIndexedSlice(Slices...) =
742            Slices.length
743         && Slices.length <= N
744         && allSatisfy!(isSlice, Slices)
745         && anySatisfy!(templateNot!is_Slice, Slices);
746 
747     static if (S)
748     {
749         ///
750         public alias _Structure = AliasSeq!(size_t[N], ptrdiff_t[S]);
751         ///
752         public _Structure _structure;
753         ///
754         public alias _lengths = _structure[0];
755         ///
756         public alias _strides = _structure[1];
757     }
758     else
759     {
760         ///
761         public alias _Structure = AliasSeq!(size_t[N]);
762         ///
763         public _Structure _structure;
764         ///
765         public alias _lengths = _structure[0];
766         ///
767         public enum ptrdiff_t[S] _strides = ptrdiff_t[S].init;
768     }
769 
770     /// Data Iterator
771     public Iterator _iterator;
772     /// Labels iterators
773     public Labels _labels;
774 
775     sizediff_t backIndex(size_t dimension = 0)() @safe @property scope const
776         if (dimension < N)
777     {
778         return _stride!dimension * (_lengths[dimension] - 1);
779     }
780 
781     size_t indexStride(size_t I)(size_t[I] _indices) @safe scope const
782     {
783         static if (_indices.length)
784         {
785             static if (kind == Contiguous)
786             {
787                 enum E = I - 1;
788                 assert(_indices[E] < _lengths[E], indexError!(DeepElement, E, N));
789                 ptrdiff_t ball = this._stride!E;
790                 ptrdiff_t stride = _indices[E] * ball;
791                 foreach_reverse (i; Iota!E) //static
792                 {
793                     ball *= _lengths[i + 1];
794                     assert(_indices[i] < _lengths[i], indexError!(DeepElement, i, N));
795                     stride += ball * _indices[i];
796                 }
797             }
798             else
799             static if (kind == Canonical)
800             {
801                 enum E = I - 1;
802                 assert(_indices[E] < _lengths[E], indexError!(DeepElement, E, N));
803                 static if (I == N)
804                     size_t stride = _indices[E];
805                 else
806                     size_t stride = _strides[E] * _indices[E];
807                 foreach_reverse (i; Iota!E) //static
808                 {
809                     assert(_indices[i] < _lengths[i], indexError!(DeepElement, i, N));
810                     stride += _strides[i] * _indices[i];
811                 }
812             }
813             else
814             {
815                 enum E = I - 1;
816                 assert(_indices[E] < _lengths[E], indexError!(DeepElement, E, N));
817                 size_t stride = _strides[E] * _indices[E];
818                 foreach_reverse (i; Iota!E) //static
819                 {
820                     assert(_indices[i] < _lengths[i], indexError!(DeepElement, i, N));
821                     stride += _strides[i] * _indices[i];
822                 }
823             }
824             return stride;
825         }
826         else
827         {
828             return 0;
829         }
830     }
831 
832 public:
833 
834     // static if (S == 0)
835     // {
836         /// Defined for Contiguous Slice only
837         // this()(size_t[N] lengths, in ptrdiff_t[] empty, Iterator iterator, Labels labels)
838         // {
839         //     version(LDC) pragma(inline, true);
840         //     assert(empty.length == 0);
841         //     this._lengths = lengths;
842         //     this._iterator = iterator;
843         // }
844 
845         // /// ditto
846         // this()(size_t[N] lengths, Iterator iterator, Labels labels)
847         // {
848         //     version(LDC) pragma(inline, true);
849         //     this._lengths = lengths;
850         //     this._iterator = iterator;
851         // }
852 
853         // /// ditto
854         // this()(size_t[N] lengths, in ptrdiff_t[] empty, Iterator iterator, Labels labels)
855         // {
856         //     version(LDC) pragma(inline, true);
857         //     assert(empty.length == 0);
858         //     this._lengths = lengths;
859         //     this._iterator = iterator;
860         // }
861 
862         // /// ditto
863         // this()(size_t[N] lengths, Iterator iterator, Labels labels)
864         // {
865         //     version(LDC) pragma(inline, true);
866         //     this._lengths = lengths;
867         //     this._iterator = iterator;
868         // }
869     // }
870 
871     // version(LDC)
872     //     private enum classicConstructor = true;
873     // else
874     //     private enum classicConstructor = S > 0;
875 
876     // static if (classicConstructor)
877     // {
878         /// Defined for Canonical and Universal Slices (DMD, GDC, LDC) and for Contiguous Slices (LDC)
879         // this()(size_t[N] lengths, ptrdiff_t[S] strides, Iterator iterator, Labels labels)
880         // {
881         //     version(LDC) pragma(inline, true);
882         //     this._lengths = lengths;
883         //     this._strides = strides;
884         //     this._iterator = iterator;
885         //     this._labels = labels;
886         // }
887 
888         // /// ditto
889         // this()(size_t[N] lengths, ptrdiff_t[S] strides, ref Iterator iterator, Labels labels)
890         // {
891         //     version(LDC) pragma(inline, true);
892         //     this._lengths = lengths;
893         //     this._strides = strides;
894         //     this._iterator = iterator;
895         //     this._labels = labels;
896         // }
897     // }
898 
899     // /// Construct from null
900     // this()(typeof(null))
901     // {
902     //     version(LDC) pragma(inline, true);
903     // }
904 
905     // static if (doUnittest)
906     // ///
907     // @safe pure version(mir_ndslice_test) unittest
908     // {
909     //     import mir.ndslice.slice;
910     //     alias Array = Slice!(double*);
911         // Array a = null;
912         // auto b = Array(null);
913         // assert(a.empty);
914         // assert(b.empty);
915 
916         // auto fun(Array a = null)
917         // {
918 
919         // }
920     // }
921 
922     static if (doUnittest)
923     /// Creates a 2-dimentional slice with custom strides.
924     nothrow pure
925     version(mir_ndslice_test) unittest
926     {
927         uint[8] array = [1, 2, 3, 4, 5, 6, 7, 8];
928         auto slice = Slice!(uint*, 2, Universal)([2, 2], [4, 1], array.ptr);
929 
930         assert(&slice[0, 0] == &array[0]);
931         assert(&slice[0, 1] == &array[1]);
932         assert(&slice[1, 0] == &array[4]);
933         assert(&slice[1, 1] == &array[5]);
934         assert(slice == [[1, 2], [5, 6]]);
935 
936         array[2] = 42;
937         assert(slice == [[1, 2], [5, 6]]);
938 
939         array[1] = 99;
940         assert(slice == [[1, 99], [5, 6]]);
941     }
942 
943     /++
944     Returns: View with stripped out reference counted context.
945     The lifetime of the result mustn't be longer then the lifetime of the original slice.
946     +/
947     auto lightScope()() return scope @property
948     {
949         auto ret = Slice!(LightScopeOf!Iterator, N, kind, staticMap!(LightScopeOf, Labels))
950             (_structure, .lightScope(_iterator));
951         foreach(i; Iota!L)
952             ret._labels[i] = .lightScope(_labels[i]);
953         return ret;
954     }
955 
956     /// ditto
957     auto lightScope()() @trusted return scope const @property
958     {
959         auto ret = Slice!(LightConstOf!(LightScopeOf!Iterator), N, kind, staticMap!(LightConstOfLightScopeOf, Labels))
960             (_structure, .lightScope(_iterator));
961         foreach(i; Iota!L)
962             ret._labels[i] = .lightScope(_labels[i]);
963         return ret;
964     }
965 
966     /// ditto
967     auto lightScope()() return scope immutable @property
968     {
969         auto ret =  Slice!(LightImmutableOf!(LightScopeOf!Iterator), N, kind, staticMap!(LightImmutableOfLightConstOf(Labels)))
970             (_structure, .lightScope(_iterator));
971         foreach(i; Iota!L)
972             ret._labels[i] = .lightScope(_labels[i]);
973         return ret;
974     }
975 
976     /// Returns: Mutable slice over immutable data.
977     Slice!(LightImmutableOf!Iterator, N, kind, staticMap!(LightImmutableOf, Labels)) lightImmutable()() return scope immutable @property
978     {
979         auto ret = typeof(return)(_structure, .lightImmutable(_iterator));
980         foreach(i; Iota!L)
981             ret._labels[i] = .lightImmutable(_labels[i]);
982         return ret;
983     }
984 
985     static if (doUnittest)
986     ///
987     @safe pure nothrow
988     version(mir_ndslice_test) unittest {
989         import mir.algorithm.iteration: equal;
990 
991         immutable Slice!(int*, 1) x = [1, 2].sliced;
992         auto y = x.lightImmutable;
993         // this._iterator is copied to the new slice (i.e. both point to the same underlying data)
994         assert(x._iterator == y._iterator);
995         assert(x[0] == 1);
996         assert(x[1] == 2);
997         assert(y[0] == 1);
998         assert(y[1] == 2);
999         // Outer immutable is moved to iteration type
1000         static assert(is(typeof(y) == Slice!(immutable(int)*, 1)));
1001         // meaning that y can be modified, even if its elements can't
1002         y.popFront;
1003         // even if x can't be modified
1004         //x.popFront; //error
1005     }
1006 
1007     /// Returns: Mutable slice over const data.
1008     Slice!(LightConstOf!Iterator, N, kind, staticMap!(LightConstOf, Labels)) lightConst()() return scope const @property @trusted
1009     {
1010         auto ret = typeof(return)(_structure, .lightConst(_iterator));
1011         foreach(i; Iota!L)
1012             ret._labels[i] = .lightConst(_labels[i]);
1013         return ret;
1014     }
1015 
1016     /// ditto
1017     Slice!(LightImmutableOf!Iterator, N, kind, staticMap!(LightImmutableOf, Labels)) lightConst()() return scope immutable @property
1018     {
1019         return this.lightImmutable;
1020     }
1021 
1022     static if (doUnittest)
1023     ///
1024     @safe pure nothrow
1025     version(mir_ndslice_test) unittest {
1026         import mir.algorithm.iteration: equal;
1027 
1028         const Slice!(int*, 1) x = [1, 2].sliced;
1029         auto y = x.lightConst;
1030         // this._iterator is copied to the new slice (i.e. both point to the same underlying data)
1031         assert(x._iterator == y._iterator);
1032         assert(x.equal([1, 2]));
1033         assert(y.equal([1, 2]));
1034         // Outer const is moved to iteration type
1035         static assert(is(typeof(y) == Slice!(const(int)*, 1)));
1036         // meaning that y can be modified, even if its elements can't
1037         y.popFront;
1038         // even if x can't be modified
1039         //x.popFront; //error
1040     }
1041 
1042     /// Label for the dimensions 'd'. By default returns the row label.
1043     Slice!(Labels[d])
1044         label(size_t d = 0)() @property
1045         if (d <= L)
1046     {
1047         return typeof(return)(_lengths[d], _labels[d]);
1048     }
1049 
1050     /// ditto
1051     void label(size_t d = 0)(Slice!(Labels[d]) rhs) @property
1052         if (d <= L)
1053     {
1054         import core.lifetime: move;
1055         assert(rhs.length == _lengths[d], "ndslice: labels dimension mismatch");
1056         _labels[d] = rhs._iterator.move;
1057     }
1058 
1059     /// ditto
1060     Slice!(LightConstOf!(Labels[d]))
1061         label(size_t d = 0)() @property const
1062         if (d <= L)
1063     {
1064         return typeof(return)(_lengths[d].lightConst, _labels[d]);
1065     }
1066 
1067     /// ditto
1068     Slice!(LightImmutableOf!(Labels[d]))
1069         label(size_t d = 0)() @property immutable
1070         if (d <= L)
1071     {
1072         return typeof(return)(_lengths[d].lightImmutable, _labels[d]);
1073     }
1074 
1075     /// Strips label off the DataFrame
1076     auto values()() @property
1077     {
1078         return Slice!(Iterator, N, kind)(_structure, _iterator);
1079     }
1080 
1081     /// ditto
1082     auto values()() @property const
1083     {
1084         return Slice!(LightConstOf!Iterator, N, kind)(_structure, .lightConst(_iterator));
1085     }
1086 
1087     /// ditto
1088     auto values()() @property immutable
1089     {
1090         return Slice!(LightImmutableOf!Iterator, N, kind)(_structure, .lightImmutable(_iterator));
1091     }
1092 
1093     /// `opIndex` overload for const slice
1094     auto ref opIndex(Indexes...)(Indexes indices) const @trusted
1095             if (isPureSlice!Indexes || isIndexedSlice!Indexes)
1096     {
1097         return lightConst.opIndex(indices);
1098     }
1099     /// `opIndex` overload for immutable slice
1100     auto ref opIndex(Indexes...)(Indexes indices) immutable @trusted
1101             if (isPureSlice!Indexes || isIndexedSlice!Indexes)
1102     {
1103         return lightImmutable.opIndex(indices);
1104     }
1105 
1106     static if (allSatisfy!(isPointer, Iterator, Labels))
1107     {
1108         private alias ConstThis = Slice!(const(Unqual!(PointerTarget!Iterator))*, N, kind);
1109         private alias ImmutableThis = Slice!(immutable(Unqual!(PointerTarget!Iterator))*, N, kind);
1110 
1111         /++
1112         Cast to const and immutable slices in case of underlying range is a pointer.
1113         +/
1114         auto toImmutable()() return scope immutable @trusted pure nothrow @nogc
1115         {
1116             return Slice!(ImmutableOfUnqualOfPointerTarget!Iterator, N, kind, staticMap!(ImmutableOfUnqualOfPointerTarget, Labels))
1117                 (_structure, _iterator, _labels);
1118         }
1119 
1120         /// ditto
1121         auto toConst()() return scope const @trusted pure nothrow @nogc
1122         {
1123             version(LDC) pragma(inline, true);
1124             return Slice!(ConstOfUnqualOfPointerTarget!Iterator, N, kind, staticMap!(ConstOfUnqualOfPointerTarget, Labels))
1125                 (_structure, _iterator, _labels);
1126         }
1127 
1128         static if (!is(Slice!(const(Unqual!(PointerTarget!Iterator))*, N, kind) == This))
1129         /// ditto
1130         alias toConst this;
1131 
1132         static if (doUnittest)
1133         ///
1134         version(mir_ndslice_test) unittest
1135         {
1136             static struct Foo
1137             {
1138                 Slice!(int*) bar;
1139 
1140                 int get(size_t i) immutable
1141                 {
1142                     return bar[i];
1143                 }
1144 
1145                 int get(size_t i) const
1146                 {
1147                     return bar[i];
1148                 }
1149 
1150                 int get(size_t i) inout
1151                 {
1152                     return bar[i];
1153                 }
1154             }
1155         }
1156 
1157         static if (doUnittest)
1158         ///
1159         version(mir_ndslice_test) unittest
1160         {
1161             Slice!(double*, 2, Universal) nn;
1162             Slice!(immutable(double)*, 2, Universal) ni;
1163             Slice!(const(double)*, 2, Universal) nc;
1164 
1165             const Slice!(double*, 2, Universal) cn;
1166             const Slice!(immutable(double)*, 2, Universal) ci;
1167             const Slice!(const(double)*, 2, Universal) cc;
1168 
1169             immutable Slice!(double*, 2, Universal) in_;
1170             immutable Slice!(immutable(double)*, 2, Universal) ii;
1171             immutable Slice!(const(double)*, 2, Universal) ic;
1172 
1173             nc = nc; nc = cn; nc = in_;
1174             nc = nc; nc = cc; nc = ic;
1175             nc = ni; nc = ci; nc = ii;
1176 
1177             void fun(T, size_t N)(Slice!(const(T)*, N, Universal) sl)
1178             {
1179                 //...
1180             }
1181 
1182             fun(nn); fun(cn); fun(in_);
1183             fun(nc); fun(cc); fun(ic);
1184             fun(ni); fun(ci); fun(ii);
1185 
1186             static assert(is(typeof(cn[]) == typeof(nc)));
1187             static assert(is(typeof(ci[]) == typeof(ni)));
1188             static assert(is(typeof(cc[]) == typeof(nc)));
1189 
1190             static assert(is(typeof(in_[]) == typeof(ni)));
1191             static assert(is(typeof(ii[]) == typeof(ni)));
1192             static assert(is(typeof(ic[]) == typeof(ni)));
1193 
1194             ni = ci[];
1195             ni = in_[];
1196             ni = ii[];
1197             ni = ic[];
1198         }
1199     }
1200 
1201     /++
1202     Iterator
1203     Returns:
1204         Iterator (pointer) to the $(LREF .Slice.first) element.
1205     +/
1206     auto iterator()() inout return scope @property
1207     {
1208         return _iterator;
1209     }
1210 
1211     static if (kind == Contiguous && isPointer!Iterator)
1212         /++
1213         `ptr` alias is available only if the slice kind is $(LREF Contiguous) contiguous and the $(LREF .Slice.iterator) is a pointers.
1214         +/
1215         alias ptr = iterator;
1216     else
1217     {
1218         import mir.rc.array: mir_rci;
1219         static if (kind == Contiguous && is(Iterator : mir_rci!ET, ET))
1220         auto ptr() return scope inout @property
1221         {
1222             return _iterator._iterator;
1223         }
1224     }
1225 
1226     /++
1227     Field (array) data.
1228     Returns:
1229         Raw data slice.
1230     Constraints:
1231         Field is defined only for contiguous slices.
1232     +/
1233     auto field()() return scope @trusted @property
1234     {
1235         static assert(kind == Contiguous, "Slice.field is defined only for contiguous slices. Slice kind is " ~ kind.stringof);
1236         static if (is(typeof(_iterator[size_t(0) .. elementCount])))
1237         {
1238             return _iterator[size_t(0) .. elementCount];
1239         }
1240         else
1241         {
1242             import mir.ndslice.topology: flattened;
1243             return this.flattened;
1244         }
1245     }
1246 
1247     /// ditto
1248     auto field()() return scope const @trusted @property
1249     {
1250         return this.lightConst.field;
1251     }
1252 
1253     /// ditto
1254     auto field()() return immutable scope @trusted @property
1255     {
1256         return this.lightImmutable.field;
1257     }
1258 
1259     static if (doUnittest)
1260     ///
1261     @safe version(mir_ndslice_test) unittest
1262     {
1263         auto arr = [1, 2, 3, 4];
1264         auto sl0 = arr.sliced;
1265         auto sl1 = arr.slicedField;
1266 
1267         assert(sl0.field is arr);
1268         assert(sl1.field is arr);
1269 
1270         arr = arr[1 .. $];
1271         sl0 = sl0[1 .. $];
1272         sl1 = sl1[1 .. $];
1273 
1274         assert(sl0.field is arr);
1275         assert(sl1.field is arr);
1276         assert((cast(const)sl1).field is arr);
1277         ()@trusted{ assert((cast(immutable)sl1).field is arr); }();
1278     }
1279 
1280     /++
1281     Returns: static array of lengths
1282     See_also: $(LREF .Slice.structure)
1283     +/
1284     size_t[N] shape()() @trusted @property scope const
1285     {
1286         return _lengths[0 .. N];
1287     }
1288 
1289     static if (doUnittest)
1290     /// Regular slice
1291     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1292     {
1293         import mir.ndslice.topology : iota;
1294         assert(iota(3, 4, 5).shape == cast(size_t[3])[3, 4, 5]);
1295     }
1296 
1297     static if (doUnittest)
1298     /// Packed slice
1299     @safe @nogc pure nothrow
1300     version(mir_ndslice_test) unittest
1301     {
1302         import mir.ndslice.topology : pack, iota;
1303         size_t[3] s = [3, 4, 5];
1304         assert(iota(3, 4, 5, 6, 7).pack!2.shape == s);
1305     }
1306 
1307     /++
1308     Returns: static array of lengths
1309     See_also: $(LREF .Slice.structure)
1310     +/
1311     ptrdiff_t[N] strides()() @trusted @property scope const
1312     {
1313         static if (N <= S)
1314             return _strides[0 .. N];
1315         else
1316         {
1317             typeof(return) ret;
1318             static if (kind == Canonical)
1319             {
1320                 foreach (i; Iota!S)
1321                     ret[i] = _strides[i];
1322                 ret[$-1] = 1;
1323             }
1324             else
1325             {
1326                 ret[$ - 1] = _stride!(N - 1);
1327                 foreach_reverse (i; Iota!(N - 1))
1328                     ret[i] = ret[i + 1] * _lengths[i + 1];
1329             }
1330             return ret;
1331         }
1332     }
1333 
1334     static if (doUnittest)
1335     /// Regular slice
1336     @safe @nogc pure nothrow
1337     version(mir_ndslice_test) unittest
1338     {
1339         import mir.ndslice.topology : iota;
1340         size_t[3] s = [20, 5, 1];
1341         assert(iota(3, 4, 5).strides == s);
1342     }
1343 
1344     static if (doUnittest)
1345     /// Modified regular slice
1346     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1347     {
1348         import mir.ndslice.topology : pack, iota, universal;
1349         import mir.ndslice.dynamic : reversed, strided, transposed;
1350         assert(iota(3, 4, 50)
1351             .universal
1352             .reversed!2      //makes stride negative
1353             .strided!2(6)    //multiplies stride by 6 and changes corresponding length
1354             .transposed!2    //brings dimension `2` to the first position
1355             .strides == cast(ptrdiff_t[3])[-6, 200, 50]);
1356     }
1357 
1358     static if (doUnittest)
1359     /// Packed slice
1360     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1361     {
1362         import mir.ndslice.topology : pack, iota;
1363         size_t[3] s = [20 * 42, 5 * 42, 1 * 42];
1364         assert(iota(3, 4, 5, 6, 7)
1365             .pack!2
1366             .strides == s);
1367     }
1368 
1369     /++
1370     Returns: static array of lengths and static array of strides
1371     See_also: $(LREF .Slice.shape)
1372    +/
1373     Structure!N structure()() @safe @property scope const
1374     {
1375         return typeof(return)(_lengths, strides);
1376     }
1377 
1378     static if (doUnittest)
1379     /// Regular slice
1380     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1381     {
1382         import mir.ndslice.topology : iota;
1383         assert(iota(3, 4, 5)
1384             .structure == Structure!3([3, 4, 5], [20, 5, 1]));
1385     }
1386 
1387     static if (doUnittest)
1388     /// Modified regular slice
1389     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1390     {
1391         import mir.ndslice.topology : pack, iota, universal;
1392         import mir.ndslice.dynamic : reversed, strided, transposed;
1393         assert(iota(3, 4, 50)
1394             .universal
1395             .reversed!2      //makes stride negative
1396             .strided!2(6)    //multiplies stride by 6 and changes corresponding length
1397             .transposed!2    //brings dimension `2` to the first position
1398             .structure == Structure!3([9, 3, 4], [-6, 200, 50]));
1399     }
1400 
1401     static if (doUnittest)
1402     /// Packed slice
1403     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1404     {
1405         import mir.ndslice.topology : pack, iota;
1406         assert(iota(3, 4, 5, 6, 7)
1407             .pack!2
1408             .structure == Structure!3([3, 4, 5], [20 * 42, 5 * 42, 1 * 42]));
1409     }
1410 
1411     /++
1412     Save primitive.
1413     +/
1414     auto save()() inout @property
1415     {
1416         return this;
1417     }
1418 
1419     static if (doUnittest)
1420     /// Save range
1421     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1422     {
1423         import mir.ndslice.topology : iota;
1424         auto slice = iota(2, 3).save;
1425     }
1426 
1427     static if (doUnittest)
1428     /// Pointer type.
1429     @safe pure nothrow version(mir_ndslice_test) unittest
1430     {
1431         import mir.ndslice.allocation;
1432         //sl type is `Slice!(2, int*)`
1433         auto sl = slice!int(2, 3).save;
1434     }
1435 
1436     /++
1437     Multidimensional `length` property.
1438     Returns: length of the corresponding dimension
1439     See_also: $(LREF .Slice.shape), $(LREF .Slice.structure)
1440     +/
1441     size_t length(size_t dimension = 0)() @safe @property scope const
1442         if (dimension < N)
1443     {
1444         return _lengths[dimension];
1445     }
1446 
1447     static if (doUnittest)
1448     ///
1449     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1450     {
1451         import mir.ndslice.topology : iota;
1452         auto slice = iota(3, 4, 5);
1453         assert(slice.length   == 3);
1454         assert(slice.length!0 == 3);
1455         assert(slice.length!1 == 4);
1456         assert(slice.length!2 == 5);
1457     }
1458 
1459     alias opDollar = length;
1460 
1461     /++
1462         Multidimensional `stride` property.
1463         Returns: stride of the corresponding dimension
1464         See_also: $(LREF .Slice.structure)
1465     +/
1466     sizediff_t _stride(size_t dimension = 0)() @safe @property scope const
1467         if (dimension < N)
1468     {
1469         static if (dimension < S)
1470         {
1471             return _strides[dimension];
1472         }
1473         else
1474         static if (dimension + 1 == N)
1475         {
1476             return 1;
1477         }
1478         else
1479         {
1480             size_t ball = _lengths[$ - 1];
1481             foreach_reverse(i; Iota!(dimension + 1, N - 1))
1482                 ball *= _lengths[i];
1483             return ball;
1484         }
1485 
1486     }
1487 
1488     static if (doUnittest)
1489     /// Regular slice
1490     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1491     {
1492         import mir.ndslice.topology : iota;
1493         auto slice = iota(3, 4, 5);
1494         assert(slice._stride   == 20);
1495         assert(slice._stride!0 == 20);
1496         assert(slice._stride!1 == 5);
1497         assert(slice._stride!2 == 1);
1498     }
1499 
1500     static if (doUnittest)
1501     /// Modified regular slice
1502     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1503     {
1504         import mir.ndslice.dynamic : reversed, strided, swapped;
1505         import mir.ndslice.topology : universal, iota;
1506         assert(iota(3, 4, 50)
1507             .universal
1508             .reversed!2      //makes stride negative
1509             .strided!2(6)    //multiplies stride by 6 and changes the corresponding length
1510             .swapped!(1, 2)  //swaps dimensions `1` and `2`
1511             ._stride!1 == -6);
1512     }
1513 
1514     /++
1515     Multidimensional input range primitive.
1516     +/
1517     bool empty(size_t dimension = 0)() @safe @property scope const
1518         if (dimension < N)
1519     {
1520         return _lengths[dimension] == 0;
1521     }
1522 
1523     static if (N == 1)
1524     {
1525         ///ditto
1526         auto ref front(size_t dimension = 0)() return scope @trusted @property
1527             if (dimension == 0)
1528         {
1529             assert(!empty!dimension);
1530             return *_iterator;
1531         }
1532 
1533         ///ditto
1534         auto ref front(size_t dimension = 0)() return scope @trusted @property const
1535             if (dimension == 0)
1536         {
1537             assert(!empty!dimension);
1538             return *_iterator.lightScope;
1539         }
1540 
1541         ///ditto
1542         auto ref front(size_t dimension = 0)() return scope @trusted @property immutable
1543             if (dimension == 0)
1544         {
1545             assert(!empty!dimension);
1546             return *_iterator.lightScope;
1547         }
1548     }
1549     else
1550     {
1551         /// ditto
1552         Element!dimension front(size_t dimension = 0)() return scope @property
1553             if (dimension < N)
1554         {
1555             typeof(return)._Structure structure_ = typeof(return)._Structure.init;
1556 
1557             foreach (i; Iota!(typeof(return).N))
1558             {
1559                 enum j = i >= dimension ? i + 1 : i;
1560                 structure_[0][i] = _lengths[j];
1561             }
1562 
1563             static if (!typeof(return).S || typeof(return).S + 1 == S)
1564                 alias s = _strides;
1565             else
1566                 auto s = strides;
1567 
1568             foreach (i; Iota!(typeof(return).S))
1569             {
1570                 enum j = i >= dimension ? i + 1 : i;
1571                 structure_[1][i] = s[j];
1572             }
1573 
1574             return typeof(return)(structure_, _iterator);
1575         }
1576 
1577         ///ditto
1578         auto front(size_t dimension = 0)() return scope @trusted @property const
1579             if (dimension < N)
1580         {
1581             assert(!empty!dimension);
1582             return this.lightConst.front!dimension;
1583         }
1584 
1585         ///ditto
1586         auto front(size_t dimension = 0)() return scope @trusted @property immutable
1587             if (dimension < N)
1588         {
1589             assert(!empty!dimension);
1590             return this.lightImmutable.front!dimension;
1591         }
1592     }
1593 
1594     static if (N == 1 && isMutable!DeepElement && !hasAccessByRef)
1595     {
1596         ///ditto
1597         auto ref front(size_t dimension = 0, T)(T value) return scope @trusted @property
1598             if (dimension == 0)
1599         {
1600             // check assign safety
1601             static auto ref fun(ref DeepElement t, ref T v) @safe
1602             {
1603                 return t = v;
1604             }
1605             assert(!empty!dimension);
1606             static if (__traits(compiles, *_iterator = value))
1607                 return *_iterator = value;
1608             else
1609                 return _iterator[0] = value;
1610         }
1611     }
1612 
1613     ///ditto
1614     static if (N == 1)
1615     auto ref Element!dimension
1616     back(size_t dimension = 0)() return scope @trusted @property
1617         if (dimension < N)
1618     {
1619         assert(!empty!dimension);
1620         return _iterator[backIndex];
1621     }
1622     else
1623     auto ref Element!dimension
1624     back(size_t dimension = 0)() return scope @trusted @property
1625         if (dimension < N)
1626     {
1627         assert(!empty!dimension);
1628         auto structure_ = typeof(return)._Structure.init;
1629 
1630         foreach (i; Iota!(typeof(return).N))
1631         {
1632             enum j = i >= dimension ? i + 1 : i;
1633             structure_[0][i] = _lengths[j];
1634         }
1635 
1636         static if (!typeof(return).S || typeof(return).S + 1 == S)
1637             alias s =_strides;
1638         else
1639             auto s = strides;
1640 
1641         foreach (i; Iota!(typeof(return).S))
1642         {
1643             enum j = i >= dimension ? i + 1 : i;
1644             structure_[1][i] = s[j];
1645         }
1646 
1647         return typeof(return)(structure_, _iterator + backIndex!dimension);
1648     }
1649 
1650     static if (N == 1 && isMutable!DeepElement && !hasAccessByRef)
1651     {
1652         ///ditto
1653         auto ref back(size_t dimension = 0, T)(T value) return scope @trusted @property
1654             if (dimension == 0)
1655         {
1656             // check assign safety
1657             static auto ref fun(ref DeepElement t, ref T v) @safe
1658             {
1659                 return t = v;
1660             }
1661             assert(!empty!dimension);
1662             return _iterator[backIndex] = value;
1663         }
1664     }
1665 
1666     ///ditto
1667     void popFront(size_t dimension = 0)() @trusted scope
1668         if (dimension < N && (dimension == 0 || kind != Contiguous))
1669     {
1670         assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0.");
1671         _lengths[dimension]--;
1672         static if ((kind == Contiguous || kind == Canonical) && dimension + 1 == N)
1673             ++_iterator;
1674         else
1675         static if (kind == Canonical || kind == Universal)
1676             _iterator += _strides[dimension];
1677         else
1678             _iterator += _stride!dimension;
1679     }
1680 
1681     ///ditto
1682     void popBack(size_t dimension = 0)() @safe scope
1683         if (dimension < N && (dimension == 0 || kind != Contiguous))
1684     {
1685         assert(_lengths[dimension], __FUNCTION__ ~ ": length!" ~ dimension.stringof ~ " should be greater than 0.");
1686         --_lengths[dimension];
1687     }
1688 
1689     ///ditto
1690     void popFrontExactly(size_t dimension = 0)(size_t n) @trusted scope
1691         if (dimension < N && (dimension == 0 || kind != Contiguous))
1692     {
1693         assert(n <= _lengths[dimension],
1694             __FUNCTION__ ~ ": n should be less than or equal to length!" ~ dimension.stringof);
1695         _lengths[dimension] -= n;
1696         _iterator += _stride!dimension * n;
1697     }
1698 
1699     ///ditto
1700     void popBackExactly(size_t dimension = 0)(size_t n) @safe scope
1701         if (dimension < N && (dimension == 0 || kind != Contiguous))
1702     {
1703         assert(n <= _lengths[dimension],
1704             __FUNCTION__ ~ ": n should be less than or equal to length!" ~ dimension.stringof);
1705         _lengths[dimension] -= n;
1706     }
1707 
1708     ///ditto
1709     void popFrontN(size_t dimension = 0)(size_t n) @trusted scope
1710         if (dimension < N && (dimension == 0 || kind != Contiguous))
1711     {
1712         popFrontExactly!dimension(min(n, _lengths[dimension]));
1713     }
1714 
1715     ///ditto
1716     void popBackN(size_t dimension = 0)(size_t n) @safe scope
1717         if (dimension < N && (dimension == 0 || kind != Contiguous))
1718     {
1719         popBackExactly!dimension(min(n, _lengths[dimension]));
1720     }
1721 
1722     static if (doUnittest)
1723     ///
1724     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1725     {
1726         import std.range.primitives;
1727         import mir.ndslice.topology : iota, canonical;
1728         auto slice = iota(10, 20, 30).canonical;
1729 
1730         static assert(isRandomAccessRange!(typeof(slice)));
1731         static assert(hasSlicing!(typeof(slice)));
1732         static assert(hasLength!(typeof(slice)));
1733 
1734         assert(slice.shape == cast(size_t[3])[10, 20, 30]);
1735         slice.popFront;
1736         slice.popFront!1;
1737         slice.popBackExactly!2(4);
1738         assert(slice.shape == cast(size_t[3])[9, 19, 26]);
1739 
1740         auto matrix = slice.front!1;
1741         assert(matrix.shape == cast(size_t[2])[9, 26]);
1742 
1743         auto column = matrix.back!1;
1744         assert(column.shape == cast(size_t[1])[9]);
1745 
1746         slice.popFrontExactly!1(slice.length!1);
1747         assert(slice.empty   == false);
1748         assert(slice.empty!1 == true);
1749         assert(slice.empty!2 == false);
1750         assert(slice.shape == cast(size_t[3])[9, 0, 26]);
1751 
1752         assert(slice.back.front!1.empty);
1753 
1754         slice.popFrontN!0(40);
1755         slice.popFrontN!2(40);
1756         assert(slice.shape == cast(size_t[3])[0, 0, 0]);
1757     }
1758 
1759     package(mir) ptrdiff_t lastIndex()() @safe @property scope const
1760     {
1761         static if (kind == Contiguous)
1762         {
1763             return elementCount - 1;
1764         }
1765         else
1766         {
1767             auto strides = strides;
1768             ptrdiff_t shift = 0;
1769             foreach(i; Iota!N)
1770                 shift += strides[i] * (_lengths[i] - 1);
1771             return shift;
1772         }
1773     }
1774 
1775     static if (N > 1)
1776     {
1777         /// Accesses the first deep element of the slice.
1778         auto ref first()() return scope @trusted @property
1779         {
1780             assert(!anyEmpty);
1781             return *_iterator;
1782         }
1783 
1784         static if (isMutable!DeepElement && !hasAccessByRef)
1785         ///ditto
1786         auto ref first(T)(T value) return scope @trusted @property
1787         {
1788             assert(!anyEmpty);
1789             static if (__traits(compiles, *_iterator = value))
1790                 return *_iterator = value;
1791             else
1792                 return _iterator[0] = value;
1793         }
1794 
1795         static if (doUnittest)
1796         ///
1797         @safe pure nothrow @nogc version(mir_ndslice_test) unittest
1798         {
1799             import mir.ndslice.topology: iota, universal, canonical;
1800             auto f = 5;
1801             assert([2, 3].iota(f).first == f);
1802         }
1803 
1804         /// Accesses the last deep element of the slice.
1805         auto ref last()() @trusted return scope @property
1806         {
1807             assert(!anyEmpty);
1808             return _iterator[lastIndex];
1809         }
1810 
1811         static if (isMutable!DeepElement && !hasAccessByRef)
1812         ///ditto
1813         auto ref last(T)(T value) @trusted return scope @property
1814         {
1815             assert(!anyEmpty);
1816             return _iterator[lastIndex] = value;
1817         }
1818 
1819         static if (doUnittest)
1820         ///
1821         @safe pure nothrow @nogc version(mir_ndslice_test) unittest
1822         {
1823             import mir.ndslice.topology: iota;
1824             auto f = 5;
1825             assert([2, 3].iota(f).last == f + 2 * 3 - 1);
1826         }
1827 
1828         static if (kind_ != SliceKind.contiguous)
1829         /// Peforms `popFrontAll` for all dimensions
1830         void popFrontAll()
1831         {
1832             assert(!anyEmpty);
1833             foreach(d; Iota!N_)
1834                 popFront!d;
1835         }
1836 
1837         static if (doUnittest)
1838         ///
1839         @safe pure nothrow version(mir_ndslice_test) unittest
1840         {
1841             import mir.ndslice.topology: iota, canonical;
1842             auto v = [2, 3].iota.canonical;
1843             v.popFrontAll;
1844             assert(v == [[4, 5]]);
1845         }
1846 
1847         static if (kind_ != SliceKind.contiguous)
1848         /// Peforms `popBackAll` for all dimensions
1849         void popBackAll()
1850         {
1851             assert(!anyEmpty);
1852             foreach(d; Iota!N_)
1853                 popBack!d;
1854         }
1855 
1856         static if (doUnittest)
1857         ///
1858         @safe pure nothrow version(mir_ndslice_test) unittest
1859         {
1860             import mir.ndslice.topology: iota, canonical;
1861             auto v = [2, 3].iota.canonical;
1862             v.popBackAll;
1863             assert(v == [[0, 1]]);
1864         }
1865     }
1866     else
1867     {
1868         alias first = front;
1869         alias last = back;
1870         alias popFrontAll = popFront;
1871         alias popBackAll = popBack;
1872     }
1873 
1874     /+
1875     Returns: `true` if for any dimension of completely unpacked slice the length equals to `0`, and `false` otherwise.
1876     +/
1877     private bool anyRUEmpty()() @trusted scope const
1878     {
1879         static if (isInstanceOf!(SliceIterator, Iterator))
1880         {
1881             import mir.ndslice.topology: unpack;
1882             return this.lightScope.unpack.anyRUEmpty;
1883         }
1884         else
1885             return _lengths[0 .. N].anyEmptyShape;
1886     }
1887 
1888 
1889     /++
1890     Returns: `true` if for any dimension the length equals to `0`, and `false` otherwise.
1891     +/
1892     bool anyEmpty()() @trusted @property scope const
1893     {
1894         return _lengths[0 .. N].anyEmptyShape;
1895     }
1896 
1897     static if (doUnittest)
1898     ///
1899     @safe pure nothrow @nogc version(mir_ndslice_test) unittest
1900     {
1901         import mir.ndslice.topology : iota, canonical;
1902         auto s = iota(2, 3).canonical;
1903         assert(!s.anyEmpty);
1904         s.popFrontExactly!1(3);
1905         assert(s.anyEmpty);
1906     }
1907 
1908     /++
1909     Convenience function for backward indexing.
1910 
1911     Returns: `this[$-index[0], $-index[1], ..., $-index[N-1]]`
1912     +/
1913     auto ref backward()(size_t[N] index) return scope
1914     {
1915         foreach (i; Iota!N)
1916             index[i] = _lengths[i] - index[i];
1917         return this[index];
1918     }
1919 
1920     /// ditto
1921     auto ref backward()(size_t[N] index) return scope const
1922     {
1923         return this.lightConst.backward(index);
1924     }
1925 
1926     /// ditto
1927     auto ref backward()(size_t[N] index) return scope const
1928     {
1929         return this.lightConst.backward(index);
1930     }
1931 
1932     static if (doUnittest)
1933     ///
1934     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1935     {
1936         import mir.ndslice.topology : iota;
1937         auto s = iota(2, 3);
1938         assert(s[$ - 1, $ - 2] == s.backward([1, 2]));
1939     }
1940 
1941     /++
1942     Returns: Total number of elements in a slice
1943     +/
1944     size_t elementCount()() @safe @property scope const
1945     {
1946         size_t len = 1;
1947         foreach (i; Iota!N)
1948             len *= _lengths[i];
1949         return len;
1950     }
1951 
1952     static if (doUnittest)
1953     /// Regular slice
1954     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1955     {
1956         import mir.ndslice.topology : iota;
1957         assert(iota(3, 4, 5).elementCount == 60);
1958     }
1959 
1960 
1961     static if (doUnittest)
1962     /// Packed slice
1963     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
1964     {
1965         import mir.ndslice.topology : pack, evertPack, iota;
1966         auto slice = iota(3, 4, 5, 6, 7, 8);
1967         auto p = slice.pack!2;
1968         assert(p.elementCount == 360);
1969         assert(p[0, 0, 0, 0].elementCount == 56);
1970         assert(p.evertPack.elementCount == 56);
1971     }
1972 
1973     /++
1974     Slice selected dimension.
1975     Params:
1976         begin = initial index of the sub-slice (inclusive)
1977         end = final index of the sub-slice (noninclusive)
1978     Returns: ndslice with `length!dimension` equal to `end - begin`.
1979     +/
1980     auto select(size_t dimension)(size_t begin, size_t end) @trusted
1981     {
1982         static if (kind == Contiguous && dimension)
1983         {
1984             import mir.ndslice.topology: canonical;
1985             auto ret = this.canonical;
1986         }
1987         else
1988         {
1989             auto ret = this;
1990         }
1991         auto len = end - begin;
1992         assert(len <= ret._lengths[dimension]);
1993         ret._lengths[dimension] = len;
1994         ret._iterator += ret._stride!dimension * begin;
1995         return ret;
1996     }
1997 
1998     static if (doUnittest)
1999     ///
2000     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
2001     {
2002         import mir.ndslice.topology : iota;
2003         auto sl = iota(3, 4);
2004         assert(sl.select!1(1, 3) == sl[0 .. $, 1 .. 3]);
2005     }
2006 
2007     /++
2008     Select the first n elements for the dimension.
2009     Params:
2010         dimension = Dimension to slice.
2011         n = count of elements for the dimension
2012     Returns: ndslice with `length!dimension` equal to `n`.
2013     +/
2014     auto selectFront(size_t dimension)(size_t n) return scope
2015     {
2016         static if (kind == Contiguous && dimension)
2017         {
2018             import mir.ndslice.topology: canonical;
2019             auto ret = this.canonical;
2020         }
2021         else
2022         {
2023             auto ret = this;
2024         }
2025         assert(n <= ret._lengths[dimension]);
2026         ret._lengths[dimension] = n;
2027         return ret;
2028     }
2029 
2030     static if (doUnittest)
2031     ///
2032     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
2033     {
2034         import mir.ndslice.topology : iota;
2035         auto sl = iota(3, 4);
2036         assert(sl.selectFront!1(2) == sl[0 .. $, 0 .. 2]);
2037     }
2038 
2039     /++
2040     Select the last n elements for the dimension.
2041     Params:
2042         dimension = Dimension to slice.
2043         n = count of elements for the dimension
2044     Returns: ndslice with `length!dimension` equal to `n`.
2045     +/
2046     auto selectBack(size_t dimension)(size_t n) return scope
2047     {
2048         static if (kind == Contiguous && dimension)
2049         {
2050             import mir.ndslice.topology: canonical;
2051             auto ret = this.canonical;
2052         }
2053         else
2054         {
2055             auto ret = this;
2056         }
2057         assert(n <= ret._lengths[dimension]);
2058         ret._iterator += ret._stride!dimension * (ret._lengths[dimension] - n);
2059         ret._lengths[dimension] = n;
2060         return ret;
2061     }
2062 
2063     static if (doUnittest)
2064     ///
2065     @safe @nogc pure nothrow version(mir_ndslice_test) unittest
2066     {
2067         import mir.ndslice.topology : iota;
2068         auto sl = iota(3, 4);
2069         assert(sl.selectBack!1(2) == sl[0 .. $, $ - 2 .. $]);
2070     }
2071 
2072     ///ditto
2073     bool opEquals(IteratorR, SliceKind rkind)(auto ref const Slice!(IteratorR, N, rkind) rslice) @trusted scope const
2074     {
2075         static if (
2076                __traits(isPOD, Iterator)
2077             && __traits(isPOD, IteratorR)
2078             && __traits(compiles, this._iterator == rslice._iterator)
2079             )
2080         {
2081             if (this._lengths != rslice._lengths)
2082                 return false;
2083             if (anyEmpty)
2084                 return true;
2085             if (this._iterator == rslice._iterator)
2086             {
2087                 auto ls = this.strides;
2088                 auto rs = rslice.strides;
2089                 foreach (i; Iota!N)
2090                 {
2091                     if (this._lengths[i] != 1 && ls[i] != rs[i])
2092                         goto L;
2093                 }
2094                 return true;
2095             }
2096         }
2097         L:
2098 
2099         static if (is(Iterator == IotaIterator!UL, UL) && is(IteratorR == Iterator))
2100         {
2101             return false;
2102         }
2103         else
2104         {
2105             import mir.algorithm.iteration : equal;
2106             return equal(this.lightScope, rslice.lightScope);
2107         }
2108     }
2109 
2110     /// ditto
2111     bool opEquals(T)(scope const(T)[] arr) @trusted scope const
2112     {
2113         auto slice = this.lightConst;
2114         if (slice.length != arr.length)
2115             return false;
2116         if (arr.length) do
2117         {
2118             if (slice.front != arr[0])
2119                 return false;
2120             slice.popFront;
2121             arr = arr[1 .. $];
2122         }
2123         while (arr.length);
2124         return true;
2125     }
2126 
2127     static if (doUnittest)
2128     ///
2129     @safe pure nothrow
2130     version(mir_ndslice_test) unittest
2131     {
2132         auto a = [1, 2, 3, 4].sliced(2, 2);
2133 
2134         assert(a != [1, 2, 3, 4, 5, 6].sliced(2, 3));
2135         assert(a != [[1, 2, 3], [4, 5, 6]]);
2136 
2137         assert(a == [1, 2, 3, 4].sliced(2, 2));
2138         assert(a == [[1, 2], [3, 4]]);
2139 
2140         assert(a != [9, 2, 3, 4].sliced(2, 2));
2141         assert(a != [[9, 2], [3, 4]]);
2142     }
2143 
2144     static if (doUnittest)
2145     @safe pure nothrow version(mir_ndslice_test) unittest
2146     {
2147         import mir.ndslice.allocation: slice;
2148         import mir.ndslice.topology : iota;
2149         assert(iota(2, 3).slice[0 .. $ - 2] == iota([4, 3], 2)[0 .. $ - 4]);
2150     }
2151 
2152     /++
2153     `Slice!(IotaIterator!size_t)` is the basic type for `[a .. b]` syntax for all ndslice based code.
2154     +/
2155     Slice!(IotaIterator!size_t) opSlice(size_t dimension)(size_t i, size_t j) @safe scope const
2156         if (dimension < N)
2157     in
2158     {
2159         assert(i <= j,
2160             "Slice.opSlice!" ~ dimension.stringof ~ ": the left opSlice boundary must be less than or equal to the right bound.");
2161         enum errorMsg = ": right opSlice boundary must be less than or equal to the length of the given dimension.";
2162         assert(j <= _lengths[dimension],
2163               "Slice.opSlice!" ~ dimension.stringof ~ errorMsg);
2164     }
2165     do
2166     {
2167         return typeof(return)(j - i, typeof(return).Iterator(i));
2168     }
2169 
2170     /++
2171     $(BOLD Fully defined index)
2172     +/
2173     auto ref opIndex()(size_t[N] _indices...) return scope @trusted
2174     {
2175         return _iterator[indexStride(_indices)];
2176     }
2177 
2178     /// ditto
2179     auto ref opIndex()(size_t[N] _indices...) return scope const @trusted
2180     {
2181         static if (is(typeof(_iterator[indexStride(_indices)])))
2182             return _iterator[indexStride(_indices)];
2183         else
2184             return .lightConst(.lightScope(_iterator))[indexStride(_indices)];
2185     }
2186 
2187     /// ditto
2188     auto ref opIndex()(size_t[N] _indices...) return scope immutable @trusted
2189     {
2190         static if (is(typeof(_iterator[indexStride(_indices)])))
2191             return _iterator[indexStride(_indices)];
2192         else
2193             return .lightImmutable(.lightScope(_iterator))[indexStride(_indices)];
2194     }
2195 
2196     /++
2197     $(BOLD Partially defined index)
2198     +/
2199     auto opIndex(size_t I)(size_t[I] _indices...) return scope @trusted
2200         if (I && I < N)
2201     {
2202         enum size_t diff = N - I;
2203         alias Ret = Slice!(Iterator, diff, diff == 1 && kind == Canonical ? Contiguous : kind);
2204         static if (I < S)
2205             return Ret(_lengths[I .. N], _strides[I .. S], _iterator + indexStride(_indices));
2206         else
2207             return Ret(_lengths[I .. N], _iterator + indexStride(_indices));
2208     }
2209 
2210     /// ditto
2211     auto opIndex(size_t I)(size_t[I] _indices...) return scope const
2212         if (I && I < N)
2213     {
2214         return this.lightConst.opIndex(_indices);
2215     }
2216 
2217     /// ditto
2218     auto opIndex(size_t I)(size_t[I] _indices...) return scope immutable
2219         if (I && I < N)
2220     {
2221         return this.lightImmutable.opIndex(_indices);
2222     }
2223 
2224     /++
2225     $(BOLD Partially or fully defined slice.)
2226     +/
2227     auto opIndex(Slices...)(Slices slices) return scope @trusted
2228         if (isPureSlice!Slices)
2229     {
2230         static if (Slices.length)
2231         {
2232             enum size_t j(size_t n) = n - Filter!(isIndex, Slices[0 .. n]).length;
2233             enum size_t F = PureIndexLength!Slices;
2234             enum size_t S = Slices.length;
2235             static assert(N - F > 0);
2236             size_t stride;
2237             static if (Slices.length == 1)
2238                 enum K = kind;
2239             else
2240             static if (kind == Universal || Slices.length == N && isIndex!(Slices[$-1]))
2241                 enum K = Universal;
2242             else
2243             static if (Filter!(isIndex, Slices[0 .. $-1]).length == Slices.length - 1 || N - F == 1)
2244                 enum K = Contiguous;
2245             else
2246                 enum K = Canonical;
2247             alias Ret = Slice!(Iterator, N - F, K);
2248             auto structure_ = Ret._Structure.init;
2249 
2250             enum bool shrink = kind == Canonical && slices.length == N;
2251             static if (shrink)
2252             {
2253                 {
2254                     enum i = Slices.length - 1;
2255                     auto slice = slices[i];
2256                     static if (isIndex!(Slices[i]))
2257                     {
2258                         assert(slice < _lengths[i], "Slice.opIndex: index must be less than length");
2259                         stride += slice;
2260                     }
2261                     else
2262                     {
2263                         stride += slice._iterator._index;
2264                         structure_[0][j!i] = slice._lengths[0];
2265                     }
2266                 }
2267             }
2268             static if (kind == Universal || kind == Canonical)
2269             {
2270                 foreach_reverse (i, slice; slices[0 .. $ - shrink]) //static
2271                 {
2272                     static if (isIndex!(Slices[i]))
2273                     {
2274                         assert(slice < _lengths[i], "Slice.opIndex: index must be less than length");
2275                         stride += _strides[i] * slice;
2276                     }
2277                     else
2278                     {
2279                         stride += _strides[i] * slice._iterator._index;
2280                         structure_[0][j!i] = slice._lengths[0];
2281                         structure_[1][j!i] = _strides[i];
2282                     }
2283                 }
2284             }
2285             else
2286             {
2287                 ptrdiff_t ball = this._stride!(slices.length - 1);
2288                 foreach_reverse (i, slice; slices) //static
2289                 {
2290                     static if (isIndex!(Slices[i]))
2291                     {
2292                         assert(slice < _lengths[i], "Slice.opIndex: index must be less than length");
2293                         stride += ball * slice;
2294                     }
2295                     else
2296                     {
2297                         stride += ball * slice._iterator._index;
2298                         structure_[0][j!i] = slice._lengths[0];
2299                         static if (j!i < Ret.S)
2300                             structure_[1][j!i] = ball;
2301                     }
2302                     static if (i)
2303                         ball *= _lengths[i];
2304                 }
2305             }
2306             foreach (i; Iota!(Slices.length, N))
2307                 structure_[0][i - F] = _lengths[i];
2308             foreach (i; Iota!(Slices.length, N))
2309                 static if (Ret.S > i - F)
2310                     structure_[1][i - F] = _strides[i];
2311 
2312             return Ret(structure_, _iterator + stride);
2313         }
2314         else
2315         {
2316             return this;
2317         }
2318     }
2319 
2320     static if (doUnittest)
2321     ///
2322     pure nothrow version(mir_ndslice_test) unittest
2323     {
2324         import mir.ndslice.allocation;
2325         auto slice = slice!int(5, 3);
2326 
2327         /// Fully defined slice
2328         assert(slice[] == slice);
2329         auto sublice = slice[0..$-2, 1..$];
2330 
2331         /// Partially defined slice
2332         auto row = slice[3];
2333         auto col = slice[0..$, 1];
2334     }
2335 
2336     /++
2337     $(BOLD Indexed slice.)
2338     +/
2339     auto opIndex(Slices...)(return scope Slices slices) return scope
2340         if (isIndexedSlice!Slices)
2341     {
2342         import mir.ndslice.topology: indexed, cartesian;
2343         static if (Slices.length == 1)
2344             alias index = slices[0];
2345         else
2346             auto index = slices.cartesian;
2347         return this.indexed(index);
2348     }
2349 
2350     static if (doUnittest)
2351     ///
2352     @safe pure nothrow version(mir_ndslice_test) unittest
2353     {
2354         import mir.ndslice.allocation: slice;
2355         auto sli = slice!int(4, 3);
2356         auto idx = slice!(size_t[2])(3);
2357         idx[] = [
2358             cast(size_t[2])[0, 2],
2359             cast(size_t[2])[3, 1],
2360             cast(size_t[2])[2, 0]];
2361 
2362         // equivalent to:
2363         // import mir.ndslice.topology: indexed;
2364         // sli.indexed(indx)[] = 1;
2365         sli[idx] = 1;
2366 
2367         assert(sli == [
2368             [0, 0, 1],
2369             [0, 0, 0],
2370             [1, 0, 0],
2371             [0, 1, 0],
2372             ]);
2373 
2374         foreach (row; sli[[1, 3].sliced])
2375             row[] += 2;
2376 
2377         assert(sli == [
2378             [0, 0, 1],
2379             [2, 2, 2], // <--  += 2
2380             [1, 0, 0],
2381             [2, 3, 2], // <--  += 2
2382             ]);
2383     }
2384 
2385     static if (doUnittest)
2386     ///
2387     @safe pure nothrow version(mir_ndslice_test) unittest
2388     {
2389         import mir.ndslice.topology: iota;
2390         import mir.ndslice.allocation: slice;
2391         auto sli = slice!int(5, 6);
2392 
2393         // equivalent to
2394         // import mir.ndslice.topology: indexed, cartesian;
2395         // auto a = [0, sli.length!0 / 2, sli.length!0 - 1].sliced;
2396         // auto b = [0, sli.length!1 / 2, sli.length!1 - 1].sliced;
2397         // auto c = cartesian(a, b);
2398         // auto minor = sli.indexed(c);
2399         auto minor = sli[[0, $ / 2, $ - 1].sliced, [0, $ / 2, $ - 1].sliced];
2400 
2401         minor[] = iota!int([3, 3], 1);
2402 
2403         assert(sli == [
2404         //   ↓     ↓        ↓︎
2405             [1, 0, 0, 2, 0, 3], // <---
2406             [0, 0, 0, 0, 0, 0],
2407             [4, 0, 0, 5, 0, 6], // <---
2408             [0, 0, 0, 0, 0, 0],
2409             [7, 0, 0, 8, 0, 9], // <---
2410             ]);
2411     }
2412 
2413     /++
2414     Element-wise binary operator overloading.
2415     Returns:
2416         lazy slice of the same kind and the same structure
2417     Note:
2418         Does not allocate neither new slice nor a closure.
2419     +/
2420     auto opUnary(string op)()
2421         if (op == "*" || op == "~" || op == "-" || op == "+")
2422     {
2423         import mir.ndslice.topology: map;
2424         static if (op == "+")
2425             return this;
2426         else
2427             return this.map!(op ~ "a");
2428     }
2429 
2430     static if (doUnittest)
2431     ///
2432     version(mir_ndslice_test) unittest
2433     {
2434         import mir.ndslice.topology;
2435 
2436         auto payload = [1, 2, 3, 4];
2437         auto s = iota([payload.length], payload.ptr); // slice of references;
2438         assert(s[1] == payload.ptr + 1);
2439 
2440         auto c = *s; // the same as s.map!"*a"
2441         assert(c[1] == *s[1]);
2442 
2443         *s[1] = 3;
2444         assert(c[1] == *s[1]);
2445     }
2446 
2447     /++
2448     Element-wise operator overloading for scalars.
2449     Params:
2450         value = a scalar
2451     Returns:
2452         lazy slice of the same kind and the same structure
2453     Note:
2454         Does not allocate neither new slice nor a closure.
2455     +/
2456     auto opBinary(string op, T)(scope return T value) return scope
2457         if(!isSlice!T)
2458     {
2459         import mir.ndslice.topology: vmap;
2460         return this.vmap(LeftOp!(op, ImplicitlyUnqual!T)(value));
2461     }
2462 
2463     /// ditto
2464     auto opBinaryRight(string op, T)(scope return T value) return scope
2465         if(!isSlice!T)
2466     {
2467         import mir.ndslice.topology: vmap;
2468         return this.vmap(RightOp!(op, ImplicitlyUnqual!T)(value));
2469     }
2470 
2471     static if (doUnittest)
2472     ///
2473     @safe pure nothrow @nogc version(mir_ndslice_test) unittest
2474     {
2475         import mir.ndslice.topology;
2476 
2477         // 0 1 2 3
2478         auto s = iota([4]);
2479         // 0 1 2 0
2480         assert(s % 3 == iota([4]).map!"a % 3");
2481         // 0 2 4 6
2482         assert(2 * s == iota([4], 0, 2));
2483     }
2484 
2485     static if (doUnittest)
2486     ///
2487     @safe pure nothrow @nogc version(mir_ndslice_test) unittest
2488     {
2489         import mir.ndslice.topology;
2490 
2491         // 0 1 2 3
2492         auto s = iota([4]);
2493         // 0 1 4 9
2494         assert(s ^^ 2.0 == iota([4]).map!"a ^^ 2.0");
2495     }
2496 
2497     /++
2498     Element-wise operator overloading for slices.
2499     Params:
2500         rhs = a slice of the same shape.
2501     Returns:
2502         lazy slice the same shape that has $(LREF Contiguous) kind
2503     Note:
2504         Binary operator overloading is allowed if both slices are contiguous or one-dimensional.
2505         $(BR)
2506         Does not allocate neither new slice nor a closure.
2507     +/
2508     auto opBinary(string op, RIterator, size_t RN, SliceKind rkind)
2509         (scope return Slice!(RIterator, RN, rkind) rhs) return scope
2510         if(N == RN && (kind == Contiguous && rkind == Contiguous || N == 1) && op != "~")
2511     {
2512         import mir.ndslice.topology: zip, map;
2513         return zip(this, rhs).map!("a " ~ op ~ " b");
2514     }
2515 
2516     static if (doUnittest)
2517     ///
2518     @safe pure nothrow @nogc version(mir_ndslice_test) unittest
2519     {
2520         import mir.ndslice.topology: iota, map, zip;
2521 
2522         auto s = iota([2, 3]);
2523         auto c = iota([2, 3], 5, 8);
2524         assert(s * s + c == s.map!"a * a".zip(c).map!"a + b");
2525     }
2526 
2527     /++
2528     Duplicates slice.
2529     Returns: GC-allocated Contiguous mutable slice.
2530     See_also: $(LREF .Slice.idup)
2531     +/
2532     Slice!(Unqual!DeepElement*, N)
2533     dup()() scope @property @trusted
2534     {
2535         if (__ctfe)
2536         {
2537             import mir.ndslice.topology: flattened;
2538             import mir.array.allocation: array;
2539             return this.flattened.array.dup.sliced(this.shape);
2540         }
2541         else
2542         {
2543             import mir.ndslice.allocation: uninitSlice;
2544             import mir.conv: emplaceRef;
2545             alias E = this.DeepElement;
2546 
2547             auto result = (() @trusted => this.shape.uninitSlice!(Unqual!E))();
2548 
2549             import mir.algorithm.iteration: each;
2550             each!(emplaceRef!(Unqual!E))(result, this);
2551 
2552             return result;
2553         }
2554     }
2555 
2556     /// ditto
2557     Slice!(immutable(DeepElement)*, N)
2558     dup()() scope const @property
2559     {
2560         this.lightScope.dup;
2561     }
2562 
2563     /// ditto
2564     Slice!(immutable(DeepElement)*, N)
2565     dup()() scope immutable @property
2566     {
2567         this.lightScope.dup;
2568     }
2569 
2570     static if (doUnittest)
2571     ///
2572     @safe pure version(mir_ndslice_test) unittest
2573     {
2574         import mir.ndslice;
2575         auto x = 3.iota!int;
2576         Slice!(immutable(int)*) imm = x.idup;
2577         Slice!(int*) mut = imm.dup;
2578         assert(imm == x);
2579         assert(mut == x);
2580     }
2581 
2582     /++
2583     Duplicates slice.
2584     Returns: GC-allocated Contiguous immutable slice.
2585     See_also: $(LREF .Slice.dup)
2586     +/
2587     Slice!(immutable(DeepElement)*, N)
2588     idup()() scope @property
2589     {
2590         if (__ctfe)
2591         {
2592             import mir.ndslice.topology: flattened;
2593             import mir.array.allocation: array;
2594             return this.flattened.array.idup.sliced(this.shape);
2595         }
2596         else
2597         {
2598             import mir.ndslice.allocation: uninitSlice;
2599             import mir.conv: emplaceRef;
2600             alias E = this.DeepElement;
2601 
2602             auto result = (() @trusted => this.shape.uninitSlice!(Unqual!E))();
2603 
2604             import mir.algorithm.iteration: each;
2605             each!(emplaceRef!(immutable E))(result, this);
2606             alias R = typeof(return);
2607             return (() @trusted => cast(R) result)();
2608         }
2609     }
2610 
2611     /// ditto
2612     Slice!(immutable(DeepElement)*, N)
2613     idup()() scope const @property
2614     {
2615         this.lightScope.idup;
2616     }
2617 
2618     /// ditto
2619     Slice!(immutable(DeepElement)*, N)
2620     idup()() scope immutable @property
2621     {
2622         this.lightScope.idup;
2623     }
2624 
2625     static if (doUnittest)
2626     ///
2627     @safe pure version(mir_ndslice_test) unittest
2628     {
2629         import mir.ndslice;
2630         auto x = 3.iota!int;
2631         Slice!(int*) mut = x.dup;
2632         Slice!(immutable(int)*) imm = mut.idup;
2633         assert(imm == x);
2634         assert(mut == x);
2635     }
2636 
2637     /++
2638     Provides the index location of a slice, taking into account
2639     `Slice._strides`. Similar to `Slice.indexStride`, except the slice is
2640     indexed by a value. Called by `Slice.accessFlat`.
2641 
2642     Params:
2643         n = location in slice
2644     Returns:
2645         location in slice, adjusted for `Slice._strides`
2646     See_also:
2647         $(SUBREF topology, flattened),
2648         $(LREF .Slice.indexStride),
2649         $(LREF .Slice.accessFlat)
2650     +/
2651     private
2652     ptrdiff_t indexStrideValue(ptrdiff_t n) @safe scope const
2653     {
2654         assert(n < elementCount, "indexStrideValue: n must be less than elementCount");
2655         assert(n >= 0, "indexStrideValue: n must be greater than or equal to zero");
2656 
2657         static if (kind == Contiguous) {
2658             return n;
2659         } else {
2660             ptrdiff_t _shift;
2661             foreach_reverse (i; Iota!(1, N))
2662             {
2663                 immutable v = n / ptrdiff_t(_lengths[i]);
2664                 n %= ptrdiff_t(_lengths[i]);
2665                 static if (i == S)
2666                     _shift += n;
2667                 else
2668                     _shift += n * _strides[i];
2669                 n = v;
2670             }
2671             _shift += n * _strides[0];
2672             return _shift;
2673         }
2674     }
2675 
2676     /++
2677     Provides access to a slice as if it were `flattened`.
2678 
2679     Params:
2680         index = location in slice
2681     Returns:
2682         value of flattened slice at `index`
2683     See_also: $(SUBREF topology, flattened)
2684     +/
2685     auto ref accessFlat(size_t index) return scope @trusted
2686     {
2687         return _iterator[indexStrideValue(index)];
2688     }
2689 
2690     ///
2691     version(mir_ndslice_test)
2692     @safe pure @nogc nothrow
2693     unittest
2694     {
2695         import mir.ndslice.topology: iota, flattened;
2696 
2697         auto x = iota(2, 3, 4);
2698         assert(x.accessFlat(9) == x.flattened[9]);
2699     }
2700 
2701     static if (isMutable!DeepElement)
2702     {
2703         private void opIndexOpAssignImplSlice(string op, RIterator, size_t RN, SliceKind rkind)
2704             (Slice!(RIterator, RN, rkind) value) scope
2705         {
2706             static if (N > 1 && RN == N && kind == Contiguous && rkind == Contiguous)
2707             {
2708                 import mir.ndslice.topology : flattened;
2709                 this.flattened.opIndexOpAssignImplSlice!op(value.flattened);
2710             }
2711             else
2712             {
2713                 auto ls = this;
2714                 do
2715                 {
2716                     static if (N > RN)
2717                     {
2718                         ls.front.opIndexOpAssignImplSlice!op(value);
2719                     }
2720                     else
2721                     {
2722                         static if (ls.N == 1)
2723                         {
2724                             static if (isInstanceOf!(SliceIterator, Iterator))
2725                             {
2726                                 static if (isSlice!(typeof(value.front)))
2727                                     ls.front.opIndexOpAssignImplSlice!op(value.front);
2728                                 else
2729                                 static if (isDynamicArray!(typeof(value.front)))
2730                                     ls.front.opIndexOpAssignImplSlice!op(value.front);
2731                                 else
2732                                     ls.front.opIndexOpAssignImplValue!op(value.front);
2733                             }
2734                             else
2735                             static if (op == "^^" && isFloatingPoint!(typeof(ls.front)) && isFloatingPoint!(typeof(value.front)))
2736                             {
2737                                 import mir.math.common: pow;
2738                                 ls.front = pow(ls.front, value.front);
2739                             }
2740                             else
2741                                 mixin("ls.front " ~ op ~ "= value.front;");
2742                         }
2743                         else
2744                         static if (RN == 1)
2745                             ls.front.opIndexOpAssignImplValue!op(value.front);
2746                         else
2747                             ls.front.opIndexOpAssignImplSlice!op(value.front);
2748                         value.popFront;
2749                     }
2750                     ls.popFront;
2751                 }
2752                 while (ls._lengths[0]);
2753             }
2754         }
2755 
2756         /++
2757         Assignment of a value of `Slice` type to a $(B fully defined slice).
2758         +/
2759         void opIndexAssign(RIterator, size_t RN, SliceKind rkind, Slices...)
2760             (Slice!(RIterator, RN, rkind) value, Slices slices) return scope
2761             if (isFullPureSlice!Slices || isIndexedSlice!Slices)
2762         {
2763             auto sl = this.lightScope.opIndex(slices);
2764             assert(_checkAssignLengths(sl, value));
2765             if(!sl.anyRUEmpty)
2766                 sl.opIndexOpAssignImplSlice!""(value);
2767         }
2768 
2769         static if (doUnittest)
2770         ///
2771         @safe pure nothrow version(mir_ndslice_test) unittest
2772         {
2773             import mir.ndslice.allocation;
2774             auto a = slice!int(2, 3);
2775             auto b = [1, 2, 3, 4].sliced(2, 2);
2776 
2777             a[0..$, 0..$-1] = b;
2778             assert(a == [[1, 2, 0], [3, 4, 0]]);
2779 
2780             // fills both rows with b[0]
2781             a[0..$, 0..$-1] = b[0];
2782             assert(a == [[1, 2, 0], [1, 2, 0]]);
2783 
2784             a[1, 0..$-1] = b[1];
2785             assert(a[1] == [3, 4, 0]);
2786 
2787             a[1, 0..$-1][] = b[0];
2788             assert(a[1] == [1, 2, 0]);
2789         }
2790 
2791         static if (doUnittest)
2792         /// Left slice is packed
2793         @safe pure nothrow version(mir_ndslice_test) unittest
2794         {
2795             import mir.ndslice.topology : blocks, iota;
2796             import mir.ndslice.allocation : slice;
2797             auto a = slice!int(4, 4);
2798             a.blocks(2, 2)[] = iota!int(2, 2);
2799 
2800             assert(a ==
2801                     [[0, 0, 1, 1],
2802                      [0, 0, 1, 1],
2803                      [2, 2, 3, 3],
2804                      [2, 2, 3, 3]]);
2805         }
2806 
2807         static if (doUnittest)
2808         /// Both slices are packed
2809         @safe pure nothrow version(mir_ndslice_test) unittest
2810         {
2811             import mir.ndslice.topology : blocks, iota, pack;
2812             import mir.ndslice.allocation : slice;
2813             auto a = slice!int(4, 4);
2814             a.blocks(2, 2)[] = iota!int(2, 2, 2).pack!1;
2815 
2816             assert(a ==
2817                     [[0, 1, 2, 3],
2818                      [0, 1, 2, 3],
2819                      [4, 5, 6, 7],
2820                      [4, 5, 6, 7]]);
2821         }
2822 
2823         void opIndexOpAssignImplArray(string op, T, Slices...)(T[] value) scope
2824         {
2825             auto ls = this;
2826             assert(ls.length == value.length, __FUNCTION__ ~ ": argument must have the same length.");
2827             static if (N == 1)
2828             {
2829                 do
2830                 {
2831                     static if (ls.N == 1)
2832                     {
2833                         static if (isInstanceOf!(SliceIterator, Iterator))
2834                         {
2835                             static if (isSlice!(typeof(value[0])))
2836                                 ls.front.opIndexOpAssignImplSlice!op(value[0]);
2837                             else
2838                             static if (isDynamicArray!(typeof(value[0])))
2839                                 ls.front.opIndexOpAssignImplSlice!op(value[0]);
2840                             else
2841                                 ls.front.opIndexOpAssignImplValue!op(value[0]);
2842                         }
2843                         else
2844                         static if (op == "^^" && isFloatingPoint!(typeof(ls.front)) && isFloatingPoint!(typeof(value[0])))
2845                         {
2846                             import mir.math.common: pow;
2847                             ls.front = pow(ls.front, value[0]);
2848                         }
2849                         else
2850                             mixin("ls.front " ~ op ~ "= value[0];");
2851                     }
2852                     else
2853                         mixin("ls.front[] " ~ op ~ "= value[0];");
2854                     value = value[1 .. $];
2855                     ls.popFront;
2856                 }
2857                 while (ls.length);
2858             }
2859             else
2860             static if (N == DynamicArrayDimensionsCount!(T[]))
2861             {
2862                 do
2863                 {
2864                     ls.front.opIndexOpAssignImplArray!op(value[0]);
2865                     value = value[1 .. $];
2866                     ls.popFront;
2867                 }
2868                 while (ls.length);
2869             }
2870             else
2871             {
2872                 do
2873                 {
2874                     ls.front.opIndexOpAssignImplArray!op(value);
2875                     ls.popFront;
2876                 }
2877                 while (ls.length);
2878             }
2879         }
2880 
2881         /++
2882         Assignment of a regular multidimensional array to a $(B fully defined slice).
2883         +/
2884         void opIndexAssign(T, Slices...)(T[] value, Slices slices) return scope
2885             if ((isFullPureSlice!Slices || isIndexedSlice!Slices)
2886                 && (!isDynamicArray!DeepElement || isDynamicArray!T)
2887                 && DynamicArrayDimensionsCount!(T[]) - DynamicArrayDimensionsCount!DeepElement <= typeof(this.opIndex(slices)).N)
2888         {
2889             auto sl = this.lightScope.opIndex(slices);
2890             sl.opIndexOpAssignImplArray!""(value);
2891         }
2892 
2893         static if (doUnittest)
2894         ///
2895         pure nothrow version(mir_ndslice_test) unittest
2896         {
2897             import mir.ndslice.allocation;
2898             auto a = slice!int(2, 3);
2899             auto b = [[1, 2], [3, 4]];
2900 
2901             a[] = [[1, 2, 3], [4, 5, 6]];
2902             assert(a == [[1, 2, 3], [4, 5, 6]]);
2903 
2904             a[0..$, 0..$-1] = [[1, 2], [3, 4]];
2905             assert(a == [[1, 2, 3], [3, 4, 6]]);
2906 
2907             a[0..$, 0..$-1] = [1, 2];
2908             assert(a == [[1, 2, 3], [1, 2, 6]]);
2909 
2910             a[1, 0..$-1] = [3, 4];
2911             assert(a[1] == [3, 4, 6]);
2912 
2913             a[1, 0..$-1][] = [3, 4];
2914             assert(a[1] == [3, 4, 6]);
2915         }
2916 
2917         static if (doUnittest)
2918         /// Packed slices
2919         pure nothrow version(mir_ndslice_test) unittest
2920         {
2921             import mir.ndslice.allocation : slice;
2922             import mir.ndslice.topology : blocks;
2923             auto a = slice!int(4, 4);
2924             a.blocks(2, 2)[] = [[0, 1], [2, 3]];
2925 
2926             assert(a ==
2927                     [[0, 0, 1, 1],
2928                      [0, 0, 1, 1],
2929                      [2, 2, 3, 3],
2930                      [2, 2, 3, 3]]);
2931         }
2932 
2933 
2934         private void opIndexOpAssignImplConcatenation(string op, T)(T value) scope
2935         {
2936             auto sl = this;
2937             static if (concatenationDimension!T)
2938             {
2939                 if (!sl.empty) do
2940                 {
2941                     static if (op == "")
2942                         sl.front.opIndexAssign(value.front);
2943                     else
2944                         sl.front.opIndexOpAssign!op(value.front);
2945                     value.popFront;
2946                     sl.popFront;
2947                 }
2948                 while(!sl.empty);
2949             }
2950             else
2951             {
2952                 foreach (ref slice; value._slices)
2953                 {
2954                     static if (op == "")
2955                         sl[0 .. slice.length].opIndexAssign(slice);
2956                     else
2957                         sl[0 .. slice.length].opIndexOpAssign!op(slice);
2958 
2959                     sl = sl[slice.length .. $];
2960                 }
2961                 assert(sl.empty);
2962             }
2963         }
2964 
2965         ///
2966         void opIndexAssign(T, Slices...)(T concatenation, Slices slices) return scope
2967             if ((isFullPureSlice!Slices || isIndexedSlice!Slices) && isConcatenation!T)
2968         {
2969             auto sl = this.lightScope.opIndex(slices);
2970             static assert(typeof(sl).N == T.N, "incompatible dimension count");
2971             sl.opIndexOpAssignImplConcatenation!""(concatenation);
2972         }
2973 
2974         static if (!isNumeric!DeepElement)
2975         /++
2976         Assignment of a value (e.g. a number) to a $(B fully defined slice).
2977         +/
2978         void opIndexAssign(T, Slices...)(T value, Slices slices) scope
2979             if ((isFullPureSlice!Slices || isIndexedSlice!Slices)
2980                 && (!isDynamicArray!T || isDynamicArray!DeepElement)
2981                 && DynamicArrayDimensionsCount!T == DynamicArrayDimensionsCount!DeepElement
2982                 && !isSlice!T
2983                 && !isConcatenation!T)
2984         {
2985             auto sl = this.lightScope.opIndex(slices);
2986             if(!sl.anyRUEmpty)
2987                 sl.opIndexOpAssignImplValue!""(value);
2988         }
2989         else
2990         void opIndexAssign(Slices...)(DeepElement value, Slices slices) scope
2991             if (isFullPureSlice!Slices || isIndexedSlice!Slices)
2992         {
2993             auto sl = this.lightScope.opIndex(slices);
2994             if(!sl.anyRUEmpty)
2995                 sl.opIndexOpAssignImplValue!""(value);
2996         }
2997 
2998         static if (doUnittest)
2999         ///
3000         @safe pure nothrow
3001         version(mir_ndslice_test) unittest
3002         {
3003             import mir.ndslice.allocation;
3004             auto a = slice!int(2, 3);
3005 
3006             a[] = 9;
3007             assert(a == [[9, 9, 9], [9, 9, 9]]);
3008 
3009             a[0..$, 0..$-1] = 1;
3010             assert(a == [[1, 1, 9], [1, 1, 9]]);
3011 
3012             a[0..$, 0..$-1] = 2;
3013             assert(a == [[2, 2, 9], [2, 2, 9]]);
3014 
3015             a[1, 0..$-1] = 3;
3016             //assert(a[1] == [3, 3, 9]);
3017 
3018             a[1, 0..$-1] = 4;
3019             //assert(a[1] == [4, 4, 9]);
3020 
3021             a[1, 0..$-1][] = 5;
3022 
3023             assert(a[1] == [5, 5, 9]);
3024         }
3025 
3026         static if (doUnittest)
3027         /// Packed slices have the same behavior.
3028         @safe pure nothrow version(mir_ndslice_test) unittest
3029         {
3030             import mir.ndslice.allocation;
3031             import mir.ndslice.topology : pack;
3032             auto a = slice!int(2, 3).pack!1;
3033 
3034             a[] = 9;
3035             //assert(a == [[9, 9, 9], [9, 9, 9]]);
3036         }
3037 
3038         /++
3039         Assignment of a value (e.g. a number) to a $(B fully defined index).
3040         +/
3041         auto ref opIndexAssign(T)(T value, size_t[N] _indices...) return scope @trusted
3042         {
3043             // check assign safety
3044             static auto ref fun(ref DeepElement t, ref T v) @safe
3045             {
3046                 return t = v;
3047             }
3048             return _iterator[indexStride(_indices)] = value;
3049         }
3050         ///ditto
3051         auto ref opIndexAssign()(DeepElement value, size_t[N] _indices...) return scope @trusted
3052         {
3053             import mir.functional: forward;
3054             // check assign safety
3055             static auto ref fun(ref DeepElement t, ref DeepElement v) @safe
3056             {
3057                 return t = v;
3058             }
3059             return _iterator[indexStride(_indices)] = forward!value;
3060         }
3061 
3062         static if (doUnittest)
3063         ///
3064         @safe pure nothrow version(mir_ndslice_test) unittest
3065         {
3066             import mir.ndslice.allocation;
3067             auto a = slice!int(2, 3);
3068 
3069             a[1, 2] = 3;
3070             assert(a[1, 2] == 3);
3071         }
3072 
3073         static if (doUnittest)
3074         @safe pure nothrow version(mir_ndslice_test) unittest
3075         {
3076             auto a = new int[6].sliced(2, 3);
3077 
3078             a[[1, 2]] = 3;
3079             assert(a[[1, 2]] == 3);
3080         }
3081 
3082         /++
3083         Op Assignment `op=` of a value (e.g. a number) to a $(B fully defined index).
3084         +/
3085         auto ref opIndexOpAssign(string op, T)(T value, size_t[N] _indices...) return scope @trusted
3086         {
3087             // check op safety
3088             static auto ref fun(ref DeepElement t, ref T v) @safe
3089             {
3090                 return mixin(`t` ~ op ~ `= v`);
3091             }
3092             auto str = indexStride(_indices);
3093             static if (op == "^^" && isFloatingPoint!DeepElement && isFloatingPoint!(typeof(value)))
3094             {
3095                 import mir.math.common: pow;
3096                 _iterator[str] = pow(_iterator[str], value);
3097             }
3098             else
3099                 return mixin (`_iterator[str] ` ~ op ~ `= value`);
3100         }
3101 
3102         static if (doUnittest)
3103         ///
3104         @safe pure nothrow version(mir_ndslice_test) unittest
3105         {
3106             import mir.ndslice.allocation;
3107             auto a = slice!int(2, 3);
3108 
3109             a[1, 2] += 3;
3110             assert(a[1, 2] == 3);
3111         }
3112 
3113         static if (doUnittest)
3114         @safe pure nothrow version(mir_ndslice_test) unittest
3115         {
3116             auto a = new int[6].sliced(2, 3);
3117 
3118             a[[1, 2]] += 3;
3119             assert(a[[1, 2]] == 3);
3120         }
3121 
3122         /++
3123         Op Assignment `op=` of a value of `Slice` type to a $(B fully defined slice).
3124         +/
3125         void opIndexOpAssign(string op, RIterator, SliceKind rkind, size_t RN, Slices...)
3126             (Slice!(RIterator, RN, rkind) value, Slices slices) return scope
3127             if (isFullPureSlice!Slices || isIndexedSlice!Slices)
3128         {
3129             auto sl = this.lightScope.opIndex(slices);
3130             assert(_checkAssignLengths(sl, value));
3131             if(!sl.anyRUEmpty)
3132                 sl.opIndexOpAssignImplSlice!op(value);
3133         }
3134 
3135         static if (doUnittest)
3136         ///
3137         @safe pure nothrow version(mir_ndslice_test) unittest
3138         {
3139             import mir.ndslice.allocation;
3140             auto a = slice!int(2, 3);
3141             auto b = [1, 2, 3, 4].sliced(2, 2);
3142 
3143             a[0..$, 0..$-1] += b;
3144             assert(a == [[1, 2, 0], [3, 4, 0]]);
3145 
3146             a[0..$, 0..$-1] += b[0];
3147             assert(a == [[2, 4, 0], [4, 6, 0]]);
3148 
3149             a[1, 0..$-1] += b[1];
3150             assert(a[1] == [7, 10, 0]);
3151 
3152             a[1, 0..$-1][] += b[0];
3153             assert(a[1] == [8, 12, 0]);
3154         }
3155 
3156         static if (doUnittest)
3157         /// Left slice is packed
3158         @safe pure nothrow version(mir_ndslice_test) unittest
3159         {
3160             import mir.ndslice.allocation : slice;
3161             import mir.ndslice.topology : blocks, iota;
3162             auto a = slice!size_t(4, 4);
3163             a.blocks(2, 2)[] += iota(2, 2);
3164 
3165             assert(a ==
3166                     [[0, 0, 1, 1],
3167                      [0, 0, 1, 1],
3168                      [2, 2, 3, 3],
3169                      [2, 2, 3, 3]]);
3170         }
3171 
3172         static if (doUnittest)
3173         /// Both slices are packed
3174         @safe pure nothrow version(mir_ndslice_test) unittest
3175         {
3176             import mir.ndslice.allocation : slice;
3177             import mir.ndslice.topology : blocks, iota, pack;
3178             auto a = slice!size_t(4, 4);
3179             a.blocks(2, 2)[] += iota(2, 2, 2).pack!1;
3180 
3181             assert(a ==
3182                     [[0, 1, 2, 3],
3183                      [0, 1, 2, 3],
3184                      [4, 5, 6, 7],
3185                      [4, 5, 6, 7]]);
3186         }
3187 
3188         /++
3189         Op Assignment `op=` of a regular multidimensional array to a $(B fully defined slice).
3190         +/
3191         void opIndexOpAssign(string op, T, Slices...)(T[] value, Slices slices) return scope
3192             if (isFullPureSlice!Slices
3193                 && (!isDynamicArray!DeepElement || isDynamicArray!T)
3194                 && DynamicArrayDimensionsCount!(T[]) - DynamicArrayDimensionsCount!DeepElement <= typeof(this.opIndex(slices)).N)
3195         {
3196             auto sl = this.lightScope.opIndex(slices);
3197             sl.opIndexOpAssignImplArray!op(value);
3198         }
3199 
3200         static if (doUnittest)
3201         ///
3202         @safe pure nothrow version(mir_ndslice_test) unittest
3203         {
3204             import mir.ndslice.allocation : slice;
3205             auto a = slice!int(2, 3);
3206 
3207             a[0..$, 0..$-1] += [[1, 2], [3, 4]];
3208             assert(a == [[1, 2, 0], [3, 4, 0]]);
3209 
3210             a[0..$, 0..$-1] += [1, 2];
3211             assert(a == [[2, 4, 0], [4, 6, 0]]);
3212 
3213             a[1, 0..$-1] += [3, 4];
3214             assert(a[1] == [7, 10, 0]);
3215 
3216             a[1, 0..$-1][] += [1, 2];
3217             assert(a[1] == [8, 12, 0]);
3218         }
3219 
3220         static if (doUnittest)
3221         /// Packed slices
3222         @safe pure nothrow
3223         version(mir_ndslice_test) unittest
3224         {
3225             import mir.ndslice.allocation : slice;
3226             import mir.ndslice.topology : blocks;
3227             auto a = slice!int(4, 4);
3228             a.blocks(2, 2)[] += [[0, 1], [2, 3]];
3229 
3230             assert(a ==
3231                     [[0, 0, 1, 1],
3232                      [0, 0, 1, 1],
3233                      [2, 2, 3, 3],
3234                      [2, 2, 3, 3]]);
3235         }
3236 
3237         private void opIndexOpAssignImplValue(string op, T)(T value) return scope
3238         {
3239             static if (N > 1 && kind == Contiguous)
3240             {
3241                 import mir.ndslice.topology : flattened;
3242                 this.flattened.opIndexOpAssignImplValue!op(value);
3243             }
3244             else
3245             {
3246                 auto ls = this;
3247                 do
3248                 {
3249                     static if (N == 1)
3250                     {
3251                         static if (isInstanceOf!(SliceIterator, Iterator))
3252                             ls.front.opIndexOpAssignImplValue!op(value);
3253                         else
3254                             mixin (`ls.front ` ~ op ~ `= value;`);
3255                     }
3256                     else
3257                         ls.front.opIndexOpAssignImplValue!op(value);
3258                     ls.popFront;
3259                 }
3260                 while(ls._lengths[0]);
3261             }
3262         }
3263 
3264         /++
3265         Op Assignment `op=` of a value (e.g. a number) to a $(B fully defined slice).
3266        +/
3267         void opIndexOpAssign(string op, T, Slices...)(T value, Slices slices) return scope
3268             if ((isFullPureSlice!Slices || isIndexedSlice!Slices)
3269                 && (!isDynamicArray!T || isDynamicArray!DeepElement)
3270                 && DynamicArrayDimensionsCount!T == DynamicArrayDimensionsCount!DeepElement
3271                 && !isSlice!T
3272                 && !isConcatenation!T)
3273         {
3274             auto sl = this.lightScope.opIndex(slices);
3275             if(!sl.anyRUEmpty)
3276                 sl.opIndexOpAssignImplValue!op(value);
3277         }
3278 
3279         static if (doUnittest)
3280         ///
3281         @safe pure nothrow version(mir_ndslice_test) unittest
3282         {
3283             import mir.ndslice.allocation;
3284             auto a = slice!int(2, 3);
3285 
3286             a[] += 1;
3287             assert(a == [[1, 1, 1], [1, 1, 1]]);
3288 
3289             a[0..$, 0..$-1] += 2;
3290             assert(a == [[3, 3, 1], [3, 3, 1]]);
3291 
3292             a[1, 0..$-1] += 3;
3293             assert(a[1] == [6, 6, 1]);
3294         }
3295 
3296         ///
3297         void opIndexOpAssign(string op,T, Slices...)(T concatenation, Slices slices) return scope
3298             if ((isFullPureSlice!Slices || isIndexedSlice!Slices) && isConcatenation!T)
3299         {
3300             auto sl = this.lightScope.opIndex(slices);
3301             static assert(typeof(sl).N == concatenation.N);
3302             sl.opIndexOpAssignImplConcatenation!op(concatenation);
3303         }
3304 
3305         static if (doUnittest)
3306         /// Packed slices have the same behavior.
3307         @safe pure nothrow version(mir_ndslice_test) unittest
3308         {
3309             import mir.ndslice.allocation;
3310             import mir.ndslice.topology : pack;
3311             auto a = slice!int(2, 3).pack!1;
3312 
3313             a[] += 9;
3314             assert(a == [[9, 9, 9], [9, 9, 9]]);
3315         }
3316 
3317 
3318         /++
3319         Increment `++` and Decrement `--` operators for a $(B fully defined index).
3320         +/
3321         auto ref opIndexUnary(string op)(size_t[N] _indices...) return scope
3322             @trusted
3323             // @@@workaround@@@ for Issue 16473
3324             //if (op == `++` || op == `--`)
3325         {
3326             // check op safety
3327             static auto ref fun(DeepElement t) @safe
3328             {
3329                 return mixin(op ~ `t`);
3330             }
3331             return mixin (op ~ `_iterator[indexStride(_indices)]`);
3332         }
3333 
3334         static if (doUnittest)
3335         ///
3336         @safe pure nothrow version(mir_ndslice_test) unittest
3337         {
3338             import mir.ndslice.allocation;
3339             auto a = slice!int(2, 3);
3340 
3341             ++a[1, 2];
3342             assert(a[1, 2] == 1);
3343         }
3344 
3345         // Issue 16473
3346         static if (doUnittest)
3347         @safe pure nothrow version(mir_ndslice_test) unittest
3348         {
3349             import mir.ndslice.allocation;
3350             auto sl = slice!double(2, 5);
3351             auto d = -sl[0, 1];
3352         }
3353 
3354         static if (doUnittest)
3355         @safe pure nothrow version(mir_ndslice_test) unittest
3356         {
3357             auto a = new int[6].sliced(2, 3);
3358 
3359             ++a[[1, 2]];
3360             assert(a[[1, 2]] == 1);
3361         }
3362 
3363         private void opIndexUnaryImpl(string op, Slices...)(Slices slices) scope
3364         {
3365             auto ls = this;
3366             do
3367             {
3368                 static if (N == 1)
3369                 {
3370                     static if (isInstanceOf!(SliceIterator, Iterator))
3371                         ls.front.opIndexUnaryImpl!op;
3372                     else
3373                         mixin (op ~ `ls.front;`);
3374                 }
3375                 else
3376                     ls.front.opIndexUnaryImpl!op;
3377                 ls.popFront;
3378             }
3379             while(ls._lengths[0]);
3380         }
3381 
3382         /++
3383         Increment `++` and Decrement `--` operators for a $(B fully defined slice).
3384         +/
3385         void opIndexUnary(string op, Slices...)(Slices slices) return scope
3386             if (isFullPureSlice!Slices && (op == `++` || op == `--`))
3387         {
3388             auto sl = this.lightScope.opIndex(slices);
3389             if (!sl.anyRUEmpty)
3390                 sl.opIndexUnaryImpl!op;
3391         }
3392 
3393         static if (doUnittest)
3394         ///
3395         @safe pure nothrow
3396         version(mir_ndslice_test) unittest
3397         {
3398             import mir.ndslice.allocation;
3399             auto a = slice!int(2, 3);
3400 
3401             ++a[];
3402             assert(a == [[1, 1, 1], [1, 1, 1]]);
3403 
3404             --a[1, 0..$-1];
3405 
3406             assert(a[1] == [0, 0, 1]);
3407         }
3408     }
3409 }
3410 
3411 /// ditto
3412 alias Slice = mir_slice;
3413 
3414 /++
3415 Slicing, indexing, and arithmetic operations.
3416 +/
3417 pure nothrow version(mir_ndslice_test) unittest
3418 {
3419     import mir.ndslice.allocation;
3420     import mir.ndslice.dynamic : transposed;
3421     import mir.ndslice.topology : iota, universal;
3422     auto tensor = iota(3, 4, 5).slice;
3423 
3424     assert(tensor[1, 2] == tensor[1][2]);
3425     assert(tensor[1, 2, 3] == tensor[1][2][3]);
3426 
3427     assert( tensor[0..$, 0..$, 4] == tensor.universal.transposed!2[4]);
3428     assert(&tensor[0..$, 0..$, 4][1, 2] is &tensor[1, 2, 4]);
3429 
3430     tensor[1, 2, 3]++; //`opIndex` returns value by reference.
3431     --tensor[1, 2, 3]; //`opUnary`
3432 
3433     ++tensor[];
3434     tensor[] -= 1;
3435 
3436     // `opIndexAssing` accepts only fully defined indices and slices.
3437     // Use an additional empty slice `[]`.
3438     static assert(!__traits(compiles, tensor[0 .. 2] *= 2));
3439 
3440     tensor[0 .. 2][] *= 2;          //OK, empty slice
3441     tensor[0 .. 2, 3, 0..$] /= 2; //OK, 3 index or slice positions are defined.
3442 
3443     //fully defined index may be replaced by a static array
3444     size_t[3] index = [1, 2, 3];
3445     assert(tensor[index] == tensor[1, 2, 3]);
3446 }
3447 
3448 /++
3449 Operations with rvalue slices.
3450 +/
3451 pure nothrow version(mir_ndslice_test) unittest
3452 {
3453     import mir.ndslice.allocation;
3454     import mir.ndslice.topology: universal;
3455     import mir.ndslice.dynamic: transposed, everted;
3456 
3457     auto tensor = slice!int(3, 4, 5).universal;
3458     auto matrix = slice!int(3, 4).universal;
3459     auto vector = slice!int(3);
3460 
3461     foreach (i; 0..3)
3462         vector[i] = i;
3463 
3464     // fills matrix columns
3465     matrix.transposed[] = vector;
3466 
3467     // fills tensor with vector
3468     // transposed tensor shape is (4, 5, 3)
3469     //            vector shape is (      3)
3470     tensor.transposed!(1, 2)[] = vector;
3471 
3472     // transposed tensor shape is (5, 3, 4)
3473     //            matrix shape is (   3, 4)
3474     tensor.transposed!2[] += matrix;
3475 
3476     // transposed tensor shape is (5, 4, 3)
3477     // transposed matrix shape is (   4, 3)
3478     tensor.everted[] ^= matrix.transposed; // XOR
3479 }
3480 
3481 /++
3482 Creating a slice from text.
3483 See also $(MREF std, format).
3484 +/
3485 version(mir_ndslice_test) unittest
3486 {
3487     import mir.algorithm.iteration: filter, all;
3488     import mir.array.allocation;
3489     import mir.exception;
3490     import mir.functional: not;
3491     import mir.ndslice.allocation;
3492     import mir.parse;
3493     import mir.primitives: empty;
3494 
3495     import std.algorithm: map;
3496     import std.string: lineSplitter, split;
3497 
3498         // std.functional, std.string, std.range;
3499 
3500     Slice!(int*, 2) toMatrix(string str)
3501     {
3502         string[][] data = str.lineSplitter.filter!(not!empty).map!split.array;
3503 
3504         size_t rows    = data   .length.enforce!"empty input";
3505         size_t columns = data[0].length.enforce!"empty first row";
3506 
3507         data.all!(a => a.length == columns).enforce!"rows have different lengths";
3508         auto slice = slice!int(rows, columns);
3509         foreach (i, line; data)
3510             foreach (j, num; line)
3511                 slice[i, j] = num.fromString!int;
3512         return slice;
3513     }
3514 
3515     auto input = "\r1 2  3\r\n 4 5 6\n";
3516 
3517     auto matrix = toMatrix(input);
3518     assert(matrix == [[1, 2, 3], [4, 5, 6]]);
3519 
3520     // back to text
3521     import std.format;
3522     auto text2 = format("%(%(%s %)\n%)\n", matrix);
3523     assert(text2 == "1 2 3\n4 5 6\n");
3524 }
3525 
3526 // Slicing
3527 @safe @nogc pure nothrow version(mir_ndslice_test) unittest
3528 {
3529     import mir.ndslice.topology : iota;
3530     auto a = iota(10, 20, 30, 40);
3531     auto b = a[0..$, 10, 4 .. 27, 4];
3532     auto c = b[2 .. 9, 5 .. 10];
3533     auto d = b[3..$, $-2];
3534     assert(b[4, 17] == a[4, 10, 21, 4]);
3535     assert(c[1, 2] == a[3, 10, 11, 4]);
3536     assert(d[3] == a[6, 10, 25, 4]);
3537 }
3538 
3539 // Operator overloading. # 1
3540 pure nothrow version(mir_ndslice_test) unittest
3541 {
3542     import mir.ndslice.allocation;
3543     import mir.ndslice.topology : iota;
3544 
3545     auto fun(ref sizediff_t x) { x *= 3; }
3546 
3547     auto tensor = iota(8, 9, 10).slice;
3548 
3549     ++tensor[];
3550     fun(tensor[0, 0, 0]);
3551 
3552     assert(tensor[0, 0, 0] == 3);
3553 
3554     tensor[0, 0, 0] *= 4;
3555     tensor[0, 0, 0]--;
3556     assert(tensor[0, 0, 0] == 11);
3557 }
3558 
3559 // Operator overloading. # 2
3560 pure nothrow version(mir_ndslice_test) unittest
3561 {
3562     import mir.ndslice.topology: map, iota;
3563     import mir.array.allocation : array;
3564     //import std.bigint;
3565 
3566     auto matrix = 72
3567         .iota
3568         //.map!(i => BigInt(i))
3569         .array
3570         .sliced(8, 9);
3571 
3572     matrix[3 .. 6, 2] += 100;
3573     foreach (i; 0 .. 8)
3574         foreach (j; 0 .. 9)
3575             if (i >= 3 && i < 6 && j == 2)
3576                 assert(matrix[i, j] >= 100);
3577             else
3578                 assert(matrix[i, j] < 100);
3579 }
3580 
3581 // Operator overloading. # 3
3582 pure nothrow version(mir_ndslice_test) unittest
3583 {
3584     import mir.ndslice.allocation;
3585     import mir.ndslice.topology : iota;
3586 
3587     auto matrix = iota(8, 9).slice;
3588     matrix[] = matrix;
3589     matrix[] += matrix;
3590     assert(matrix[2, 3] == (2 * 9 + 3) * 2);
3591 
3592     auto vec = iota([9], 100);
3593     matrix[] = vec;
3594     foreach (v; matrix)
3595         assert(v == vec);
3596 
3597     matrix[] += vec;
3598     foreach (vector; matrix)
3599         foreach (elem; vector)
3600             assert(elem >= 200);
3601 }
3602 
3603 // Type deduction
3604 version(mir_ndslice_test) unittest
3605 {
3606     // Arrays
3607     foreach (T; AliasSeq!(int, const int, immutable int))
3608         static assert(is(typeof((T[]).init.sliced(3, 4)) == Slice!(T*, 2)));
3609 
3610     // Container Array
3611     import std.container.array;
3612     Array!int ar;
3613     ar.length = 12;
3614     auto arSl = ar[].slicedField(3, 4);
3615 }
3616 
3617 // Test for map #1
3618 version(mir_ndslice_test) unittest
3619 {
3620     import mir.ndslice.topology: map, byDim;
3621     auto slice = [1, 2, 3, 4].sliced(2, 2);
3622 
3623     auto r = slice.byDim!0.map!(a => a.map!(a => a * 6));
3624     assert(r.front.front == 6);
3625     assert(r.front.back == 12);
3626     assert(r.back.front == 18);
3627     assert(r.back.back == 24);
3628     assert(r[0][0] ==  6);
3629     assert(r[0][1] == 12);
3630     assert(r[1][0] == 18);
3631     assert(r[1][1] == 24);
3632 
3633     import std.range.primitives;
3634     static assert(hasSlicing!(typeof(r)));
3635     static assert(isForwardRange!(typeof(r)));
3636     static assert(isRandomAccessRange!(typeof(r)));
3637 }
3638 
3639 // Test for map #2
3640 version(mir_ndslice_test) unittest
3641 {
3642     import mir.ndslice.topology: map, byDim;
3643     import std.range.primitives;
3644     auto data = [1, 2, 3, 4];
3645     static assert(hasSlicing!(typeof(data)));
3646     static assert(isForwardRange!(typeof(data)));
3647     static assert(isRandomAccessRange!(typeof(data)));
3648     auto slice = data.sliced(2, 2);
3649     static assert(hasSlicing!(typeof(slice)));
3650     static assert(isForwardRange!(typeof(slice)));
3651     static assert(isRandomAccessRange!(typeof(slice)));
3652     auto r = slice.byDim!0.map!(a => a.map!(a => a * 6));
3653     static assert(hasSlicing!(typeof(r)));
3654     static assert(isForwardRange!(typeof(r)));
3655     static assert(isRandomAccessRange!(typeof(r)));
3656     assert(r.front.front == 6);
3657     assert(r.front.back == 12);
3658     assert(r.back.front == 18);
3659     assert(r.back.back == 24);
3660     assert(r[0][0] ==  6);
3661     assert(r[0][1] == 12);
3662     assert(r[1][0] == 18);
3663     assert(r[1][1] == 24);
3664 }
3665 
3666 private enum bool isType(alias T) = false;
3667 
3668 private enum bool isType(T) = true;
3669 
3670 private enum isStringValue(alias T) = is(typeof(T) : string);
3671 
3672 
3673 private bool _checkAssignLengths(
3674     LIterator, RIterator,
3675     size_t LN, size_t RN,
3676     SliceKind lkind, SliceKind rkind,
3677     )
3678     (Slice!(LIterator, LN, lkind) ls,
3679      Slice!(RIterator, RN, rkind) rs)
3680 {
3681     static if (isInstanceOf!(SliceIterator, LIterator))
3682     {
3683         import mir.ndslice.topology: unpack;
3684         return _checkAssignLengths(ls.unpack, rs);
3685     }
3686     else
3687     static if (isInstanceOf!(SliceIterator, RIterator))
3688     {
3689         import mir.ndslice.topology: unpack;
3690         return _checkAssignLengths(ls, rs.unpack);
3691     }
3692     else
3693     {
3694         foreach (i; Iota!(0, RN))
3695             if (ls._lengths[i + LN - RN] != rs._lengths[i])
3696                 return false;
3697         return true;
3698     }
3699 }
3700 
3701 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
3702 {
3703     import mir.ndslice.topology : iota;
3704 
3705     assert(_checkAssignLengths(iota(2, 2), iota(2, 2)));
3706     assert(!_checkAssignLengths(iota(2, 2), iota(2, 3)));
3707     assert(!_checkAssignLengths(iota(2, 2), iota(3, 2)));
3708     assert(!_checkAssignLengths(iota(2, 2), iota(3, 3)));
3709 }
3710 
3711 pure nothrow version(mir_ndslice_test) unittest
3712 {
3713     auto slice = new int[15].slicedField(5, 3);
3714 
3715     /// Fully defined slice
3716     assert(slice[] == slice);
3717     auto sublice = slice[0..$-2, 1..$];
3718 
3719     /// Partially defined slice
3720     auto row = slice[3];
3721     auto col = slice[0..$, 1];
3722 }
3723 
3724 pure nothrow version(mir_ndslice_test) unittest
3725 {
3726     auto a = new int[6].slicedField(2, 3);
3727     auto b = [1, 2, 3, 4].sliced(2, 2);
3728 
3729     a[0..$, 0..$-1] = b;
3730     assert(a == [[1, 2, 0], [3, 4, 0]]);
3731 
3732     a[0..$, 0..$-1] = b[0];
3733     assert(a == [[1, 2, 0], [1, 2, 0]]);
3734 
3735     a[1, 0..$-1] = b[1];
3736     assert(a[1] == [3, 4, 0]);
3737 
3738     a[1, 0..$-1][] = b[0];
3739     assert(a[1] == [1, 2, 0]);
3740 }
3741 
3742 pure nothrow version(mir_ndslice_test) unittest
3743 {
3744     auto a = new int[6].slicedField(2, 3);
3745     auto b = [[1, 2], [3, 4]];
3746 
3747     a[] = [[1, 2, 3], [4, 5, 6]];
3748     assert(a == [[1, 2, 3], [4, 5, 6]]);
3749 
3750     a[0..$, 0..$-1] = [[1, 2], [3, 4]];
3751     assert(a == [[1, 2, 3], [3, 4, 6]]);
3752 
3753     a[0..$, 0..$-1] = [1, 2];
3754     assert(a == [[1, 2, 3], [1, 2, 6]]);
3755 
3756     a[1, 0..$-1] = [3, 4];
3757     assert(a[1] == [3, 4, 6]);
3758 
3759     a[1, 0..$-1][] = [3, 4];
3760     assert(a[1] == [3, 4, 6]);
3761 }
3762 
3763 pure nothrow version(mir_ndslice_test) unittest
3764 {
3765     auto a = new int[6].slicedField(2, 3);
3766 
3767     a[] = 9;
3768     //assert(a == [[9, 9, 9], [9, 9, 9]]);
3769 
3770     a[0..$, 0..$-1] = 1;
3771     //assert(a == [[1, 1, 9], [1, 1, 9]]);
3772 
3773     a[0..$, 0..$-1] = 2;
3774     //assert(a == [[2, 2, 9], [2, 2, 9]]);
3775 
3776     a[1, 0..$-1] = 3;
3777     //assert(a[1] == [3, 3, 9]);
3778 
3779     a[1, 0..$-1] = 4;
3780     //assert(a[1] == [4, 4, 9]);
3781 
3782     a[1, 0..$-1][] = 5;
3783     //assert(a[1] == [5, 5, 9]);
3784 }
3785 
3786 pure nothrow version(mir_ndslice_test) unittest
3787 {
3788     auto a = new int[6].slicedField(2, 3);
3789 
3790     a[1, 2] = 3;
3791     assert(a[1, 2] == 3);
3792 }
3793 
3794 pure nothrow version(mir_ndslice_test) unittest
3795 {
3796     auto a = new int[6].slicedField(2, 3);
3797 
3798     a[[1, 2]] = 3;
3799     assert(a[[1, 2]] == 3);
3800 }
3801 
3802 pure nothrow version(mir_ndslice_test) unittest
3803 {
3804     auto a = new int[6].slicedField(2, 3);
3805 
3806     a[1, 2] += 3;
3807     assert(a[1, 2] == 3);
3808 }
3809 
3810 pure nothrow version(mir_ndslice_test) unittest
3811 {
3812     auto a = new int[6].slicedField(2, 3);
3813 
3814     a[[1, 2]] += 3;
3815     assert(a[[1, 2]] == 3);
3816 }
3817 
3818 pure nothrow version(mir_ndslice_test) unittest
3819 {
3820     auto a = new int[6].slicedField(2, 3);
3821     auto b = [1, 2, 3, 4].sliced(2, 2);
3822 
3823     a[0..$, 0..$-1] += b;
3824     assert(a == [[1, 2, 0], [3, 4, 0]]);
3825 
3826     a[0..$, 0..$-1] += b[0];
3827     assert(a == [[2, 4, 0], [4, 6, 0]]);
3828 
3829     a[1, 0..$-1] += b[1];
3830     assert(a[1] == [7, 10, 0]);
3831 
3832     a[1, 0..$-1][] += b[0];
3833     assert(a[1] == [8, 12, 0]);
3834 }
3835 
3836 pure nothrow version(mir_ndslice_test) unittest
3837 {
3838     auto a = new int[6].slicedField(2, 3);
3839 
3840     a[0..$, 0..$-1] += [[1, 2], [3, 4]];
3841     assert(a == [[1, 2, 0], [3, 4, 0]]);
3842 
3843     a[0..$, 0..$-1] += [1, 2];
3844     assert(a == [[2, 4, 0], [4, 6, 0]]);
3845 
3846     a[1, 0..$-1] += [3, 4];
3847     assert(a[1] == [7, 10, 0]);
3848 
3849     a[1, 0..$-1][] += [1, 2];
3850     assert(a[1] == [8, 12, 0]);
3851 }
3852 
3853 pure nothrow version(mir_ndslice_test) unittest
3854 {
3855     auto a = new int[6].slicedField(2, 3);
3856 
3857     a[] += 1;
3858     assert(a == [[1, 1, 1], [1, 1, 1]]);
3859 
3860     a[0..$, 0..$-1] += 2;
3861     assert(a == [[3, 3, 1], [3, 3, 1]]);
3862 
3863     a[1, 0..$-1] += 3;
3864     assert(a[1] == [6, 6, 1]);
3865 }
3866 
3867 pure nothrow version(mir_ndslice_test) unittest
3868 {
3869     auto a = new int[6].slicedField(2, 3);
3870 
3871     ++a[1, 2];
3872     assert(a[1, 2] == 1);
3873 }
3874 
3875 pure nothrow version(mir_ndslice_test) unittest
3876 {
3877     auto a = new int[6].slicedField(2, 3);
3878 
3879     ++a[[1, 2]];
3880     assert(a[[1, 2]] == 1);
3881 }
3882 
3883 pure nothrow version(mir_ndslice_test) unittest
3884 {
3885     auto a = new int[6].slicedField(2, 3);
3886 
3887     ++a[];
3888     assert(a == [[1, 1, 1], [1, 1, 1]]);
3889 
3890     --a[1, 0..$-1];
3891     assert(a[1] == [0, 0, 1]);
3892 }
3893 
3894 version(mir_ndslice_test) unittest
3895 {
3896     import mir.ndslice.topology: iota, universal;
3897 
3898     auto sl = iota(3, 4).universal;
3899     assert(sl[0 .. $] == sl);
3900 }
3901 
3902 version(mir_ndslice_test) unittest
3903 {
3904     import mir.ndslice.topology: canonical, iota;
3905     static assert(kindOf!(typeof(iota([1, 2]).canonical[1])) == Contiguous);
3906 }
3907 
3908 version(mir_ndslice_test) unittest
3909 {
3910     import mir.ndslice.topology: iota;
3911     auto s = iota(2, 3);
3912     assert(s.front!1 == [0, 3]);
3913     assert(s.back!1 == [2, 5]);
3914 }
3915 
3916 /++
3917 Assignment utility for generic code that works both with scalars and with ndslices.
3918 Params:
3919     op = assign operation (generic, optional)
3920     lside = left side
3921     rside = right side
3922 Returns:
3923     expression value
3924 +/
3925 auto ndassign(string op = "", L, R)(ref L lside, auto ref R rside) @property
3926     if (!isSlice!L && (op.length == 0 || op[$-1] != '='))
3927 {
3928     return mixin(`lside ` ~ op ~ `= rside`);
3929 }
3930 
3931 /// ditto
3932 auto ndassign(string op = "", L, R)(L lside, auto ref R rside) @property
3933     if (isSlice!L && (op.length == 0 || op[$-1] != '='))
3934 {
3935     static if (op == "")
3936         return lside.opIndexAssign(rside);
3937     else
3938         return lside.opIndexOpAssign!op(rside);
3939 }
3940 
3941 ///
3942 version(mir_ndslice_test) unittest
3943 {
3944     import mir.ndslice.topology: iota;
3945     import mir.ndslice.allocation: slice;
3946     auto scalar = 3;
3947     auto vector = 3.iota.slice; // [0, 1, 2]
3948 
3949     // scalar = 5;
3950     scalar.ndassign = 5;
3951     assert(scalar == 5);
3952 
3953     // vector[] = vector * 2;
3954     vector.ndassign = vector * 2;
3955     assert(vector == [0, 2, 4]);
3956 
3957     // vector[] += scalar;
3958     vector.ndassign!"+"= scalar;
3959     assert(vector == [5, 7, 9]);
3960 }
3961 
3962 version(mir_ndslice_test) pure nothrow unittest
3963 {
3964     import mir.ndslice.allocation: slice;
3965     import mir.ndslice.topology: universal;
3966 
3967     auto df = slice!(double, int, int)(2, 3).universal;
3968     df.label[] = [1, 2];
3969     df.label!1[] = [1, 2, 3];
3970     auto lsdf = df.lightScope;
3971     assert(lsdf.label!0[0] == 1);
3972     assert(lsdf.label!1[1] == 2);
3973 
3974     auto immdf = (cast(immutable)df).lightImmutable;
3975     assert(immdf.label!0[0] == 1);
3976     assert(immdf.label!1[1] == 2);
3977 
3978     auto constdf = df.lightConst;
3979     assert(constdf.label!0[0] == 1);
3980     assert(constdf.label!1[1] == 2);
3981 
3982     auto constdf2 = df.toConst;
3983     assert(constdf2.label!0[0] == 1);
3984     assert(constdf2.label!1[1] == 2);
3985 
3986     auto immdf2 = (cast(immutable)df).toImmutable;
3987     assert(immdf2.label!0[0] == 1);
3988     assert(immdf2.label!1[1] == 2);
3989 }
3990 
3991 version(mir_ndslice_test) pure nothrow unittest
3992 {
3993     import mir.ndslice.allocation: slice;
3994     import mir.ndslice.topology: universal;
3995 
3996     auto df = slice!(double, int, int)(2, 3).universal;
3997     df[] = 5;
3998 
3999     Slice!(double*, 2, Universal) values = df.values;
4000     assert(values[0][0] == 5);
4001     Slice!(LightConstOf!(double*), 2, Universal) constvalues = df.values;
4002     assert(constvalues[0][0] == 5);
4003     Slice!(LightImmutableOf!(double*), 2, Universal) immvalues = (cast(immutable)df).values;
4004     assert(immvalues[0][0] == 5);
4005 }
4006 
4007 version(mir_ndslice_test) @safe unittest
4008 {
4009     import mir.ndslice.allocation;
4010     auto a = rcslice!double([2, 3], 0);
4011     auto b = rcslice!double([2, 3], 0);
4012     a[1, 2] = 3;
4013     b[] = a;
4014     assert(a == b);
4015 }
4016 
4017 version(mir_ndslice_test)
4018 @safe pure @nogc nothrow
4019 unittest
4020 {
4021     import mir.ndslice.topology: iota, flattened;
4022 
4023     auto m = iota(2, 3, 4); // Contiguous Matrix
4024     auto mFlat = m.flattened;
4025 
4026     for (size_t i = 0; i < m.elementCount; i++) {
4027         assert(m.accessFlat(i) == mFlat[i]);
4028     }
4029 }
4030 
4031 version(mir_ndslice_test)
4032 @safe pure @nogc nothrow
4033 unittest
4034 {
4035     import mir.ndslice.topology: iota, flattened;
4036 
4037     auto m = iota(3, 4); // Contiguous Matrix
4038     auto x = m.front; // Contiguous Vector
4039 
4040     for (size_t i = 0; i < x.elementCount; i++) {
4041         assert(x.accessFlat(i) == m[0, i]);
4042     }
4043 }
4044 
4045 version(mir_ndslice_test)
4046 @safe pure @nogc nothrow
4047 unittest
4048 {
4049     import mir.ndslice.topology: iota, flattened;
4050 
4051     auto m = iota(3, 4); // Contiguous Matrix
4052     auto x = m[0 .. $, 0 .. $ - 1]; // Canonical Matrix
4053     auto xFlat = x.flattened;
4054 
4055     for (size_t i = 0; i < x.elementCount; i++) {
4056         assert(x.accessFlat(i) == xFlat[i]);
4057     }
4058 }
4059 
4060 
4061 version(mir_ndslice_test)
4062 @safe pure @nogc nothrow
4063 unittest
4064 {
4065     import mir.ndslice.topology: iota, flattened;
4066 
4067     auto m = iota(2, 3, 4); // Contiguous Matrix
4068     auto x = m[0 .. $, 0 .. $, 0 .. $ - 1]; // Canonical Matrix
4069     auto xFlat = x.flattened;
4070 
4071     for (size_t i = 0; i < x.elementCount; i++) {
4072         assert(x.accessFlat(i) == xFlat[i]);
4073     }
4074 }
4075 
4076 
4077 version(mir_ndslice_test)
4078 @safe pure @nogc nothrow
4079 unittest
4080 {
4081     import mir.ndslice.topology: iota, flattened;
4082     import mir.ndslice.dynamic: transposed;
4083 
4084     auto m = iota(2, 3, 4); // Contiguous Matrix
4085     auto x = m.transposed!(2, 1, 0); // Universal Matrix
4086     auto xFlat = x.flattened;
4087 
4088     for (size_t i = 0; i < x.elementCount; i++) {
4089         assert(x.accessFlat(i) == xFlat[i]);
4090     }
4091 }
4092 
4093 version(mir_ndslice_test)
4094 @safe pure @nogc nothrow
4095 unittest
4096 {
4097     import mir.ndslice.topology: iota, flattened;
4098     import mir.ndslice.dynamic: transposed;
4099 
4100     auto m = iota(3, 4); // Contiguous Matrix
4101     auto x = m.transposed; // Universal Matrix
4102     auto xFlat = x.flattened;
4103 
4104     for (size_t i = 0; i < x.elementCount; i++) {
4105         assert(x.accessFlat(i) == xFlat[i]);
4106     }
4107 }
4108 
4109 version(mir_ndslice_test)
4110 @safe pure @nogc nothrow
4111 unittest
4112 {
4113     import mir.ndslice.topology: iota, flattened, diagonal;
4114 
4115     auto m = iota(3, 4); // Contiguous Matrix
4116     auto x = m.diagonal; // Universal Vector
4117 
4118     for (size_t i = 0; i < x.elementCount; i++) {
4119         assert(x.accessFlat(i) == m[i, i]);
4120     }
4121 }
4122 
4123 version(mir_ndslice_test)
4124 @safe pure @nogc nothrow
4125 unittest
4126 {
4127     import mir.ndslice.topology: iota, flattened;
4128 
4129     auto m = iota(3, 4); // Contiguous Matrix
4130     auto x = m.front!1; // Universal Vector
4131 
4132     for (size_t i = 0; i < x.elementCount; i++) {
4133         assert(x.accessFlat(i) == m[i, 0]);
4134     }
4135 }
4136 
4137 version(mir_ndslice_test)
4138 @safe pure @nogc nothrow
4139 unittest // check it can be compiled
4140 {
4141     import mir.algebraic;
4142     alias S = Slice!(double*, 2);
4143     alias D = Variant!S;
4144 }
4145 
4146 version(mir_ndslice_test)
4147 unittest
4148 {
4149     import mir.ndslice;
4150 
4151     auto matrix = slice!short(3, 4);
4152     matrix[] = 0;
4153     matrix.diagonal[] = 1;
4154 
4155     auto row = matrix[2];
4156     row[3] = 6;
4157     assert(matrix[2, 3] == 6); // D & C index order
4158 }