The OpenD Programming Language

1 /++
2 This is a submodule of $(MREF mir,ndslice).
3 
4 Iterator is a type with a pointer like behavior.
5 An ndslice can be created on top of an iterator using $(SUBREF slice, sliced).
6 
7 $(BOOKTABLE $(H2 Iterators),
8 $(TR $(TH Iterator Name) $(TH Used By))
9 $(T2 BytegroupIterator, $(SUBREF topology, bytegroup).)
10 $(T2 CachedIterator, $(SUBREF topology, cached), $(SUBREF topology, cachedGC).)
11 $(T2 ChopIterator, $(SUBREF topology, chopped))
12 $(T2 FieldIterator, $(SUBREF slice, slicedField), $(SUBREF topology, bitwise), $(SUBREF topology, ndiota), and others.)
13 $(T2 FlattenedIterator, $(SUBREF topology, flattened))
14 $(T2 IndexIterator, $(SUBREF topology, indexed))
15 $(T2 IotaIterator, $(SUBREF topology, iota))
16 $(T2 MapIterator, $(SUBREF topology, map))
17 $(T2 MemberIterator, $(SUBREF topology, member))
18 $(T2 NeighboursIterator, $(SUBREF topology, withNeighboursSum))
19 $(T2 RetroIterator, $(SUBREF topology, retro))
20 $(T2 SliceIterator, $(SUBREF topology, map) in composition with $(LREF MapIterator) for packed slices.)
21 $(T2 SlideIterator, $(SUBREF topology, diff), $(SUBREF topology, pairwise), and $(SUBREF topology, slide).)
22 $(T2 StairsIterator, $(SUBREF topology, stairs))
23 $(T2 StrideIterator, $(SUBREF topology, stride))
24 $(T2 SubSliceIterator, $(SUBREF topology, subSlices))
25 $(T2 TripletIterator, $(SUBREF topology, triplets))
26 $(T2 ZipIterator, $(SUBREF topology, zip))
27 )
28 
29 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
30 Copyright: 2020 Ilia Ki, Kaleidic Associates Advisory Limited, Symmetry Investments
31 Authors: Ilia Ki
32 
33 Macros:
34 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP)
35 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
36 +/
37 module mir.ndslice.iterator;
38 
39 import mir.internal.utility: Iota;
40 import mir.math.common: fmamath;
41 import mir.ndslice.field;
42 import mir.ndslice.internal;
43 import mir.ndslice.slice: SliceKind, Slice, Universal, Canonical, Contiguous, isSlice;
44 import mir.qualifier;
45 import mir.conv;
46 import std.traits;
47 
48 private static immutable assumeZeroShiftExceptionMsg = "*.assumeFieldsHaveZeroShift: shift is not zero!";
49 version(D_Exceptions)
50     private static immutable assumeZeroShiftException = new Exception(assumeZeroShiftExceptionMsg);
51 
52 @fmamath:
53 
54 enum std_ops = q{
55     void opUnary(string op)() scope
56         if (op == "--" || op == "++")
57     { mixin(op ~ "_iterator;"); }
58 
59     void opOpAssign(string op)(ptrdiff_t index) scope
60         if (op == "-" || op == "+")
61     { mixin("_iterator " ~ op ~ "= index;"); }
62 
63     auto opBinary(string op)(ptrdiff_t index)
64         if (op == "+" || op == "-")
65     {
66         auto ret = this;
67         mixin(`ret ` ~ op ~ `= index;`);
68         return ret;
69     }
70 
71     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
72     { return this._iterator - right._iterator; }
73 
74     bool opEquals()(scope ref const typeof(this) right) scope const
75     { return this._iterator == right._iterator; }
76 
77     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
78     {
79         static if (isPointer!Iterator)
80             return this._iterator - right._iterator;
81         else
82             return this._iterator.opCmp(right._iterator);
83     }
84 };
85 
86 /++
87 Step counter.
88 
89 `IotaIterator` is used by $(SUBREF topology, iota).
90 +/
91 struct IotaIterator(I)
92     if (isIntegral!I || isPointer!I)
93 {
94 @fmamath:
95 
96     ///
97     I _index;
98 
99     static if (isPointer!I)
100     ///
101     auto lightConst()() const @property
102     {
103         static if (isIntegral!I)
104             return IotaIterator!I(_index);
105         else
106             return IotaIterator!(LightConstOf!I)(_index);
107     }
108 
109     static if (isPointer!I)
110     ///
111     auto lightImmutable()() immutable @property
112     {
113         static if (isIntegral!I)
114             return IotaIterator!I(_index);
115         else
116             return IotaIterator!(LightImmutableOf!I)(_index);
117     }
118 
119 pure:
120 
121     I opUnary(string op : "*")()
122     { return _index; }
123 
124     void opUnary(string op)()
125         if (op == "--" || op == "++")
126     { mixin(op ~ `_index;`); }
127 
128     I opIndex()(ptrdiff_t index) const
129     { return cast(I)(_index + index); }
130 
131     void opOpAssign(string op)(ptrdiff_t index)
132         if (op == `+` || op == `-`)
133     { mixin(`_index ` ~ op ~ `= index;`); }
134 
135     auto opBinary(string op)(ptrdiff_t index)
136         if (op == "+" || op == "-")
137     {
138         auto ret = this;
139         mixin(`ret ` ~ op ~ `= index;`);
140         return ret;
141     }
142 
143     ptrdiff_t opBinary(string op : "-")(const typeof(this) right) const
144     { return cast(ptrdiff_t)(this._index - right._index); }
145 
146     bool opEquals()(const typeof(this) right) const
147     { return this._index == right._index; }
148 
149     auto opCmp()(const typeof(this) right) const
150     { return this._index - right._index; }
151 }
152 
153 ///
154 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
155 {
156     IotaIterator!int iota;
157     assert(*iota == 0);
158 
159     // iteration
160     ++iota;
161     assert(*iota == 1);
162     
163     assert(iota[2] == 3);
164     assert(iota[-1] == 0);
165 
166     --iota;
167     assert(*iota == 0);
168 
169     // opBinary
170     assert(*(iota + 2) == 2);
171     assert(*(iota - 3) == -3);
172     assert((iota - 3) - iota == -3);
173 
174     // construction
175     assert(*IotaIterator!int(3) == 3);
176     assert(iota - 1 < iota);
177 }
178 
179 ///
180 pure nothrow @nogc version(mir_ndslice_test) unittest
181 {
182     int[32] data;
183     auto iota = IotaIterator!(int*)(data.ptr);
184     assert(*iota == data.ptr);
185 
186     // iteration
187     ++iota;
188     assert(*iota == 1 + data.ptr);
189     
190     assert(iota[2] == 3 + data.ptr);
191     assert(iota[-1] == 0 + data.ptr);
192 
193     --iota;
194     assert(*iota == 0 + data.ptr);
195 
196     // opBinary
197     assert(*(iota + 2) == 2 + data.ptr);
198     assert(*(iota - 3) == -3 + data.ptr);
199     assert((iota - 3) - iota == -3);
200 
201     // construction
202     assert(*IotaIterator!(int*)(data.ptr) == data.ptr);
203     assert(iota - 1 < iota);
204 }
205 
206 auto RetroIterator__map(Iterator, alias fun)(ref RetroIterator!Iterator it)
207 {
208     auto iterator = it._iterator._mapIterator!fun;
209     return RetroIterator!(typeof(iterator))(iterator);
210 }
211 
212 version(mir_ndslice_test) unittest
213 {
214     import mir.ndslice.topology;
215     import mir.ndslice.allocation;
216     auto v = iota(9).retro.map!(a => a).slice;
217     uint r;
218     auto w = iota(9).retro.map!(a => a).map!(a => a * r).slice;
219 }
220 
221 /++
222 Reverse directions for an iterator.
223 
224 `RetroIterator` is used by $(SUBREF topology, retro).
225 +/
226 struct RetroIterator(Iterator)
227 {
228 @fmamath:
229     ///
230     Iterator _iterator;
231 
232     ///
233     auto lightConst()() const @property
234     {
235         return RetroIterator!(LightConstOf!Iterator)(.lightConst(_iterator));
236     }
237 
238     ///
239     auto lightImmutable()() immutable @property
240     {
241         return RetroIterator!(LightImmutableOf!Iterator)(.lightImmutable(_iterator));
242     }
243 
244     ///
245     static alias __map(alias fun) = RetroIterator__map!(Iterator, fun);
246 
247     auto ref opUnary(string op : "*")()
248     { return *_iterator; }
249 
250     void opUnary(string op : "--")()
251     { ++_iterator; }
252 
253     void opUnary(string op : "++")() pure
254     { --_iterator; }
255 
256     auto ref opIndex()(ptrdiff_t index)
257     { return _iterator[-index]; }
258 
259     void opOpAssign(string op : "-")(ptrdiff_t index) scope
260     { _iterator += index; }
261 
262     void opOpAssign(string op : "+")(ptrdiff_t index) scope
263     { _iterator -= index; }
264 
265     auto opBinary(string op)(ptrdiff_t index)
266         if (op == "+" || op == "-")
267     {
268         auto ret = this;
269         mixin(`ret ` ~ op ~ `= index;`);
270         return ret;
271     }
272 
273     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
274     { return right._iterator - this._iterator; }
275 
276     bool opEquals()(scope ref const typeof(this) right) scope const
277     { return right._iterator == this._iterator; }
278 
279     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
280     {
281         static if (isPointer!Iterator)
282             return right._iterator - this._iterator;
283         else
284             return right._iterator.opCmp(this._iterator);
285     }
286 }
287 
288 ///
289 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
290 {
291     IotaIterator!int iota;
292     RetroIterator!(IotaIterator!int) retro;
293 
294     ++iota;
295     --retro;
296     assert(*retro == *iota);
297 
298     --iota;
299     ++retro;
300     assert(*retro == *iota);
301 
302     assert(retro[-7] == iota[7]);
303 
304     iota += 100;
305     retro -= 100;
306     assert(*retro == *iota);
307 
308     iota -= 100;
309     retro += 100;
310     assert(*retro == *iota);
311 
312     assert(*(retro + 10) == *(iota - 10));
313 
314     assert(retro - 1 < retro);
315 
316     assert((retro - 5) - retro == -5);
317 
318     iota = IotaIterator!int(3);
319     retro = RetroIterator!(IotaIterator!int)(iota);
320     assert(*retro == *iota);
321 }
322 
323 auto StrideIterator__map(Iterator, alias fun)(StrideIterator!Iterator it)
324 {
325     auto iterator = it._iterator._mapIterator!fun;
326     return StrideIterator!(typeof(iterator))(it._stride, iterator);
327 }
328 
329 version(mir_ndslice_test) unittest
330 {
331     import mir.ndslice.topology;
332     import mir.ndslice.allocation;
333     auto v = iota([3], 0, 3).map!(a => a).slice;
334     uint r;
335     auto w = iota([3], 0, 3).map!(a => a).map!(a => a * r).slice;
336 }
337 
338 /++
339 Iterates an iterator with a fixed strides.
340 
341 `StrideIterator` is used by $(SUBREF topology, stride).
342 +/
343 struct StrideIterator(Iterator)
344 {
345 @fmamath:
346     ///
347     ptrdiff_t _stride;
348     ///
349     Iterator _iterator;
350 
351     ///
352     auto lightConst()() const @property
353     {
354         return StrideIterator!(LightConstOf!Iterator)(_stride, .lightConst(_iterator));
355     }
356 
357     ///
358     auto lightImmutable()() immutable @property
359     {
360         return StrideIterator!(LightImmutableOf!Iterator)(_stride, .lightImmutable(_iterator));
361     }
362 
363     ///
364     static alias __map(alias fun) = StrideIterator__map!(Iterator, fun);
365 
366     auto ref opUnary(string op : "*")()
367     { return *_iterator; }
368 
369     void opUnary(string op)() scope
370         if (op == "--" || op == "++")
371     { mixin("_iterator " ~ op[0] ~ "= _stride;"); }
372 
373     auto ref opIndex()(ptrdiff_t index)
374     { return _iterator[index * _stride]; }
375 
376     void opOpAssign(string op)(ptrdiff_t index) scope
377         if (op == "-" || op == "+")
378     { mixin("_iterator " ~ op ~ "= index * _stride;"); }
379 
380     auto opBinary(string op)(ptrdiff_t index)
381         if (op == "+" || op == "-")
382     {
383         auto ret = this;
384         mixin(`ret ` ~ op ~ `= index;`);
385         return ret;
386     }
387 
388     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
389     { return (this._iterator - right._iterator) / _stride; }
390 
391     bool opEquals()(scope ref const typeof(this) right) scope const
392     { return this._iterator == right._iterator; }
393 
394     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
395     {
396         static if (isPointer!Iterator)
397             ptrdiff_t ret = this._iterator - right._iterator;
398         else
399             ptrdiff_t ret = this._iterator.opCmp(right._iterator);
400         return _stride >= 0 ? ret : -ret;
401     }
402 }
403 
404 ///
405 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
406 {
407     IotaIterator!int iota;
408     StrideIterator!(IotaIterator!int) stride;
409     stride._stride = -3;
410 
411     iota -= stride._stride;
412     --stride;
413     assert(*stride == *iota);
414 
415     iota += stride._stride;
416     ++stride;
417     assert(*stride == *iota);
418 
419     assert(stride[7] == iota[7 * stride._stride]);
420 
421     iota -= 100 * stride._stride;
422     stride -= 100;
423     assert(*stride == *iota);
424 
425     iota += 100 * stride._stride;
426     stride += 100;
427     assert(*stride == *iota);
428 
429     assert(*(stride + 10) == *(iota + 10 * stride._stride));
430 
431     assert(stride - 1 < stride);
432 
433     assert((stride - 5) - stride == -5);
434 
435     iota = IotaIterator!int(3);
436     stride = StrideIterator!(IotaIterator!int)(3, iota);
437     assert(*stride == *iota);
438 }
439 
440 auto StrideIterator__map(Iterator, size_t factor, alias fun)(StrideIterator!(Iterator, factor) it)
441 {
442     auto iterator = it._iterator._mapIterator!fun;
443     return StrideIterator!(typeof(iterator), factor)(iterator);
444 }
445 
446 /++
447 Iterates an iterator with a fixed strides.
448 
449 `StrideIterator` is used by $(SUBREF topology, stride).
450 +/
451 struct StrideIterator(Iterator, ptrdiff_t factor)
452 {
453 @fmamath:
454     ///
455     enum _stride = factor;
456 
457     ///
458     Iterator _iterator;
459 
460     ///
461     auto lightConst()() const @property
462     {
463         return StrideIterator!(LightConstOf!Iterator, _stride)(.lightConst(_iterator));
464     }
465 
466     ///
467     auto lightImmutable()() immutable @property
468     {
469         return StrideIterator!(LightImmutableOf!Iterator, _stride)(.lightImmutable(_iterator));
470     }
471 
472     ///
473     static alias __map(alias fun) = StrideIterator__map!(Iterator, _stride, fun);
474 
475     auto ref opUnary(string op : "*")()
476     { return *_iterator; }
477 
478     void opUnary(string op)() scope
479         if (op == "--" || op == "++")
480     { mixin("_iterator " ~ op[0] ~ "= _stride;"); }
481 
482     auto ref opIndex()(ptrdiff_t index)
483     { return _iterator[index * _stride]; }
484 
485     void opOpAssign(string op)(ptrdiff_t index) scope
486         if (op == "-" || op == "+")
487     { mixin("_iterator " ~ op ~ "= index * _stride;"); }
488 
489     auto opBinary(string op)(ptrdiff_t index)
490         if (op == "+" || op == "-")
491     {
492         auto ret = this;
493         mixin(`ret ` ~ op ~ `= index;`);
494         return ret;
495     }
496 
497     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
498     { return (this._iterator - right._iterator) / _stride; }
499 
500     bool opEquals()(scope ref const typeof(this) right) scope const
501     { return this._iterator == right._iterator; }
502 
503     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
504     {
505         static if (isPointer!Iterator)
506             ptrdiff_t ret = this._iterator - right._iterator;
507         else
508             ptrdiff_t ret = this._iterator.opCmp(right._iterator);
509         return _stride >= 0 ? ret : -ret;
510     }
511 }
512 
513 ///
514 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
515 {
516     IotaIterator!int iota;
517     StrideIterator!(IotaIterator!int, -3) stride;
518 
519     iota -= stride._stride;
520     --stride;
521     assert(*stride == *iota);
522 
523     iota += stride._stride;
524     ++stride;
525     assert(*stride == *iota);
526 
527     assert(stride[7] == iota[7 * stride._stride]);
528 
529     iota -= 100 * stride._stride;
530     stride -= 100;
531     assert(*stride == *iota);
532 
533     iota += 100 * stride._stride;
534     stride += 100;
535     assert(*stride == *iota);
536 
537     assert(*(stride + 10) == *(iota + 10 * stride._stride));
538 
539     assert(stride - 1 < stride);
540 
541     assert((stride - 5) - stride == -5);
542 }
543 
544 package template _zip_types(Iterators...)
545 {
546     alias AliasSeq(T...) = T;
547     static if (Iterators.length)
548     {
549         enum i = Iterators.length - 1;
550         alias T = typeof(Iterators[i].init[sizediff_t.init]);
551         static if (__traits(compiles, &Iterators[i].init[sizediff_t.init]))
552         {
553             import mir.functional: Ref;
554             alias _zip_types = AliasSeq!(_zip_types!(Iterators[0 .. i]), Ref!T);
555         }
556         else
557             alias _zip_types = AliasSeq!(_zip_types!(Iterators[0 .. i]), T);
558     }
559     else
560         alias _zip_types = AliasSeq!();
561 }
562 
563 package template _zip_fronts(Iterators...)
564 {
565     static if (Iterators.length)
566     {
567         enum i = Iterators.length - 1;
568         static if (__traits(compiles, &Iterators[i].init[sizediff_t.init]))
569             enum _zip_fronts = _zip_fronts!(Iterators[0 .. i]) ~ "_ref(*_iterators[" ~ i.stringof ~ "]), ";
570         else
571             enum _zip_fronts = _zip_fronts!(Iterators[0 .. i]) ~ "*_iterators[" ~ i.stringof ~ "], ";
572     }
573     else
574         enum _zip_fronts = "";
575 }
576 
577 package template _zip_index(Iterators...)
578 {
579     static if (Iterators.length)
580     {
581         enum i = Iterators.length - 1;
582         static if (__traits(compiles, &Iterators[i].init[sizediff_t.init]))
583             enum _zip_index = _zip_index!(Iterators[0 .. i]) ~ "_ref(_iterators[" ~ i.stringof ~ "][index]), ";
584         else
585             enum _zip_index = _zip_index!(Iterators[0 .. i]) ~ "_iterators[" ~ i.stringof ~ "][index], ";
586     }
587     else
588         enum _zip_index = "";
589 }
590 
591 /++
592 Iterates multiple iterators in lockstep.
593 
594 `ZipIterator` is used by $(SUBREF topology, zip).
595 +/
596 struct ZipIterator(Iterators...)
597     if (Iterators.length > 1)
598 {
599 @fmamath:
600     import std.traits: ConstOf, ImmutableOf;
601     import std.meta: staticMap;
602     import mir.functional: Tuple, Ref, _ref;
603     ///
604     Iterators _iterators;
605 
606     ///
607     auto lightConst()() const @property
608     {
609         import std.format: format;
610         import mir.ndslice.topology: iota;
611         import std.meta: staticMap;
612         alias Ret = ZipIterator!(staticMap!(LightConstOf, Iterators));
613         enum ret = "Ret(%(.lightConst(_iterators[%s]),%)]))".format(_iterators.length.iota);
614         return mixin(ret);
615     }
616 
617     ///
618     auto lightImmutable()() immutable @property
619     {
620         import std.format: format;
621         import mir.ndslice.topology: iota;
622         import std.meta: staticMap;
623         alias Ret = ZipIterator!(staticMap!(LightImmutableOf, Iterators));
624         enum ret = "Ret(%(.lightImmutable(_iterators[%s]),%)]))".format(_iterators.length.iota);
625         return mixin(ret);
626     }
627 
628     auto opUnary(string op : "*")()
629     { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); }
630 
631 
632     auto opUnary(string op : "*")() const
633     { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); }
634 
635     auto opUnary(string op : "*")() immutable
636     { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_fronts!Iterators ~ ")"); }
637 
638     void opUnary(string op)() scope
639         if (op == "++" || op == "--")
640     {
641         foreach (ref _iterator; _iterators)
642             mixin(op ~ `_iterator;`);
643     }
644 
645     auto opIndex()(ptrdiff_t index)
646     { return mixin("Tuple!(_zip_types!Iterators)(" ~ _zip_index!Iterators ~ ")"); }
647 
648     auto opIndexAssign(Types...)(Tuple!(Types) value, ptrdiff_t index)
649         if (Types.length == Iterators.length)
650     {
651         foreach(i, ref val; value.expand)
652         {
653             import mir.functional: unref;
654             _iterators[i][index] = unref(val);
655         }
656         return opIndex(index);
657     }
658 
659     void opOpAssign(string op)(ptrdiff_t index) scope
660         if (op == "+" || op == "-")
661     {
662         foreach (ref _iterator; _iterators)
663             mixin(`_iterator ` ~ op ~ `= index;`);
664     }
665 
666     auto opBinary(string op)(ptrdiff_t index)
667         if (op == "+" || op == "-")
668     {
669         auto ret = this;
670         mixin(`ret ` ~ op ~ `= index;`);
671         return ret;
672     }
673 
674     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
675     { return this._iterators[0] - right._iterators[0]; }
676 
677     bool opEquals()(scope ref const typeof(this) right) scope const
678     { return this._iterators[0] == right._iterators[0]; }
679 
680     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
681     {
682         static if (isPointer!(Iterators[0]))
683             return this._iterators[0] - right._iterators[0];
684         else
685             return this._iterators[0].opCmp(right._iterators[0]);
686     }
687 
688     import std.meta: anySatisfy;
689     static if (anySatisfy!(hasZeroShiftFieldMember, Iterators))
690     /// Defined if at least one of `Iterators` has member `assumeFieldsHaveZeroShift`.
691     auto assumeFieldsHaveZeroShift() @property
692     {
693         import std.meta: staticMap;
694         alias _fields = _iterators;
695         return mixin("ZipField!(staticMap!(ZeroShiftField, Iterators))(" ~ applyAssumeZeroShift!Iterators ~ ")");
696     }
697 }
698 
699 ///
700 pure nothrow @nogc version(mir_ndslice_test) unittest
701 {
702     import mir.ndslice.traits: isIterator;
703 
704     double[10] data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
705     alias ItA = IotaIterator!int;
706     alias ItB = double*;
707     alias ItZ = ZipIterator!(ItA, ItB);
708     auto zip = ItZ(ItA(3), data.ptr);
709     assert((*zip).a == 3);
710     assert((*zip).b == 1);
711 
712     // iteration
713     ++zip;
714     assert((*zip).a == 3 + 1);
715     assert((*zip).b == 1 + 1);
716     assert(&(*zip).b() == data.ptr + 1);
717     
718     assert(zip[4].a == 3 + 5);
719     assert(zip[4].b == 1 + 5);
720     assert(&zip[4].b() == data.ptr + 5);
721 
722     --zip;
723     assert((*zip).a == 3);
724     assert((*zip).b == 1);
725 
726     assert((*(zip + 2)).a == 3 + 2);
727     assert((*(zip - 3)).a == 3 + -3);
728     assert((*(zip + 2)).b == 1 + 2);
729     assert((*(zip + 3 - 3)).b == 1);
730     assert((zip - 3).opBinary!"-"(zip) == -3);
731 
732     assert(zip == zip);
733     assert(zip - 1 < zip);
734 
735     static assert(isIterator!(ZipIterator!(double*, int*)));
736     static assert(isIterator!(ZipIterator!(immutable(double)*, immutable(int)*)));
737 }
738 
739 ///
740 struct CachedIterator(Iterator, CacheIterator, FlagIterator)
741 {
742     ///
743     Iterator _iterator;
744     ///
745     CacheIterator _caches;
746     ///
747     FlagIterator _flags;
748 
749 @fmamath:
750 
751     ///
752     auto lightScope()() return scope @property
753     {
754         return CachedIterator!(LightScopeOf!Iterator, LightScopeOf!CacheIterator, LightScopeOf!FlagIterator)(
755             .lightScope(_iterator),
756             .lightScope(_caches),
757             .lightScope(_flags),
758             );
759     }
760 
761     ///
762     auto lightScope()() return scope const @property
763     {
764         return lightConst.lightScope;
765     }
766 
767     ///
768     auto lightScope()() return scope immutable @property
769     {
770         return lightImmutable.lightScope;
771     }
772 
773     ///
774     auto lightConst()() const @property
775     {
776         return CachedIterator!(LightConstOf!Iterator, CacheIterator, FlagIterator)(
777             .lightConst(_iterator),
778             *cast(CacheIterator*)&_caches,
779             *cast(FlagIterator*)&_flags,
780             );
781     }
782 
783     ///
784     auto lightImmutable()() immutable @property @trusted
785     {
786         return CachedIterator!(LightImmutableOf!Iterator, CacheIterator, FlagIterator)(
787             .lightImmutable(_iterator),
788             *cast(CacheIterator*)&_caches,
789             *cast(FlagIterator*)&_flags,
790             );
791     }
792 
793     private alias T = typeof(Iterator.init[0]);
794     private alias UT = Unqual!T;
795 
796     auto opUnary(string op : "*")()
797     {
798         if (_expect(!*_flags, false))
799         {
800             _flags[0] = true;
801             emplaceRef!T(*cast(UT*)&*_caches, *_iterator);
802         }
803         return *_caches;
804     }
805 
806     auto opIndex()(ptrdiff_t index)
807     {
808         if (_expect(!_flags[index], false))
809         {
810             _flags[index] = true;
811             emplaceRef!T(*cast(UT*)&(_caches[index]), _iterator[index]);
812         }
813         return _caches[index];
814     }
815 
816     auto ref opIndexAssign(T)(auto ref T val, ptrdiff_t index)
817     {
818         _flags[index] = true;
819         return _caches[index] = val;
820     }
821 
822     void opUnary(string op)() scope
823         if (op == "--" || op == "++")
824     {
825         mixin(op ~ "_iterator;");
826         mixin(op ~ "_caches;");
827         mixin(op ~ "_flags;");
828     }
829 
830     void opOpAssign(string op)(ptrdiff_t index) scope
831         if (op == "-" || op == "+")
832     {
833         mixin("_iterator" ~ op ~ "= index;");
834         mixin("_caches" ~ op ~ "= index;");
835         mixin("_flags" ~ op ~ "= index;");
836     }
837 
838     auto opBinary(string op)(ptrdiff_t index)
839         if (op == "+" || op == "-")
840     {
841         auto ret = this;
842         mixin(`ret ` ~ op ~ `= index;`);
843         return ret;
844     }
845 
846     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
847     { return this._iterator - right._iterator; }
848 
849     bool opEquals()(scope ref const typeof(this) right) scope const
850     { return this._iterator == right._iterator; }
851 
852     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
853     {
854         static if (isPointer!Iterator)
855             return this._iterator - right._iterator;
856         else
857             return this._iterator.opCmp(right._iterator);
858     }
859 }
860 
861 private enum map_primitives = q{
862 
863     import mir.functional: Tuple, autoExpandAndForward;
864 
865     auto ref opUnary(string op : "*")()
866     {
867         static if (is(typeof(*_iterator) : Tuple!T, T...))
868         {
869             auto t = *_iterator;
870             return _fun(autoExpandAndForward!t);
871         }
872         else
873             return _fun(*_iterator);
874     }
875 
876     auto ref opIndex(ptrdiff_t index) scope
877     {
878         static if (is(typeof(_iterator[0]) : Tuple!T, T...))
879         {
880             auto t = _iterator[index];
881             return _fun(autoExpandAndForward!t);
882         }
883         else
884             return _fun(_iterator[index]);
885     }
886 
887     static if (!__traits(compiles, &opIndex(ptrdiff_t.init)))
888     {
889         auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope
890         {
891             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
892             {
893                 auto t = _iterator[index];
894                 return _fun(autoExpandAndForward!t) = value;
895             }
896             else
897                 return _fun(_iterator[index]) = value;
898         }
899 
900         auto ref opIndexUnary(string op)(ptrdiff_t index)
901         {
902             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
903             {
904                 auto t = _iterator[index];
905                 return _fun(autoExpandAndForward!t);
906             }
907             else
908                 return mixin(op ~ "_fun(_iterator[index])");
909         }
910 
911         auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index)
912         {
913             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
914             {
915                 auto t = _iterator[index];
916                 return mixin("_fun(autoExpandAndForward!t)" ~ op ~ "= value");
917             }
918             else
919                 return mixin("_fun(_iterator[index])" ~ op ~ "= value");
920         }
921     }
922 };
923 
924 /++
925 `VmapIterator` is used by $(SUBREF topology, map).
926 +/
927 struct VmapIterator(Iterator, Fun)
928 {
929 @fmamath:
930 
931     ///
932     Iterator _iterator;
933     ///
934     Fun _fun;
935 
936     ///
937     auto lightConst()() const @property
938     {
939         return VmapIterator!(LightConstOf!Iterator, LightConstOf!Fun)(.lightConst(_iterator), .lightConst(_fun));
940     }
941 
942     ///
943     auto lightImmutable()() immutable @property
944     {
945         return VmapIterator!(LightImmutableOf!Iterator, LightImmutableOf!Fun)(.lightImmutable(_iterator), .lightImmutable(_fun));
946     }
947 
948     import mir.functional: Tuple, autoExpandAndForward;
949 
950     auto ref opUnary(string op : "*")()
951     {
952         static if (is(typeof(*_iterator) : Tuple!T, T...))
953         {
954             auto t = *_iterator;
955             return _fun(autoExpandAndForward!t);
956         }
957         else
958             return _fun(*_iterator);
959     }
960 
961     auto ref opIndex(ptrdiff_t index) scope
962     {
963         static if (is(typeof(_iterator[0]) : Tuple!T, T...))
964         {
965             auto t = _iterator[index];
966             return _fun(autoExpandAndForward!t);
967         }
968         else
969             return _fun(_iterator[index]);
970     }
971 
972     static if (!__traits(compiles, &opIndex(ptrdiff_t.init)))
973     {
974         auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope
975         {
976             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
977             {
978                 auto t = _iterator[index];
979                 return _fun(autoExpandAndForward!t) = value;
980             }
981             else
982                 return _fun(_iterator[index]) = value;
983         }
984 
985         auto ref opIndexUnary(string op)(ptrdiff_t index)
986         {
987             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
988             {
989                 auto t = _iterator[index];
990                 return mixin(op ~ "_fun(autoExpandAndForward!t)");
991             }
992             else
993                 return mixin(op ~ "_fun(_iterator[index])");
994         }
995 
996         auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index)
997         {
998             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
999             {
1000                 auto t = _iterator[index];
1001                 return mixin("_fun(autoExpandAndForward!t)" ~ op ~ "= value");
1002             }
1003             else
1004                 return mixin("_fun(_iterator[index])" ~ op ~ "= value");
1005         }
1006     }
1007     
1008     mixin(std_ops);
1009 
1010     static if (hasZeroShiftFieldMember!Iterator)
1011     ///
1012     auto assumeFieldsHaveZeroShift() @property
1013     {
1014         return _vmapField(_iterator.assumeFieldsHaveZeroShift, _fun);
1015     }
1016 }
1017 
1018 auto MapIterator__map(Iterator, alias fun0, alias fun)(ref MapIterator!(Iterator, fun0) it)
1019 {
1020     return MapIterator!(Iterator, fun)(it._iterator);
1021 }
1022 
1023 /++
1024 `MapIterator` is used by $(SUBREF topology, map).
1025 +/
1026 struct MapIterator(Iterator, alias _fun)
1027 {
1028 @fmamath:
1029     ///
1030     Iterator _iterator;
1031 
1032     ///
1033     auto lightConst()() const @property
1034     {
1035         return MapIterator!(LightConstOf!Iterator, _fun)(.lightConst(_iterator));
1036     }
1037 
1038     ///
1039     auto lightImmutable()() immutable @property
1040     {
1041         return MapIterator!(LightImmutableOf!Iterator, _fun)(.lightImmutable(_iterator));
1042     }
1043 
1044     import mir.functional: pipe, autoExpandAndForward;
1045     ///
1046     static alias __map(alias fun1) = MapIterator__map!(Iterator, _fun, pipe!(_fun, fun1));
1047 
1048     import mir.functional: Tuple, autoExpandAndForward;
1049 
1050     auto ref opUnary(string op : "*")()
1051     {
1052         static if (is(typeof(*_iterator) : Tuple!T, T...))
1053         {
1054             auto t = *_iterator;
1055             return _fun(autoExpandAndForward!t);
1056         }
1057         else
1058             return _fun(*_iterator);
1059     }
1060 
1061     auto ref opIndex(ptrdiff_t index) scope
1062     {
1063         static if (is(typeof(_iterator[0]) : Tuple!T, T...))
1064         {
1065             auto t = _iterator[index];
1066             return _fun(autoExpandAndForward!t);
1067         }
1068         else
1069             return _fun(_iterator[index]);
1070     }
1071 
1072     static if (!__traits(compiles, &opIndex(ptrdiff_t.init)))
1073     {
1074         auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope
1075         {
1076             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
1077             {
1078                 auto t = _iterator[index];
1079                 return _fun(autoExpandAndForward!t) = value;
1080             }
1081             else
1082                 return _fun(_iterator[index]) = value;
1083         }
1084 
1085         auto ref opIndexUnary(string op)(ptrdiff_t index)
1086         {
1087             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
1088             {
1089                 auto t = _iterator[index];
1090                 return mixin(op ~ "_fun(autoExpandAndForward!t)");
1091             }
1092             else
1093                 return mixin(op ~ "_fun(_iterator[index])");
1094         }
1095 
1096         auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index)
1097         {
1098             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
1099             {
1100                 auto t = _iterator[index];
1101                 return mixin("_fun(autoExpandAndForward!t)" ~ op ~ "= value");
1102             }
1103             else
1104                 return mixin("_fun(_iterator[index])" ~ op ~ "= value");
1105         }
1106     }
1107     
1108     mixin(std_ops);
1109 
1110     static if (hasZeroShiftFieldMember!Iterator)
1111     ///
1112     auto assumeFieldsHaveZeroShift() @property
1113     {
1114         return _mapField!_fun(_iterator.assumeFieldsHaveZeroShift);
1115     }
1116 }
1117 
1118 /+
1119 Creates a mapped iterator. Uses `__map` if possible.
1120 +/
1121 auto _mapIterator(alias fun, Iterator)(Iterator iterator)
1122 {
1123     import core.lifetime: move;
1124     static if (__traits(hasMember, Iterator, "__map"))
1125     {
1126         static if (is(Iterator : MapIterator!(Iter0, fun0), Iter0, alias fun0)
1127                 && !__traits(compiles, Iterator.__map!fun(iterator)))
1128         {
1129             // https://github.com/libmir/mir-algorithm/issues/111
1130             debug(mir) pragma(msg, __FUNCTION__~" not coalescing chained map calls into a single lambda, possibly because of multiple embedded context pointers");
1131             return MapIterator!(Iterator, fun)(move(iterator));
1132         }
1133         else
1134             return Iterator.__map!fun(iterator);
1135     }
1136     else
1137        return MapIterator!(Iterator, fun)(move(iterator));
1138 }
1139 
1140 
1141 /+
1142 Creates a mapped iterator. Uses `__vmap` if possible.
1143 +/
1144 auto _vmapIterator(Iterator, Fun)(Iterator iterator, Fun fun)
1145 {
1146     static if (__traits(hasMember, Iterator, "__vmap"))
1147         return Iterator.__vmap(iterator, fun);
1148     else
1149        return MapIterator!(Iterator, fun)(iterator);
1150 }
1151 
1152 @safe pure nothrow @nogc version(mir_ndslice_test) unittest
1153 {
1154     // https://github.com/libmir/mir-algorithm/issues/111
1155     import mir.ndslice.topology : iota, map;
1156     import mir.functional : pipe;
1157 
1158     static auto foo(T)(T x)
1159     {
1160         return x.map!(a => a + 1);
1161     }
1162 
1163     static auto bar(T)(T x)
1164     {
1165         return foo(x).map!(a => a + 2);
1166     }
1167 
1168     auto data = iota(5);
1169     auto result = iota([5], 3);
1170 
1171     auto x = data.map!(a => a + 1).map!(a => a + 2);
1172     assert(x == result);
1173 
1174     auto y = bar(data);
1175     assert(y == result);
1176 }
1177 
1178 /++
1179 `NeighboursIterator` is used by $(SUBREF topology, map).
1180 +/
1181 struct NeighboursIterator(Iterator, size_t N, alias _fun, bool around)
1182 {
1183     import std.meta: AliasSeq;
1184 @fmamath:
1185     ///
1186     Iterator _iterator;
1187     static if (N)
1188         Iterator[2][N] _neighbours;
1189     else alias _neighbours = AliasSeq!();
1190 
1191     ///
1192     auto lightConst()() const @property
1193     {
1194         LightConstOf!Iterator[2][N] neighbours;
1195         foreach (i; 0 .. N)
1196         {
1197             neighbours[i][0] = .lightConst(_neighbours[i][0]);
1198             neighbours[i][1] = .lightConst(_neighbours[i][1]);
1199         }
1200         return NeighboursIterator!(LightConstOf!Iterator, N, _fun, around)(.lightConst(_iterator), neighbours);
1201     }
1202 
1203     ///
1204     auto lightImmutable()() immutable @property
1205     {
1206         LightImmutableOf!Iterator[2][N] neighbours;
1207         foreach (i; 0 .. N)
1208         {
1209             neighbours[i][0] = .lightImmutable(_neighbours[i][0]);
1210             neighbours[i][1] = .lightImmutable(_neighbours[i][1]);
1211         }
1212         return NeighboursIterator!(LightImmutableOf!Iterator, N, _fun, around)(.lightImmutable(_iterator), neighbours);
1213     }
1214 
1215     import mir.functional: Tuple, _ref;
1216 
1217     private alias RA = Unqual!(typeof(_fun(_iterator[-1], _iterator[+1])));
1218     private alias Result = Tuple!(_zip_types!Iterator, RA);
1219 
1220     auto ref opUnary(string op : "*")()
1221     {
1222         return opIndex(0);
1223     }
1224 
1225     auto ref opIndex(ptrdiff_t index) scope
1226     {
1227         static if (around)
1228             RA result = _fun(_iterator[index - 1], _iterator[index + 1]);
1229 
1230         foreach (i; Iota!N)
1231         {
1232             static if (i == 0 && !around)
1233                 RA result = _fun(_neighbours[i][0][index], _neighbours[i][1][index]);
1234             else
1235                 result = _fun(result, _fun(_neighbours[i][0][index], _neighbours[i][1][index]));
1236         }
1237         static if (__traits(compiles, &_iterator[index]))
1238             return Result(_ref(_iterator[index]), result);
1239         else
1240             return Result(_iterator[index], result);
1241     }
1242     
1243     void opUnary(string op)() scope
1244         if (op == "--" || op == "++")
1245     {
1246         mixin(op ~ "_iterator;");
1247         foreach (i; Iota!N)
1248         {
1249             mixin(op ~ "_neighbours[i][0];");
1250             mixin(op ~ "_neighbours[i][1];");
1251         }
1252     }
1253 
1254     void opOpAssign(string op)(ptrdiff_t index) scope
1255         if (op == "-" || op == "+")
1256     {
1257 
1258         mixin("_iterator " ~ op ~ "= index;");
1259         foreach (i; Iota!N)
1260         {
1261             mixin("_neighbours[i][0] " ~ op ~ "= index;");
1262             mixin("_neighbours[i][1] " ~ op ~ "= index;");
1263         }
1264     }
1265 
1266     auto opBinary(string op)(ptrdiff_t index)
1267         if (op == "+" || op == "-")
1268     {
1269         auto ret = this;
1270         mixin(`ret ` ~ op ~ `= index;`);
1271         return ret;
1272     }
1273 
1274     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
1275     { return this._iterator - right._iterator; }
1276 
1277     bool opEquals()(scope ref const typeof(this) right) scope const
1278     { return this._iterator == right._iterator; }
1279 
1280     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
1281     {
1282         static if (isPointer!Iterator)
1283             return this._iterator - right._iterator;
1284         else
1285             return this._iterator.opCmp(right._iterator);
1286     }
1287 }
1288 
1289 /++
1290 `MemberIterator` is used by $(SUBREF topology, member).
1291 +/
1292 struct MemberIterator(Iterator, string member)
1293 {
1294 @fmamath:
1295     ///
1296     Iterator _iterator;
1297 
1298     ///
1299     auto lightConst()() const @property
1300     {
1301         return MemberIterator!(LightConstOf!Iterator, member)(.lightConst(_iterator));
1302     }
1303 
1304     ///
1305     auto lightImmutable()() immutable @property
1306     {
1307         return MemberIterator!(LightImmutableOf!Iterator, member)(.lightImmutable(_iterator));
1308     }
1309 
1310     auto ref opUnary(string op : "*")()
1311     {
1312         return __traits(getMember, *_iterator, member);
1313     }
1314 
1315     auto ref opIndex()(ptrdiff_t index)
1316     {
1317         return __traits(getMember, _iterator[index], member);
1318     }
1319 
1320     static if (!__traits(compiles, &opIndex(ptrdiff_t.init)))
1321     {
1322         auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope
1323         {
1324             return __traits(getMember, _iterator[index], member) = value;
1325         }
1326 
1327         auto ref opIndexUnary(string op)(ptrdiff_t index)
1328         {
1329             return mixin(op ~ "__traits(getMember, _iterator[index], member)");
1330         }
1331 
1332         auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index)
1333         {
1334             return mixin("__traits(getMember, _iterator[index], member)" ~ op ~ "= value");
1335         }
1336     }
1337 
1338     mixin(std_ops);
1339 }
1340 
1341 /++
1342 `BytegroupIterator` is used by $(SUBREF topology, Bytegroup) and $(SUBREF topology, bytegroup).
1343 +/
1344 struct BytegroupIterator(Iterator, size_t count, DestinationType)
1345     if (count)
1346 {
1347 @fmamath:
1348     ///
1349     Iterator _iterator;
1350 
1351     ///
1352     auto lightConst()() const @property
1353     {
1354         return BytegroupIterator!(LightConstOf!Iterator, count, DestinationType)(.lightConst(_iterator));
1355     }
1356 
1357     ///
1358     auto lightImmutable()() immutable @property
1359     {
1360         return BytegroupIterator!(LightImmutableOf!Iterator, count, DestinationType)(.lightImmutable(_iterator));
1361     }
1362 
1363     package(mir) alias Byte = Unqual!(typeof(_iterator[0]));
1364 
1365     version(LittleEndian)
1366         private enum BE = false;
1367     else
1368         private enum BE = true;
1369 
1370     private union U
1371     {
1372         DestinationType value;
1373         static if (DestinationType.sizeof > Byte[count].sizeof && BE && isScalarType!DestinationType)
1374         {
1375             struct
1376             {
1377                 ubyte[DestinationType.sizeof - Byte[count].sizeof] shiftPayload;
1378                 Byte[count] bytes;
1379             }
1380         }
1381         else
1382         {
1383             Byte[count] bytes;
1384         }
1385     }
1386 
1387     DestinationType opUnary(string op : "*")()
1388     {
1389         U ret = { value: DestinationType.init };
1390         foreach (i; Iota!count)
1391             ret.bytes[i] = _iterator[i];
1392         return ret.value;
1393     }
1394 
1395     DestinationType opIndex()(ptrdiff_t index)
1396     {
1397         return *(this + index);
1398     }
1399 
1400     DestinationType opIndexAssign(T)(T val, ptrdiff_t index) scope
1401     {
1402         auto it = this + index;
1403         U ret = { value: val };
1404         foreach (i; Iota!count)
1405             it._iterator[i] = ret.bytes[i];
1406         return ret.value;
1407     }
1408 
1409     void opUnary(string op)() scope
1410         if (op == "--" || op == "++")
1411     { mixin("_iterator " ~ op[0] ~ "= count;"); }
1412 
1413     void opOpAssign(string op)(ptrdiff_t index) scope
1414         if (op == "-" || op == "+")
1415     { mixin("_iterator " ~ op ~ "= index * count;"); }
1416 
1417     auto opBinary(string op)(ptrdiff_t index)
1418         if (op == "+" || op == "-")
1419     {
1420         auto ret = this;
1421         mixin(`ret ` ~ op ~ `= index;`);
1422         return ret;
1423     }
1424 
1425     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
1426     { return (this._iterator - right._iterator) / count; }
1427 
1428     bool opEquals()(scope ref const typeof(this) right) scope const
1429     { return this._iterator == right._iterator; }
1430 
1431     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
1432     {
1433         static if (isPointer!Iterator)
1434             return this._iterator - right._iterator;
1435         else
1436             return this._iterator.opCmp(right._iterator);
1437     }
1438 }
1439 
1440 auto SlideIterator__map(Iterator, size_t params, alias fun0, alias fun)(SlideIterator!(Iterator, params, fun0) it)
1441 {
1442     return SlideIterator!(Iterator, params, fun)(it._iterator);
1443 }
1444 
1445 /++
1446 `SlideIterator` is used by $(SUBREF topology, diff) and $(SUBREF topology, slide).
1447 +/
1448 struct SlideIterator(Iterator, size_t params, alias fun)
1449     if (params > 1)
1450 {
1451 @fmamath:
1452     ///
1453     Iterator _iterator;
1454 
1455     ///
1456     auto lightConst()() const @property
1457     {
1458         return SlideIterator!(LightConstOf!Iterator, params, fun)(.lightConst(_iterator));
1459     }
1460 
1461     ///
1462     auto lightImmutable()() immutable @property
1463     {
1464         return SlideIterator!(LightImmutableOf!Iterator, params, fun)(.lightImmutable(_iterator));
1465     }
1466 
1467     import mir.functional: pipe;
1468     ///
1469     static alias __map(alias fun1) = SlideIterator__map!(Iterator, params, fun, pipe!(fun, fun1));
1470 
1471     auto ref opUnary(string op : "*")()
1472     {
1473         return mixin("fun(" ~ _iotaArgs!(params, "_iterator[", "], ") ~ ")");
1474     }
1475 
1476     auto ref opIndex()(ptrdiff_t index)
1477     {
1478         return mixin("fun(" ~ _iotaArgs!(params, "_iterator[index + ", "], ") ~ ")");
1479     }
1480 
1481     mixin(std_ops);
1482 }
1483 
1484 ///
1485 version(mir_ndslice_test) unittest
1486 {
1487     import mir.functional: naryFun;
1488     auto data = [1, 3, 8, 18];
1489     auto diff = SlideIterator!(int*, 2, naryFun!"b - a")(data.ptr);
1490     assert(*diff == 2);
1491     assert(diff[1] == 5);
1492     assert(diff[2] == 10);
1493 }
1494 
1495 auto IndexIterator__map(Iterator, Field, alias fun)(ref IndexIterator!(Iterator, Field) it)
1496 {
1497     auto field = it._field._mapField!fun;
1498     return IndexIterator!(Iterator, typeof(field))(it._iterator, field);
1499 }
1500 
1501 version(mir_ndslice_test) unittest
1502 {
1503     import mir.ndslice.topology;
1504     import mir.ndslice.allocation;
1505     import mir.ndslice.slice;
1506     auto indices = [4, 3, 1, 2, 0, 4].sliced;
1507     auto v = iota(5).indexed(indices).map!(a => a).slice;
1508     uint r;
1509     auto w = iota(5).indexed(indices).map!(a => a).map!(a => a * r).slice;
1510 }
1511 
1512 /++
1513 Iterates a field using an iterator.
1514 
1515 `IndexIterator` is used by $(SUBREF topology, indexed).
1516 +/
1517 struct IndexIterator(Iterator, Field)
1518 {
1519     import mir.functional: Tuple, autoExpandAndForward;
1520 
1521 @fmamath:
1522     ///
1523     Iterator _iterator;
1524     ///
1525     Field _field;
1526 
1527     ///
1528     auto lightConst()() const @property
1529     {
1530         return IndexIterator!(LightConstOf!Iterator, LightConstOf!Field)(.lightConst(_iterator), .lightConst(_field));
1531     }
1532 
1533     ///
1534     auto lightImmutable()() immutable @property
1535     {
1536         return IndexIterator!(LightImmutableOf!Iterator, LightImmutableOf!Field)(.lightImmutable(_iterator), _field.lightImmutable);
1537     }
1538 
1539     ///
1540     static alias __map(alias fun) = IndexIterator__map!(Iterator, Field, fun);
1541 
1542     auto ref opUnary(string op : "*")()
1543     {
1544         static if (is(typeof(_iterator[0]) : Tuple!T, T...))
1545         {
1546             auto t = *_iterator;
1547             return _field[autoExpandAndForward!t];
1548         }
1549         else
1550             return _field[*_iterator];
1551     }
1552 
1553     auto ref opIndex()(ptrdiff_t index)
1554     {
1555         static if (is(typeof(_iterator[0]) : Tuple!T, T...))
1556         {
1557             auto t = _iterator[index];
1558             return _field[autoExpandAndForward!t];
1559         }
1560         else
1561             return _field[_iterator[index]];
1562     }
1563 
1564     static if (!__traits(compiles, &opIndex(ptrdiff_t.init)))
1565     {
1566         auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index) scope
1567         {
1568             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
1569             {
1570                 auto t = _iterator[index];
1571                 return _field[autoExpandAndForward!t] = value;
1572             }
1573             else
1574                 return _field[_iterator[index]] = value;
1575         }
1576 
1577         auto ref opIndexUnary(string op)(ptrdiff_t index)
1578         {
1579             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
1580             {
1581                 auto t = _iterator[index];
1582                 return mixin(op ~ "_field[autoExpandAndForward!t]");
1583             }
1584             else
1585                 return mixin(op ~ "_field[_iterator[index]]");
1586         }
1587 
1588         auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index)
1589         {
1590             static if (is(typeof(_iterator[0]) : Tuple!T, T...))
1591             {
1592                 auto t = _iterator[index];
1593                 return mixin("_field[autoExpandAndForward!t]" ~ op ~ "= value");
1594             }
1595             else
1596                 return mixin("_field[_iterator[index]]" ~ op ~ "= value");
1597         }
1598     }
1599 
1600     mixin(std_ops);
1601 }
1602 
1603 /++
1604 Iterates chunks in a sliceable using an iterator composed of indices.
1605 
1606 Definition:
1607 ----
1608 auto index = iterator[i];
1609 auto elem  = sliceable[index[0] .. index[1]];
1610 ----
1611 +/
1612 struct SubSliceIterator(Iterator, Sliceable)
1613 {
1614 @fmamath:
1615     ///
1616     Iterator _iterator;
1617     ///
1618     Sliceable _sliceable;
1619 
1620     ///
1621     auto lightConst()() const @property
1622     {
1623         return SubSliceIterator!(LightConstOf!Iterator, LightConstOf!Sliceable)(.lightConst(_iterator), _sliceable.lightConst);
1624     }
1625 
1626     ///
1627     auto lightImmutable()() immutable @property
1628     {
1629         return SubSliceIterator!(LightImmutableOf!Iterator, LightImmutableOf!Sliceable)(.lightImmutable(_iterator), _sliceable.lightImmutable);
1630     }
1631 
1632     auto ref opUnary(string op : "*")()
1633     {
1634         auto i = *_iterator;
1635         return _sliceable[i[0] .. i[1]];
1636     }
1637 
1638     auto ref opIndex()(ptrdiff_t index)
1639     {
1640         auto i = _iterator[index];
1641         return _sliceable[i[0] .. i[1]];
1642     }
1643 
1644     mixin(std_ops);
1645 }
1646 
1647 /++
1648 Iterates chunks in a sliceable using an iterator composed of indices stored consequently.
1649 
1650 Definition:
1651 ----
1652 auto elem  = _sliceable[_iterator[index] .. _iterator[index + 1]];
1653 ----
1654 +/
1655 struct ChopIterator(Iterator, Sliceable)
1656 {
1657 @fmamath:
1658     ///
1659     Iterator _iterator;
1660     ///
1661     Sliceable _sliceable;
1662 
1663     ///
1664     auto lightConst()() const @property
1665     {
1666         return ChopIterator!(LightConstOf!Iterator, LightConstOf!Sliceable)(.lightConst(_iterator), _sliceable.lightConst);
1667     }
1668 
1669     ///
1670     auto lightImmutable()() immutable @property
1671     {
1672         return ChopIterator!(LightImmutableOf!Iterator, LightImmutableOf!Sliceable)(.lightImmutable(_iterator), _sliceable.lightImmutable);
1673     }
1674 
1675     auto ref opUnary(string op : "*")()
1676     {
1677         return _sliceable[*_iterator .. _iterator[1]];
1678     }
1679 
1680     auto ref opIndex()(ptrdiff_t index)
1681     {
1682         return _sliceable[_iterator[index] .. _iterator[index + 1]];
1683     }
1684 
1685     mixin(std_ops);
1686 }
1687 
1688 /++
1689 Iterates on top of another iterator and returns a slice
1690 as a multidimensional window at the current position.
1691 
1692 `SliceIterator` is used by $(SUBREF topology, map) for packed slices.
1693 +/
1694 struct SliceIterator(Iterator, size_t N = 1, SliceKind kind = Contiguous)
1695 {
1696 @fmamath:
1697     ///
1698     alias Element = Slice!(Iterator, N, kind);
1699     ///
1700     Element._Structure _structure;
1701     ///
1702     Iterator _iterator;
1703 
1704     ///
1705     auto lightConst()() const @property
1706     {
1707         return SliceIterator!(LightConstOf!Iterator, N, kind)(_structure, .lightConst(_iterator));
1708     }
1709 
1710     ///
1711     auto lightImmutable()() immutable @property
1712     {
1713         return SliceIterator!(LightImmutableOf!Iterator, N, kind)(_structure, .lightImmutable(_iterator));
1714     }
1715 
1716     auto opUnary(string op : "*")()
1717     {
1718         return Element(_structure, _iterator);
1719     }
1720 
1721     auto opIndex()(ptrdiff_t index)
1722     {
1723         return Element(_structure, _iterator + index);
1724     }
1725 
1726     mixin(std_ops);
1727 }
1728 
1729 public auto FieldIterator__map(Field, alias fun)(FieldIterator!(Field) it)
1730 {
1731     import mir.ndslice.field: _mapField;
1732     auto field = it._field._mapField!fun;
1733     return FieldIterator!(typeof(field))(it._index, field);
1734 }
1735 
1736 version(mir_ndslice_test) unittest
1737 {
1738     import mir.ndslice.topology;
1739     import mir.ndslice.allocation;
1740     auto v = ndiota(3, 3).map!(a => a).slice;
1741     uint r;
1742     auto w = ndiota(3, 3).map!(a => a).map!(a => a[0] * r).slice;
1743 }
1744 
1745 /++
1746 Creates an iterator on top of a field.
1747 
1748 `FieldIterator` is used by $(SUBREF slice, slicedField), $(SUBREF topology, bitwise), $(SUBREF topology, ndiota), and others.
1749 +/
1750 struct FieldIterator(Field)
1751 {
1752 @fmamath:
1753     ///
1754     ptrdiff_t _index;
1755     ///
1756     Field _field;
1757 
1758     ///
1759     auto lightConst()() const @property
1760     {
1761         return FieldIterator!(LightConstOf!Field)(_index, .lightConst(_field));
1762     }
1763 
1764     ///
1765     auto lightImmutable()() immutable @property
1766     {
1767         return FieldIterator!(LightImmutableOf!Field)(_index, .lightImmutable(_field));
1768     }
1769 
1770     ///
1771     static alias __map(alias fun) = FieldIterator__map!(Field, fun);
1772 
1773     ///
1774     Slice!(IotaIterator!size_t) opSlice(size_t dimension)(size_t i, size_t j) scope const
1775     {
1776         assert(i <= j);
1777         return typeof(return)(j - i, typeof(return).Iterator(i));
1778     }
1779 
1780     /++
1781     Returns:
1782         `_field[_index + sl.i .. _index + sl.j]`.
1783     +/
1784     auto opIndex()(Slice!(IotaIterator!size_t) sl)
1785     {
1786         auto idx = _index + sl._iterator._index;
1787         return _field[idx .. idx + sl.length];
1788     }
1789 
1790     auto ref opUnary(string op : "*")()
1791     { return _field[_index]; }
1792 
1793     void opUnary(string op)() scope
1794         if (op == "++" || op == "--")
1795     { mixin(op ~ `_index;`); }
1796 
1797     auto ref opIndex()(ptrdiff_t index)
1798     { return _field[_index + index]; }
1799 
1800     static if (!__traits(compiles, &_field[_index]))
1801     {
1802         auto ref opIndexAssign(T)(auto ref T value, ptrdiff_t index)
1803         { return _field[_index + index] = value; }
1804 
1805         auto ref opIndexUnary(string op)(ptrdiff_t index)
1806         { mixin (`return ` ~ op ~ `_field[_index + index];`); }
1807 
1808         auto ref opIndexOpAssign(string op, T)(T value, ptrdiff_t index)
1809         { mixin (`return _field[_index + index] ` ~ op ~ `= value;`); }
1810     }
1811 
1812     void opOpAssign(string op)(ptrdiff_t index) scope
1813         if (op == "+" || op == "-")
1814     { mixin(`_index ` ~ op ~ `= index;`); }
1815 
1816     auto opBinary(string op)(ptrdiff_t index)
1817         if (op == "+" || op == "-")
1818     {
1819         auto ret = this;
1820         mixin(`ret ` ~ op ~ `= index;`);
1821         return ret;
1822     }
1823 
1824     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
1825     { return this._index - right._index; }
1826 
1827     bool opEquals()(scope ref const typeof(this) right) scope const
1828     { return this._index == right._index; }
1829 
1830     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
1831     { return this._index - right._index; }
1832 
1833     ///
1834     auto assumeFieldsHaveZeroShift() @property
1835     {
1836         if (_expect(_index != 0, false))
1837         {
1838             version (D_Exceptions)
1839                 { import mir.exception : toMutable; throw assumeZeroShiftException.toMutable; }
1840             else
1841                 assert(0, assumeZeroShiftExceptionMsg);
1842         }
1843         static if (hasZeroShiftFieldMember!Field)
1844             return _field.assumeFieldsHaveZeroShift;
1845         else
1846             return _field;
1847     }
1848 }
1849 
1850 auto FlattenedIterator__map(Iterator, size_t N, SliceKind kind, alias fun)(FlattenedIterator!(Iterator, N, kind) it)
1851 {
1852     import mir.ndslice.topology: map;
1853     auto slice = it._slice.map!fun;
1854     return FlattenedIterator!(TemplateArgsOf!(typeof(slice)))(it._indices, slice);
1855 }
1856 
1857 version(mir_ndslice_test) unittest
1858 {
1859     import mir.ndslice.topology;
1860     import mir.ndslice.allocation;
1861     auto v = iota(3, 3).universal.flattened.map!(a => a).slice;
1862     uint r;
1863     auto w = iota(3, 3).universal.flattened.map!(a => a).map!(a => a * r).slice;
1864 }
1865 
1866 /++
1867 Creates an iterator on top of all elements in a slice.
1868 
1869 `FieldIterator` is used by $(SUBREF topology, bitwise), $(SUBREF topology, ndiota), and others.
1870 +/
1871 struct FlattenedIterator(Iterator, size_t N, SliceKind kind)
1872     if (N > 1 && (kind == Universal || kind == Canonical))
1873 {
1874 @fmamath:
1875     ///
1876     ptrdiff_t[N] _indices;
1877     ///
1878     Slice!(Iterator, N, kind) _slice;
1879 
1880     ///
1881     auto lightConst()() const @property
1882     {
1883         return FlattenedIterator!(LightConstOf!Iterator, N, kind)(_indices, _slice.lightConst);
1884     }
1885 
1886     ///
1887     auto lightImmutable()() immutable @property
1888     {
1889         return FlattenedIterator!(LightImmutableOf!Iterator, N, kind)(_indices, _slice.lightImmutable);
1890     }
1891 
1892     ///
1893     static alias __map(alias fun) = FlattenedIterator__map!(Iterator, N, kind, fun);
1894 
1895     private ptrdiff_t getShift()(ptrdiff_t n)
1896     {
1897         ptrdiff_t _shift;
1898         n += _indices[$ - 1];
1899         foreach_reverse (i; Iota!(1, N))
1900         {
1901             immutable v = n / ptrdiff_t(_slice._lengths[i]);
1902             n %= ptrdiff_t(_slice._lengths[i]);
1903             static if (i == _slice.S)
1904                 _shift += (n - _indices[i]);
1905             else
1906                 _shift += (n - _indices[i]) * _slice._strides[i];
1907             n = _indices[i - 1] + v;
1908         }
1909         _shift += (n - _indices[0]) * _slice._strides[0];
1910         return _shift;
1911     }
1912 
1913     auto ref opUnary(string op : "*")()
1914     {
1915         return *_slice._iterator;
1916     }
1917 
1918     void opUnary(string op)() scope
1919         if (op == "--" || op == "++")
1920     {
1921         foreach_reverse (i; Iota!N)
1922         {
1923             static if (i == _slice.S)
1924                 mixin(op ~ `_slice._iterator;`);
1925             else
1926                 mixin(`_slice._iterator ` ~ op[0] ~ `= _slice._strides[i];`);
1927             mixin (op ~ `_indices[i];`);
1928             static if (i)
1929             {
1930                 static if (op == "++")
1931                 {
1932                     if (_indices[i] < _slice._lengths[i])
1933                         return;
1934                     static if (i == _slice.S)
1935                         _slice._iterator -= _slice._lengths[i];
1936                     else
1937                         _slice._iterator -= _slice._lengths[i] * _slice._strides[i];
1938                     _indices[i] = 0;
1939                 }
1940                 else
1941                 {
1942                     if (_indices[i] >= 0)
1943                         return;
1944                     static if (i == _slice.S)
1945                         _slice._iterator += _slice._lengths[i];
1946                     else
1947                         _slice._iterator += _slice._lengths[i] * _slice._strides[i];
1948                     _indices[i] = _slice._lengths[i] - 1;
1949                 }
1950             }
1951         }
1952     }
1953 
1954     auto ref opIndex()(ptrdiff_t index)
1955     {
1956         return _slice._iterator[getShift(index)];
1957     }
1958 
1959     static if (isMutable!(_slice.DeepElement) && !_slice.hasAccessByRef)
1960     ///
1961     auto ref opIndexAssign(E)(scope ref E elem, size_t index) return scope
1962     {
1963         return _slice._iterator[getShift(index)] = elem;
1964     }
1965 
1966     void opOpAssign(string op : "+")(ptrdiff_t n) scope
1967     {
1968         ptrdiff_t _shift;
1969         n += _indices[$ - 1];
1970         foreach_reverse (i; Iota!(1, N))
1971         {
1972             immutable v = n / ptrdiff_t(_slice._lengths[i]);
1973             n %= ptrdiff_t(_slice._lengths[i]);
1974             static if (i == _slice.S)
1975                 _shift += (n - _indices[i]);
1976             else
1977                 _shift += (n - _indices[i]) * _slice._strides[i];
1978             _indices[i] = n;
1979             n = _indices[i - 1] + v;
1980         }
1981         _shift += (n - _indices[0]) * _slice._strides[0];
1982         _indices[0] = n;
1983         foreach_reverse (i; Iota!(1, N))
1984         {
1985             if (_indices[i] >= 0)
1986                 break;
1987             _indices[i] += _slice._lengths[i];
1988             _indices[i - 1]--;
1989         }
1990         _slice._iterator += _shift;
1991     }
1992 
1993     void opOpAssign(string op : "-")(ptrdiff_t n) scope
1994     { this += -n; }
1995 
1996     auto opBinary(string op)(ptrdiff_t index)
1997         if (op == "+" || op == "-")
1998     {
1999         auto ret = this;
2000         mixin(`ret ` ~ op ~ `= index;`);
2001         return ret;
2002     }
2003 
2004     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
2005     {
2006         ptrdiff_t ret = this._indices[0] - right._indices[0];
2007         foreach (i; Iota!(1, N))
2008         {
2009             ret *= _slice._lengths[i];
2010             ret += this._indices[i] - right._indices[i];
2011         }
2012         return ret;
2013     }
2014 
2015     bool opEquals()(scope ref const typeof(this) right) scope const
2016     {
2017         foreach_reverse (i; Iota!N)
2018             if (this._indices[i] != right._indices[i])
2019                 return false;
2020         return true;
2021     }
2022 
2023     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
2024     {
2025         foreach (i; Iota!(N - 1))
2026             if (auto ret = this._indices[i] - right._indices[i])
2027                 return ret;
2028         return this._indices[$ - 1] - right._indices[$ - 1];
2029     }
2030 }
2031 
2032 version(mir_ndslice_test) unittest
2033 {
2034     import mir.ndslice.topology;
2035     import mir.ndslice.slice;
2036 
2037     auto it0 = iota(3, 4).universal.flattened._iterator;
2038     auto it1 = it0;
2039     assert(it0 == it1);
2040     it0 += 5;
2041     assert(it0 > it1);
2042     it0 -= 5;
2043     assert(*it0 == *it1);
2044     assert(it0 == it1);
2045     it0 += 5;
2046     it0 += 7;
2047     it0 -= 9;
2048     assert(it0 > it1);
2049     it1 += 3;
2050     assert(*it0 == *it1);
2051     assert(it0 == it1);
2052     assert(it0 <= it1);
2053     assert(it0 >= it1);
2054 
2055     ++it0;
2056     ++it0;
2057     ++it0;
2058     ++it0;
2059     ++it0;
2060     ++it0;
2061     ++it0;
2062     ++it0;
2063     ++it0;
2064 
2065     assert(it0 - it1 == 9);
2066     assert(it1 - it0 == -9);
2067 
2068     ++it0;
2069 
2070     assert(it0 - it1 == 10);
2071     assert(it1 - it0 == -10);
2072 
2073     --it0;
2074 
2075     assert(it0 - it1 == 9);
2076     assert(it1 - it0 == -9);
2077     assert(it0[-9] == *it1);
2078     assert(*it0 == it1[9]);
2079 
2080     --it0;
2081     --it0;
2082     --it0;
2083     --it0;
2084     --it0;
2085     --it0;
2086     --it0;
2087     --it0;
2088     --it0;
2089     assert(*it0 == *it1);
2090     assert(it0 == it1);
2091     assert(it0 <= it1);
2092     assert(it0 >= it1);
2093 }
2094 
2095 /++
2096 `StairsIterator` is used by $(SUBREF topology, stairs).
2097 +/
2098 struct StairsIterator(Iterator, string direction)
2099     if (direction == "+" || direction == "-")
2100 {
2101     ///
2102     size_t _length;
2103 
2104     ///
2105     Iterator _iterator;
2106 
2107     ///
2108     auto lightConst()() const @property
2109     {
2110         return StairsIterator!(LightConstOf!Iterator, direction)(_length, .lightConst(_iterator));
2111     }
2112 
2113     ///
2114     auto lightImmutable()() immutable @property
2115     {
2116         return StairsIterator!(LightImmutableOf!Iterator, direction)(_length, .lightImmutable(_iterator));
2117     }
2118 
2119 @fmamath:
2120 
2121     ///
2122     Slice!Iterator opUnary(string op : "*")()
2123     {
2124         import mir.ndslice.slice: sliced;
2125         return _iterator.sliced(_length);
2126     }
2127 
2128     ///
2129     Slice!Iterator opIndex()(ptrdiff_t index)
2130     {
2131         import mir.ndslice.slice: sliced;
2132         static if (direction == "+")
2133         {
2134             auto newLength = _length + index;
2135             auto shift = ptrdiff_t(_length + newLength - 1) * index / 2;
2136         }
2137         else
2138         {
2139             auto newLength = _length - index;
2140             auto shift = ptrdiff_t(_length + newLength + 1) * index / 2;
2141         }
2142         assert(ptrdiff_t(newLength) >= 0);
2143         return (_iterator + shift).sliced(newLength);
2144     }
2145 
2146     void opUnary(string op)() scope
2147         if (op == "--" || op == "++")
2148     {
2149         static if (op == "++")
2150         {
2151             _iterator += _length;
2152             static if (direction == "+")
2153                 ++_length;
2154             else
2155                 --_length;
2156         }
2157         else
2158         {
2159             assert(_length);
2160             static if (direction == "+")
2161                 --_length;
2162             else
2163                 ++_length;
2164             _iterator -= _length;
2165         }
2166     }
2167 
2168     void opOpAssign(string op)(ptrdiff_t index) scope
2169         if (op == "-" || op == "+")
2170     {
2171         static if (op == direction)
2172             auto newLength = _length + index;
2173         else
2174             auto newLength = _length - index;
2175         static if (direction == "+")
2176             auto shift = ptrdiff_t(_length + newLength - 1) * index / 2;
2177         else
2178             auto shift = ptrdiff_t(_length + newLength + 1) * index / 2;
2179         assert(ptrdiff_t(newLength) >= 0);
2180         _length = newLength;
2181         static if (op == "+")
2182             _iterator += shift;
2183         else
2184             _iterator -= shift;
2185     }
2186 
2187     auto opBinary(string op)(ptrdiff_t index)
2188         if (op == "+" || op == "-")
2189     {
2190         auto ret = this;
2191         mixin(`ret ` ~ op ~ `= index;`);
2192         return ret;
2193     }
2194 
2195     ptrdiff_t opBinary(string op : "-")(scope ref const typeof(this) right) scope const
2196     {
2197         static if (direction == "+")
2198             return this._length - right._length;
2199         else
2200             return right._length - this._length;
2201     }
2202 
2203     bool opEquals()(scope ref const typeof(this) right) scope const
2204     { return this._length == right._length; }
2205 
2206     ptrdiff_t opCmp()(scope ref const typeof(this) right) scope const
2207     { return this - right; }
2208 }
2209 
2210 ///
2211 version(mir_ndslice_test) unittest
2212 {
2213     // 0
2214     // 1 2
2215     // 3 4 5
2216     // 6 7 8 9
2217     // 10 11 12 13 14
2218     auto it = StairsIterator!(IotaIterator!size_t, "+")(1, IotaIterator!size_t());
2219     assert(*it == [0]);
2220     assert(it[4] == [10, 11, 12, 13, 14]);
2221     assert(*(it + 4) == [10, 11, 12, 13, 14]);
2222     ++it;
2223     assert(*it == [1, 2]);
2224     it += 3;
2225     assert(*it == [10, 11, 12, 13, 14]);
2226     assert(it[-3] == [1, 2]);
2227     assert(*(it - 3) == [1, 2]);
2228     assert(it + 1 > it);
2229     assert(it + 1 - 1 == it);
2230     assert(it - 3 - it == -3);
2231     --it;
2232     assert(*it == [6, 7, 8, 9]);
2233 }
2234 
2235 ///
2236 version(mir_ndslice_test) unittest
2237 {
2238     // [0, 1, 2, 3, 4],
2239     //    [5, 6, 7, 8],
2240     //     [9, 10, 11],
2241     //        [12, 13],
2242     //            [14]]);
2243 
2244     auto it = StairsIterator!(IotaIterator!size_t, "-")(5, IotaIterator!size_t());
2245     assert(*it == [0, 1, 2, 3, 4]);
2246     assert(it[4] == [14]);
2247     assert(*(it + 4) == [14]);
2248     ++it;
2249     assert(*it == [5, 6, 7, 8]);
2250     it += 3;
2251     assert(*it == [14]);
2252     assert(it[-3] == [5, 6, 7, 8]);
2253     assert(*(it - 3) == [5, 6, 7, 8]);
2254     assert(it + 1 > it);
2255     assert(it + 1 - 1 == it);
2256     assert(it - 3 - it == -3);
2257     --it;
2258     assert(*it == [12, 13]);
2259 }
2260 
2261 /++
2262 Element type of $(LREF TripletIterator).
2263 +/
2264 struct Triplet(Iterator, SliceKind kind = Contiguous)
2265 {
2266 @fmamath:
2267     ///
2268     size_t _iterator;
2269     ///
2270     Slice!(Iterator, 1, kind) _slice;
2271 
2272     ///
2273     auto lightConst()() const @property
2274     {
2275         return Triplet!(LightConstOf!Iterator, kind)(_iterator, slice.lightConst);
2276     }
2277 
2278     ///
2279     auto lightImmutable()() immutable @property
2280     {
2281         return Triplet!(LightImmutableOf!Iterator, kind)(_iterator, slice.lightImmutable);
2282     }
2283 
2284     @property
2285     {
2286         ///
2287         auto ref center()
2288         {
2289             assert(_iterator < _slice.length);
2290             return _slice[_iterator];
2291         }
2292 
2293         ///
2294         Slice!(Iterator, 1, kind) left()
2295         {
2296             assert(_iterator < _slice.length);
2297             return _slice[0 .. _iterator];
2298         }
2299 
2300         ///
2301         Slice!(Iterator, 1, kind) right()
2302         {
2303             assert(_iterator < _slice.length);
2304             return _slice[_iterator + 1 .. $];
2305         }
2306     }
2307 }
2308 
2309 /++
2310 Iterates triplets position in a slice. 
2311 
2312 `TripletIterator` is used by $(SUBREF topology, triplets).
2313 +/
2314 struct TripletIterator(Iterator, SliceKind kind = Contiguous)
2315 {
2316 @fmamath:
2317 
2318     ///
2319     size_t _iterator;
2320     ///
2321     Slice!(Iterator, 1, kind) _slice;
2322 
2323     ///
2324     auto lightConst()() const @property
2325     {
2326         return TripletIterator!(LightConstOf!Iterator, kind)(_iterator, _slice.lightConst);
2327     }
2328 
2329     ///
2330     auto lightImmutable()() immutable @property
2331     {
2332         return TripletIterator!(LightImmutableOf!Iterator, kind)(_iterator, _slice.lightImmutable);
2333     }
2334 
2335     ///
2336     Triplet!(Iterator, kind) opUnary(string op : "*")()
2337     {
2338         return typeof(return)(_iterator, _slice);
2339     }
2340 
2341     ///
2342     Triplet!(Iterator, kind) opIndex()(ptrdiff_t index)
2343     {
2344         return typeof(return)(_iterator + index, _slice);
2345     }
2346 
2347     mixin(std_ops);
2348 }