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