24 changes: 13 additions & 11 deletions std/experimental/ndslice/iteration.d
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ import std.meta;
import std.experimental.ndslice.internal;
import std.experimental.ndslice.slice; //: Slice;

@fmb:

private enum _swappedCode = q{
with (slice)
{
Expand All @@ -130,7 +132,7 @@ See_also: $(LREF everted), $(LREF transposed)
+/
template swapped(size_t dimensionA, size_t dimensionB)
{
auto swapped(size_t N, Range)(Slice!(N, Range) slice)
@fmb auto swapped(size_t N, Range)(Slice!(N, Range) slice)
{
{
enum i = 0;
Expand Down Expand Up @@ -239,7 +241,7 @@ Returns:
+/
template rotated(size_t dimensionA, size_t dimensionB)
{
auto rotated(size_t N, Range)(Slice!(N, Range) slice, sizediff_t k = 1)
@fmb auto rotated(size_t N, Range)(Slice!(N, Range) slice, sizediff_t k = 1)
{
{
enum i = 0;
Expand Down Expand Up @@ -408,7 +410,7 @@ See_also: $(LREF swapped), $(LREF everted)
template transposed(Dimensions...)
if (Dimensions.length)
{
Slice!(N, Range) transposed(size_t N, Range)(auto ref Slice!(N, Range) slice)
@fmb Slice!(N, Range) transposed(size_t N, Range)(auto ref Slice!(N, Range) slice)
{
mixin DimensionsCountCTError;
foreach (i, dimension; Dimensions)
Expand Down Expand Up @@ -550,7 +552,7 @@ Returns:
template reversed(Dimensions...)
if (Dimensions.length)
{
auto reversed(size_t N, Range)(Slice!(N, Range) slice)
@fmb auto reversed(size_t N, Range)(Slice!(N, Range) slice)
{
foreach (i, dimension; Dimensions)
{
Expand Down Expand Up @@ -664,7 +666,7 @@ Returns:
template strided(Dimensions...)
if (Dimensions.length)
{
auto strided(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) factors)
@fmb auto strided(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) factors)
body
{
foreach (i, dimension; Dimensions)
Expand Down Expand Up @@ -890,7 +892,7 @@ Returns:
template dropOne(Dimensions...)
if (Dimensions.length)
{
Slice!(N, Range) dropOne(size_t N, Range)(Slice!(N, Range) slice)
@fmb Slice!(N, Range) dropOne(size_t N, Range)(Slice!(N, Range) slice)
{
foreach (i, dimension; Dimensions)
{
Expand Down Expand Up @@ -931,7 +933,7 @@ body
template dropBackOne(Dimensions...)
if (Dimensions.length)
{
Slice!(N, Range) dropBackOne(size_t N, Range)(Slice!(N, Range) slice)
@fmb Slice!(N, Range) dropBackOne(size_t N, Range)(Slice!(N, Range) slice)
{
foreach (i, dimension; Dimensions)
{
Expand Down Expand Up @@ -1027,7 +1029,7 @@ Returns:
template dropExactly(Dimensions...)
if (Dimensions.length)
{
Slice!(N, Range) dropExactly(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
@fmb Slice!(N, Range) dropExactly(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
body
{
foreach (i, dimension; Dimensions)
Expand Down Expand Up @@ -1055,7 +1057,7 @@ body
template dropBackExactly(Dimensions...)
if (Dimensions.length)
{
Slice!(N, Range) dropBackExactly(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
@fmb Slice!(N, Range) dropBackExactly(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
body
{
foreach (i, dimension; Dimensions)
Expand Down Expand Up @@ -1116,7 +1118,7 @@ Returns:
template drop(Dimensions...)
if (Dimensions.length)
{
Slice!(N, Range) drop(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
@fmb Slice!(N, Range) drop(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
body
{
foreach (i, dimension; Dimensions)
Expand Down Expand Up @@ -1144,7 +1146,7 @@ body
template dropBack(Dimensions...)
if (Dimensions.length)
{
Slice!(N, Range) dropBack(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
@fmb Slice!(N, Range) dropBack(size_t N, Range)(Slice!(N, Range) slice, Repeat!(Dimensions.length, size_t) ns)
body
{
foreach (i, dimension; Dimensions)
Expand Down
96 changes: 39 additions & 57 deletions std/experimental/ndslice/package.d
Original file line number Diff line number Diff line change
Expand Up @@ -63,23 +63,22 @@ matrix.diagonal[] = 1;
auto row = matrix[2];
row[3] = 6;
assert(matrix[2, 3] == 6); // D & C index order
assert(matrix(3, 2) == 6); // Math & Fortran index order
------
Note:
In many examples $(LINK2 std_experimental_ndslice_selection.html#iotaSlice, iotaSlice) is used
In many examples $(SUBREF selection, iotaSlice) is used
instead of a regular array, which makes it
possible to carry out tests without memory allocation.
$(SCRIPT inhibitQuickIndex = 1;)
$(DIVC quickindex,
$(BOOKTABLE ,
$(TR $(TH Category) $(TH Submodule) $(TH Declarations)
)
$(TR $(TDNW Basic Level
$(TR $(TH Submodule) $(TH Declarations))
$(TR $(TDNW $(SUBMODULE slice)
$(BR) $(SMALL $(SUBREF slice, Slice), its properties, operator overloading))
$(TDNW $(SUBMODULE slice))
$(TD
$(SUBREF slice, sliced)
$(SUBREF slice, Slice)
Expand All @@ -95,9 +94,8 @@ $(TR $(TDNW Basic Level
$(SUBREF slice, SliceException)
)
)
$(TR $(TDNW Medium Level
$(BR) $(SMALL Various iteration operators))
$(TDNW $(SUBMODULE iteration))
$(TR $(TDNW $(SUBMODULE iteration)
$(BR) $(SMALL Basic iteration operators))
$(TD
$(SUBREF iteration, transposed)
$(SUBREF iteration, strided)
Expand All @@ -109,17 +107,17 @@ $(TR $(TDNW Medium Level
$(SUBREF iteration, dropToHypercube) and other `drop` primitives
)
)
$(TR $(TDNW Advanced Level $(BR)
$(SMALL Abstract operators for loop free programming
$(BR) Take `movingWindowByChannel` as an example))
$(TDNW $(SUBMODULE selection))
$(TR $(TDNW $(SUBMODULE selection)
$(BR) $(SMALL Subspace manipulations $(BR) Operators for loop free programming))
$(TD
$(SUBREF selection, blocks)
$(SUBREF selection, windows)
$(SUBREF selection, diagonal)
$(SUBREF selection, reshape)
$(SUBREF selection, byElement)
$(SUBREF selection, byElementInStandardSimplex)
$(SUBREF selection, mapSlice)
$(SUBREF selection, indexSlice)
$(SUBREF selection, iotaSlice)
$(SUBREF selection, repeatSlice)
Expand Down Expand Up @@ -169,12 +167,9 @@ Returns:
Slice!(3, C*) movingWindowByChannel(alias filter, C)
(Slice!(3, C*) image, size_t nr, size_t nc)
{
import std.algorithm.iteration : map;
import std.array : array;
// 0. 3D
// The last dimension represents the color channel.
auto wnds = image
return image
// 1. 2D composed of 1D
// Packs the last dimension.
.pack!1
Expand All @@ -189,21 +184,11 @@ Slice!(3, C*) movingWindowByChannel(alias filter, C)
.transposed!(0, 1, 4)
// 5. 3D Composed of 2D
// Packs the last two dimensions.
.pack!2;
return wnds
// 6. Range composed of 2D
// Gathers all windows in the range.
.byElement
// 7. Range composed of pixels
.pack!2
// 2D to pixel lazy conversion.
.map!filter
// 8. `C[]`
// The only memory allocation in this function.
.array
// 9. 3D
// Returns slice with corresponding shape.
.sliced(wnds.shape);
.mapSlice!filter
// Creates the new image. The only memory allocation in this function.
.slice;
}
-------
Expand All @@ -218,15 +203,16 @@ Params:
Returns:
median value over the range `r`
+/
T median(Range, T)(Range r, T[] buf)
T median(Range, T)(Slice!(2, Range) sl, T[] buf)
{
import std.algorithm.sorting : topN;
// copy sl to the buffer
size_t n;
foreach (e; r)
buf[n++] = e;
auto m = n >> 1;
buf[0 .. n].topN(m);
return buf[m];
foreach (r; sl)
foreach (e; r)
buf[n++] = e;
buf[0 .. n].topN(n / 2);
return buf[n / 2];
}
-------
Expand Down Expand Up @@ -264,7 +250,7 @@ void main(string[] args)
auto ret = image.pixels
.sliced(cast(size_t)image.h, cast(size_t)image.w, cast(size_t)image.c)
.movingWindowByChannel
!(window => median(window.byElement, buf))
!(window => median(window, buf))
(nr, nc);
write_image(
Expand Down Expand Up @@ -311,10 +297,10 @@ Acknowledgements: John Loughran Colvin
Source: $(PHOBOSSRC std/_experimental/_ndslice/_package.d)
Macros:
SUBMODULE = $(MREF std, experimental, ndslice, $1)
SUBMODULE = $(MREF_ALTTEXT $1, std,experimental, ndslice, $1)
SUBREF = $(REF_ALTTEXT $(TT $2), $2, std,experimental, ndslice, $1)$(NBSP)
T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
TDNW2 = <td class="donthyphenate nobr" rowspan="2">$0</td>
*/
module std.experimental.ndslice;

Expand All @@ -340,37 +326,33 @@ unittest
static Slice!(3, ubyte*) movingWindowByChannel
(Slice!(3, ubyte*) image, size_t nr, size_t nc, ubyte delegate(Slice!(2, ubyte*)) filter)
{
import std.algorithm.iteration : map;
import std.array : array;
auto wnds = image
return image
.pack!1
.windows(nr, nc)
.unpack
.transposed!(0, 1, 4)
.pack!2;
return wnds
.byElement
.map!filter
.array
.sliced(wnds.shape);
.pack!2
.mapSlice!filter
.slice;
}

static T median(Range, T)(Range r, T[] buf)
static T median(Range, T)(Slice!(2, Range) sl, T[] buf)
{
import std.algorithm.sorting : topN;
// copy sl to the buffer
size_t n;
foreach (e; r)
buf[n++] = e;
auto m = n >> 1;
buf[0 .. n].topN(m);
return buf[m];
foreach (r; sl)
foreach (e; r)
buf[n++] = e;
buf[0 .. n].topN(n / 2);
return buf[n / 2];
}

import std.conv : to;
import std.getopt : getopt, defaultGetoptPrinter;
import std.path : stripExtension;

auto args = ["std"];
auto args = ["bin", "image"];
uint nr, nc, def = 3;
auto helpInformation = args.getopt(
"nr", "number of rows in window, default value is " ~ def.to!string, &nr,
Expand All @@ -391,7 +373,7 @@ unittest
{
auto ret =
movingWindowByChannel
(new ubyte[300].sliced(10, 10, 3), nr, nc, window => median(window.byElement, buf));
(new ubyte[300].sliced(10, 10, 3), nr, nc, window => median(window, buf));
}
}

Expand Down
205 changes: 202 additions & 3 deletions std/experimental/ndslice/selection.d
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ import std.meta; //: allSatisfy;
import std.experimental.ndslice.internal;
import std.experimental.ndslice.slice; //: Slice;

@fmb:

/++
Creates a packed slice, i.e. slice of slices.
The function does not carry out any calculations, it simply returns the same
Expand All @@ -73,7 +75,7 @@ Returns:
+/
template pack(K...)
{
auto pack(size_t N, Range)(auto ref Slice!(N, Range) slice)
@fmb auto pack(size_t N, Range)(auto ref Slice!(N, Range) slice)
{
template Template(size_t NInner, Range, R...)
{
Expand Down Expand Up @@ -930,6 +932,7 @@ auto byElement(size_t N, Range)(auto ref Slice!(N, Range) slice)
+/
static struct ByElement
{
@fmb:
This _slice;
size_t _length;
size_t[N] _indexes;
Expand Down Expand Up @@ -1436,6 +1439,7 @@ auto byElementInStandardSimplex(size_t N, Range)(auto ref Slice!(N, Range) slice
+/
static struct ByElementInTopSimplex
{
@fmb:
This _slice;
size_t _length;
size_t maxHypercubeLength;
Expand Down Expand Up @@ -1676,7 +1680,7 @@ template IndexSlice(size_t N)
{
private size_t[N-1] _lengths;

size_t[N] opIndex(size_t index) const
@fmb size_t[N] opIndex(size_t index) const
{
size_t[N] indexes = void;
foreach_reverse (i; Iota!(0, N - 1))
Expand Down Expand Up @@ -1786,7 +1790,7 @@ struct IotaMap()
{
enum bool empty = false;

static size_t opIndex()(size_t index) @safe pure nothrow @nogc @property
@fmb static size_t opIndex()(size_t index) @safe pure nothrow @nogc @property
{
pragma(inline, true);
return index;
Expand Down Expand Up @@ -1912,6 +1916,8 @@ template RepeatSlice(size_t N, T)
private alias UT = T;
private UT _value;

@fmb:

ref T opIndex(sizediff_t)
{
return _value;
Expand Down Expand Up @@ -1943,3 +1949,196 @@ template RepeatSlice(size_t N, T)
val += 2;
assert((val + 3)._value == 3);
}

/++
Implements the homonym function (also known as `transform`) present
in many languages of functional flavor. The call `mapSlice!(fun)(tensor)`
returns a tensor of which elements are obtained by applying `fun`
for all elements in `tensor`. The original tensors are
not changed. Evaluation is done lazily.
Note:
$(SUBREF iteration, transposed) and
$(SUBREF selection, pack) can be used to specify dimensions.
Params:
fun = One or more functions.
tensor = An input tensor.
Returns:
a tensor with each fun applied to all the elements. If there is more than one
fun, the element type will be `Tuple` containing one element for each fun.
See_Also:
$(REF map, std,algorithm,iteration)
$(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function))
+/
template mapSlice(fun...)
if (fun.length)
{
///
@fmb auto mapSlice(size_t N, Range)
(auto ref Slice!(N, Range) tensor)
{
// this static if-else block
// may be unified with std.algorithms.iteration.map
// after ndslice be removed from the Mir library.
static if (fun.length > 1)
{
import std.functional : adjoin, unaryFun;

alias _funs = staticMap!(unaryFun, fun);
alias _fun = adjoin!_funs;

// Once DMD issue #5710 is fixed, this validation loop can be moved into a template.
foreach (f; _funs)
{
static assert(!is(typeof(f(RE.init)) == void),
"Mapping function(s) must not return void: " ~ _funs.stringof);
}
}
else
{
import std.functional : unaryFun;

alias _fun = unaryFun!fun;
alias _funs = AliasSeq!(_fun);

// Do the validation separately for single parameters due to DMD issue #15777.
static assert(!is(typeof(_fun(RE.init)) == void),
"Mapping function(s) must not return void: " ~ _funs.stringof);
}

// Specialization for packed tensors (tensors composed of tensors).
static if (is(Range : Slice!(NI, RangeI), size_t NI, RangeI))
{
alias Ptr = Pack!(NI - 1, RangeI);
alias M = Map!(Ptr, _fun);
alias R = Slice!(N, M);
return R(tensor._lengths[0 .. N], tensor._strides[0 .. N],
M(Ptr(tensor._lengths[N .. $], tensor._strides[N .. $], tensor._ptr)));
}
else
{
alias M = Map!(SlicePtr!Range, _fun);
alias R = Slice!(N, M);
with(tensor) return R(_lengths, _strides, M(_ptr));
}
}
}

///
pure nothrow unittest
{
import std.experimental.ndslice.selection : iotaSlice;

auto s = iotaSlice(2, 3).mapSlice!(a => a * 3);
assert(s == [[ 0, 3, 6],
[ 9, 12, 15]]);
}

pure nothrow unittest
{
import std.experimental.ndslice.selection : iotaSlice;

assert(iotaSlice(2, 3).slice.mapSlice!"a * 2" == [[0, 2, 4], [6, 8, 10]]);
}

/// Packed tensors.
pure nothrow unittest
{
import std.experimental.ndslice.selection : iotaSlice, windows;

// iotaSlice windows mapSlice sums ( ndFold!"a + b" )
// --------------
// ------- | --- --- | ------
// | 0 1 2 | => || 0 1 || 1 2 || => | 8 12 |
// | 3 4 5 | || 3 4 || 4 5 || ------
// ------- | --- --- |
// --------------
auto s = iotaSlice(2, 3)
.windows(2, 2)
.mapSlice!((a) {
size_t s;
foreach (r; a)
foreach (e; r)
s += e;
return s;
});

assert(s == [[8, 12]]);
}

pure nothrow unittest
{
import std.experimental.ndslice.selection : iotaSlice, windows;

auto s = iotaSlice(2, 3)
.slice
.windows(2, 2)
.mapSlice!((a) {
size_t s;
foreach (r; a)
foreach (e; r)
s += e;
return s;
});

assert(s == [[8, 12]]);
}

/// Zipped tensors
pure nothrow unittest
{
import std.experimental.ndslice.slice : assumeSameStructure;
import std.experimental.ndslice.selection : iotaSlice;

// 0 1 2
// 3 4 5
auto sl1 = iotaSlice(2, 3);
// 1 2 3
// 4 5 6
auto sl2 = iotaSlice([2, 3], 1);

// tensors must have the same strides
assert(sl1.structure == sl2.structure);

auto zip = assumeSameStructure!("a", "b")(sl1, sl2);

auto lazySum = zip.mapSlice!(z => z.a + z.b);

assert(lazySum == [[ 1, 3, 5],
[ 7, 9, 11]]);
}

/++
Multiple functions can be passed to `mapSlice`.
In that case, the element type of `mapSlice` is a tuple containing
one element for each function.
+/
pure nothrow unittest
{
import std.experimental.ndslice.selection : iotaSlice;

auto s = iotaSlice(2, 3).mapSlice!("a + a", "a * a");

auto sums = [[0, 2, 4], [6, 8, 10]];
auto products = [[0, 1, 4], [9, 16, 25]];

foreach (i; 0..s.length!0)
foreach (j; 0..s.length!1)
{
auto values = s[i, j];
assert(values[0] == sums[i][j]);
assert(values[1] == products[i][j]);
}
}

/++
You may alias `mapSlice` with some function(s) to a symbol and use it separately:
+/
pure nothrow unittest
{
import std.conv : to;
import std.experimental.ndslice.selection : iotaSlice;

alias stringize = mapSlice!(to!string);
assert(stringize(iotaSlice(2, 3)) == [["0", "1", "2"], ["3", "4", "5"]]);
}
223 changes: 16 additions & 207 deletions std/experimental/ndslice/slice.d
Original file line number Diff line number Diff line change
Expand Up @@ -560,17 +560,17 @@ auto slice(T,
size_t N)(auto ref in size_t[N] lengths, auto ref T init)
{
immutable len = lengthsProduct(lengths);
static if (!hasElaborateAssign!(T[]))
static if (ra && !hasElaborateAssign!T)
{
import std.array : uninitializedArray;
auto arr = uninitializedArray!(T[])(len);
auto arr = uninitializedArray!(Unqual!T[])(len);
}
else
{
auto arr = new T[len];
auto arr = new Unqual!T[len];
}
arr[] = init;
auto ret = arr.sliced!ra(lengths);
auto ret = .sliced!ra(cast(T[])arr, lengths);
return ret;
}

Expand Down Expand Up @@ -659,6 +659,8 @@ Params:
slice = slice to copy shape and data from
Returns:
a structure with fields `array` and `slice`
Note:
`makeSlice` always returns slice with mutable elements
+/
SliceAllocationResult!(Lengths.length, T, ra)
makeSlice(T,
Expand Down Expand Up @@ -1200,6 +1202,8 @@ struct Slice(size_t _N, _Range)
&& (isPointer!_Range || is(typeof(_Range.init[size_t.init]))))
|| is(_Range == Slice!(N1, Range1), size_t N1, Range1)))
{
@fmb:

package:

enum doUnittest = is(_Range == int*) && _N == 1;
Expand Down Expand Up @@ -1255,10 +1259,7 @@ struct Slice(size_t _N, _Range)

size_t[PureN] _lengths;
sizediff_t[PureN] _strides;
static if (hasPtrBehavior!PureRange)
PureRange _ptr;
else
PtrShell!PureRange _ptr;
SlicePtr!PureRange _ptr;

sizediff_t backIndex(size_t dimension = 0)() @property const
if (dimension < N)
Expand Down Expand Up @@ -1541,10 +1542,7 @@ struct Slice(size_t _N, _Range)
assert(!empty!dimension);
static if (PureN == 1)
{
static if (__traits(compiles, _ptr.front ))
return _ptr.front;
else
return _ptr[0];
return _ptr[0];
}
else
{
Expand Down Expand Up @@ -1574,10 +1572,7 @@ struct Slice(size_t _N, _Range)
if (dimension == 0)
{
assert(!empty!dimension);
static if (__traits(compiles, _ptr.front = value))
return _ptr.front = value;
else
return _ptr[0] = value;
return _ptr[0] = value;
}
}

Expand Down Expand Up @@ -3231,113 +3226,6 @@ private void opIndexAssignImpl
_indexAssign!(false, op)(ls, rs);
}

private struct PtrShell(Range)
{
sizediff_t _shift;
Range _range;

enum hasAccessByRef = isPointer!Range ||
__traits(compiles, &_range[0]);

void opOpAssign(string op)(sizediff_t shift)
if (op == `+` || op == `-`)
{
mixin (`_shift ` ~ op ~ `= shift;`);
}

auto opBinary(string op)(sizediff_t shift)
if (op == `+` || op == `-`)
{
mixin (`return typeof(this)(_shift ` ~ op ~ ` shift, _range);`);
}

auto ref opIndex(sizediff_t index)
in
{
assert(_shift + index >= 0);
static if (hasLength!Range)
assert(_shift + index <= _range.length);
}
body
{
return _range[_shift + index];
}

static if (!hasAccessByRef)
{
auto ref opIndexAssign(T)(T value, sizediff_t index)
in
{
assert(_shift + index >= 0);
static if (hasLength!Range)
assert(_shift + index <= _range.length);
}
body
{
return _range[_shift + index] = value;
}

auto ref opIndexOpAssign(string op, T)(T value, sizediff_t index)
in
{
assert(_shift + index >= 0);
static if (hasLength!Range)
assert(_shift + index <= _range.length);
}
body
{
mixin (`return _range[_shift + index] ` ~ op ~ `= value;`);
}

auto ref opIndexUnary(string op)(sizediff_t index)
in
{
assert(_shift + index >= 0);
static if (hasLength!Range)
assert(_shift + index <= _range.length);
}
body
{
mixin (`return ` ~ op ~ `_range[_shift + index];`);
}
}

auto save() @property
{
return this;
}
}

private auto ptrShell(Range)(Range range, sizediff_t shift = 0)
{
return PtrShell!Range(shift, range);
}

@safe pure nothrow unittest
{
import std.internal.test.dummyrange;
foreach (RB; AliasSeq!(ReturnBy.Reference, ReturnBy.Value))
{
DummyRange!(RB, Length.Yes, RangeType.Random) range;
range.reinit;
assert(range.length >= 10);
auto ptr = range.ptrShell;
assert(ptr[0] == range[0]);
auto save0 = range[0];
ptr[0] += 10;
++ptr[0];
assert(ptr[0] == save0 + 11);
(ptr + 5)[2] = 333;
assert(range[7] == 333);

auto ptrCopy = ptr.save;
ptrCopy._range.popFront;
ptr[1] = 2;
assert(ptr[0] == save0 + 11);
assert(ptrCopy[0] == 2);
}
}

pure nothrow unittest
{
import std.internal.test.dummyrange;
Expand Down Expand Up @@ -3395,102 +3283,23 @@ unittest
assert(ptrCopy[0] == 2);
}

private enum isSlicePointer(T) = isPointer!T || is(T : PtrShell!R, R);

package template hasPtrBehavior(T)
{
static if (isPointer!T)
enum hasPtrBehavior = true;
else
static if (!isAggregateType!T)
enum hasPtrBehavior = false;
else
enum hasPtrBehavior = hasUDA!(T, LikePtr);
}

private template PtrTuple(Names...)
{
@LikePtr struct PtrTuple(Ptrs...)
if (allSatisfy!(isSlicePointer, Ptrs) && Ptrs.length == Names.length)
{
Ptrs ptrs;

void opOpAssign(string op)(sizediff_t shift)
if (op == `+` || op == `-`)
{
foreach (ref ptr; ptrs)
mixin (`ptr ` ~ op ~ `= shift;`);
}

auto opBinary(string op)(sizediff_t shift)
if (op == `+` || op == `-`)
{
auto ret = this.ptrs;
ret.opOpAssign!op(shift);
return ret;
}

public struct Index
{
Ptrs _ptrs__;
mixin (PtrTupleFrontMembers!Names);
}

auto opIndex(sizediff_t index)
{
auto p = ptrs;
foreach (ref ptr; p)
ptr += index;
return Index(p);
}

auto front() @property
{
return Index(ptrs);
}
}
}

pure nothrow unittest
{
auto a = new int[20], b = new int[20];
alias T = PtrTuple!("a", "b");
alias S = T!(int*, int*);
static assert (hasUDA!(S, LikePtr));
auto t = S(a.ptr, b.ptr);
t[4].a++;
auto r = t[4];
r.b = r.a * 2;
assert(b[4] == 2);
t.front.a++;
r = t.front;
t[0].a++;
r = t[0];
r.b = r.a * 2;
assert(b[0] == 2);
}

private template PtrTupleFrontMembers(Names...)
if (Names.length <= 32)
{
static if (Names.length)
{
alias Top = Names[0..$-1];
enum int m = Top.length;
enum PtrTupleFrontMembers = PtrTupleFrontMembers!Top
~ "
@property auto ref " ~ Names[$-1] ~ "() {
static if (__traits(compiles, _ptrs__[" ~ m.stringof ~ "].front()))
return _ptrs__[" ~ m.stringof ~ "].front;
else
return _ptrs__[" ~ m.stringof ~ "][0];
}
";
}
else
{
enum PtrTupleFrontMembers = "";
}
}


private template PrepareRangeType(Range)
{
static if (isPointer!Range)
Expand All @@ -3505,7 +3314,7 @@ private enum bool isType(T) = true;

private enum isStringValue(alias T) = is(typeof(T) : string);

private void _indexAssignKernel(string op, TL, TR)(size_t c, TL* l, TR* r)
private void _indexAssignKernel(string op, TL, TR)(size_t c, TL l, TR r)
{
do
{
Expand All @@ -3516,7 +3325,7 @@ private void _indexAssignKernel(string op, TL, TR)(size_t c, TL* l, TR* r)
while (--c);
}

private void _indexAssignValKernel(string op, TL, TR)(size_t c, TL* l, TR r)
private void _indexAssignValKernel(string op, TL, TR)(size_t c, TL l, TR r)
{
do
{
Expand Down