Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Added RefRange to std.range. #633

Merged
merged 1 commit into from

2 participants

@jmdavis
Collaborator

Wrapper which effectively allows you to pass a range by reference. Both the
original range and the RefRange will always have the exact same elements.
Any operation done on one will affect the other. So, for instance, if it's
passed to a function which would implicitly copy the original range if it
were passed to it, the original range is not copied but is consumed as
if it were a reference type.

std/range.d
((46 lines not shown))
+ if(isForwardRange!R)
+{
+public:
+
+ /++ +/
+ this(R* range)
+ {
+ _range = range;
+ }
+
+
+ /++ +/
+ auto opAssign(RefRange rhs)
+ {
+ if(_range)
+ *_range = *rhs._range;
@kyllingstad Collaborator

This behaviour would be very surprising to me. I would expect an assignment to always change the reference, and never the referee.

@jmdavis Collaborator
jmdavis added a note

Because if you don't do that, then drop and popFrontN don't work. They do nothing. It becomes like if an array assignment assigned only the pointer and not the length.

@jmdavis Collaborator
jmdavis added a note

Wait, no. Bad analogy. If it assigned only the pointer, that would affect popBackN, not popFrontN. In any case, this line

    r = r[n .. r.length];

in popFrontN didn't work right if I didn't do this. I'll have to think through it again to figure out exactly why though.

@jmdavis Collaborator
jmdavis added a note

Oh. I remember now. The problem is that the assignment needs to affect the original range. If you just assign the pointer, then the original range is unaffected. So, what you then get with

r = r[n .. r.length];

is that the slice of the origanl range ( which opSlice returned wrapped in a RefRange) is assigned to the original range. So, the

r = r[n .. r.length];

acts like it was the slice of the original range which is assigned to the original range rather than having the wrapper then refer to a new range.

@kyllingstad Collaborator

I see your point, but I still don't like the fact that if I do the following:

SomeRange foo, bar;
auto a = refRange(&foo);
auto b = refRange(&bar);
RefRange!SomeRange x;

then the two lines

x = a;   // Only affects x
x = b;   // What, now b is assigned to a?

do two completely different things. I'm inclined to say that you should just drop support for slicing in RefRange, if this is the only reason for this behaviour. (Anyway, isn't it a bug if an algorithm does stuff like r = r[n .. r.length]? At least from the std.range documentation, there doesn't seem to be any requirement that opSlice return a range of the same type – just that it returns an input range.)

At any rate, there needs to be a null pointer check before rhs._range is dereferenced.

@jmdavis Collaborator
jmdavis added a note

But the whole point of the RefRange is to make it so that it is the original range, only now it's being passed around by reference, so it gets affected by the functions that it's passed to rather than being copied when it's passed. So, any operation occuring to the RefRange also occurs to the original. Saying

x = a;
x = b;

is tantamount to saying

a = b;

because x is a, just wrapped.

@jmdavis Collaborator
jmdavis added a note

Okay. I fixed the null dereference issue and added documentation to opAssign to clarify its behavior.

As for assigning a slice to the original, hasSlicing doesn't check for that, but it seems quite common that code assumes that a slice is assignable to the original. The same goes with save, though it actually tests the assignment. So, at present, it wouldn't work at all to use std.range or std.algorithm with a type that returned something different for a slice. And given the fact that it's quite common to assign a slice to the original, and that behavior is very much needed in general, I don't see how we could make it work otherwise. hasSlicing should probably be updated to reflect that requirement however.

@kyllingstad Collaborator

All right, I have no more complaints. :-)

The hasSlicing debate is worth having, I think. It is not obvious (at least not to me) that a slice needs to have the same type as the slicee. At one point it was even suggested (by Walter, no less!) to make built-in arrays and slices different types... But that is for a different forum, and not this pull request.

@jmdavis Collaborator
jmdavis added a note

I didn't realize that hasSlicing and save had to have the same type until I was working on this. It made me rethink how I was going to handle slices. And actually, they don't have to be the same type. They just have to be implicitly assignable to the original type. And in the case of arrays, a slice doesn't have the same type (though not due to that proposal). Rather, the slice is a tail-const version of the array. However, the tail-const version is assignable to the fully const version, so it works.

The problem is that you can't do that with user-defined types (at least not as far as I've been able to figure out). You can make opSlice and save return the exact same type, but then you can't operate on const user-defined ranges at all. Or you could make them return tail-const versions, but then the assignment fails, because the tail-const version is a completely different type (e.g. Range!(const int) instead of const Range!int). And I have been unable to figure out how to make Range!(const int) be implicitly assignable to const Range!int. I believe that the problem is that in implementing alias this or opAssign, you end up with a recursive template definition (certainly, the compiler gets OOM-ed for taking up too much memory when it tries to compile it). So, const and ranges continue not to get along very well. But that whole discussion really should be discussed in the newsgroup. I've been meaning to, after trying to solve the issues in relation to this, but I haven't gotten around to it yet. I'l have to remember to post on that soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@kyllingstad kyllingstad commented on the diff
std/range.d
((57 lines not shown))
+ /++ +/
+ auto opAssign(RefRange rhs)
+ {
+ if(_range)
+ *_range = *rhs._range;
+ else
+ _range = rhs._range;
+
+ return this;
+ }
+
+
+ /++
+ A pointer to the wrapped range.
+ +/
+ @property inout(R*) ptr() @safe inout pure
@kyllingstad Collaborator

nothrow(?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@kyllingstad kyllingstad commented on the diff
std/range.d
((36 lines not shown))
+string str = "hello world";
+auto wrappedStr = refRange(&str);
+assert(str.front == 'h');
+str.popFrontN(5);
+assert(str == " world");
+assert(wrappedStr.front == ' ');
+assert(*wrappedStr.ptr == " world");
+--------------------
+ +/
+struct RefRange(R)
+ if(isForwardRange!R)
+{
+public:
+
+ /++ +/
+ this(R* range)
@kyllingstad Collaborator

@safe pure nothrow(?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
std/range.d
((132 lines not shown))
+
+
+ version(D_Ddoc)
+ {
+ /++ +/
+ @property auto save() {assert(0);}
+ /++ Ditto +/
+ @property auto save() const {assert(0);}
+ /++ Ditto +/
+ auto opSlice() {assert(0);}
+ /++ Ditto +/
+ auto opSlice() const {assert(0);}
+ }
+ else
+ {
+ static void _testSave(R)(R* range)
@kyllingstad Collaborator

Nifty trick. :) _testSave should probably be private.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@kyllingstad
Collaborator

Why aren't bidirectional and random-access range primitives included? (They can of course be added later without breaking anything, I just wonder if there is some technical reason preventing you from adding them.)

@jmdavis
Collaborator

Bidirectional range and random-access range primitives are included.

@kyllingstad
Collaborator

@jmdavis:

Bidirectional range and random-access range primitives are included.

You are absolutely right – I have no idea how I missed that, when I was even actively looking for them.

std/range.d
((336 lines not shown))
+ {
+ mixin(_genOpSlice());
+ }
+
+ @property auto opSlice(IndexType1, IndexType2)
+ (IndexType1 begin, IndexType2 end) const
+ if(is(typeof((*cast(const R*)_range)[begin .. end])))
+ {
+ mixin(_genOpSlice());
+ }
+
+ private static string _genOpSlice() @safe pure nothrow
+ {
+ return `import std.conv;` ~
+ `alias typeof((*_range)[begin .. end]) S;` ~
+ `static assert(hasSlicing!S, "S.stringof is not sliceable.");` ~
@kyllingstad Collaborator

S.stringof should be outside the string literal – the inner one, that is.

@jmdavis Collaborator
jmdavis added a note

Ouch.

@jmdavis Collaborator
jmdavis added a note

Fixed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@jmdavis jmdavis Added RefRange to std.range.
It's a wrapper which effectively allows you to pass a range by
reference.
a885cf8
@jmdavis jmdavis merged commit 51d4428 into from
@jmdavis
Collaborator

Merged.

@ghost Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@ghost Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@ghost Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
@ghost Unknown referenced this pull request from a commit
Commit has since been removed from the repository and is no longer available.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jun 21, 2012
  1. @jmdavis

    Added RefRange to std.range.

    jmdavis authored
    It's a wrapper which effectively allows you to pass a range by
    reference.
This page is out of date. Refresh to see the latest.
Showing with 782 additions and 3 deletions.
  1. +782 −3 std/range.d
View
785 std/range.d
@@ -310,9 +310,9 @@ Copyright: Copyright by authors 2008-.
License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
-Authors: $(WEB erdani.com, Andrei Alexandrescu), David Simcha. Credit
-for some of the ideas in building this module goes to $(WEB
-fantascienza.net/leonardo/so/, Leonardo Maffi).
+Authors: $(WEB erdani.com, Andrei Alexandrescu), David Simcha,
+and Jonathan M Davis. Credit for some of the ideas in building this module goes
+to $(WEB fantascienza.net/leonardo/so/, Leonardo Maffi).
*/
module std.range;
@@ -7036,3 +7036,782 @@ unittest
}
assert(ok);
}
+
+
+/++
+ Wrapper which effectively allows you to pass a range by reference. Both the
+ original range and the RefRange will always have the exact same elements.
+ Any operation done on one will affect the other. So, for instance, if it's
+ passed to a function which would implicitly copy the original range if it
+ were passed to it, the original range is $(I not) copied but is consumed as
+ if it were a reference type.
+
+ Note that $(D save) works as normal and operates on a new range, so if
+ $(D save) is ever called on the RefRange, then no operations on the saved
+ range will affect the original.
+
+ Examples:
+--------------------
+import std.algorithm;
+ubyte[] buffer = [1, 9, 45, 12, 22];
+auto found1 = find(buffer, 45);
+assert(found1 == [45, 12, 22]);
+assert(buffer == [1, 9, 45, 12, 22]);
+
+auto wrapped1 = refRange(&buffer);
+auto found2 = find(wrapped1, 45);
+assert(*found2.ptr == [45, 12, 22]);
+assert(buffer == [45, 12, 22]);
+
+auto found3 = find(wrapped2.save, 22);
+assert(*found3.ptr == [22]);
+assert(buffer == [45, 12, 22]);
+
+string str = "hello world";
+auto wrappedStr = refRange(&str);
+assert(str.front == 'h');
+str.popFrontN(5);
+assert(str == " world");
+assert(wrappedStr.front == ' ');
+assert(*wrappedStr.ptr == " world");
+--------------------
+ +/
+struct RefRange(R)
+ if(isForwardRange!R)
+{
+public:
+
+ /++ +/
+ this(R* range) @safe pure nothrow
@kyllingstad Collaborator

@safe pure nothrow(?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ {
+ _range = range;
+ }
+
+
+ /++
+ This does not assign the pointer of $(D rhs) to this $(D RefRange).
+ Rather it assigns the range pointed to by $(D rhs) to the range pointed
+ to by this $(D RefRange). This is because $(I any) operation on a
+ $(D RefRange) is the same is if it occurred to the original range. The
+ one exception is when a $(D RefRange) is assigned $(D null) either
+ directly or because $(D rhs) is $(D null). In that case, $(D RefRange)
+ no longer refers to the original range but is $(D null).
+
+ Examples:
+--------------------
+ubyte[] buffer1 = [1, 2, 3, 4, 5];
+ubyte[] buffer2 = [6, 7, 8, 9, 10];
+auto wrapped1 = refRange(&buffer1);
+auto wrapped2 = refRange(&buffer2);
+assert(wrapped1.ptr is &buffer1);
+assert(wrapped2.ptr is &buffer2);
+assert(wrapped1.ptr !is wrapped2.ptr);
+assert(buffer1 != buffer2);
+
+wrapped1 = wrapped2;
+
+//Everything points to the same stuff as before.
+assert(wrapped1.ptr is &buffer1);
+assert(wrapped2.ptr is &buffer2);
+assert(wrapped1.ptr !is wrapped2.ptr);
+
+//But buffer1 has changed due to the assignment.
+assert(buffer1 == [6, 7, 8, 9, 10]);
+assert(buffer2 == [6, 7, 8, 9, 10]);
+
+buffer2 = [11, 12, 13, 14, 15];
+
+//Everything points to the same stuff as before.
+assert(wrapped1.ptr is &buffer1);
+assert(wrapped2.ptr is &buffer2);
+assert(wrapped1.ptr !is wrapped2.ptr);
+
+//But buffer2 has changed due to the assignment.
+assert(buffer1 == [6, 7, 8, 9, 10]);
+assert(buffer2 == [11, 12, 13, 14, 15]);
+
+wrapped2 = null;
+
+//The pointer changed for wrapped2 but not wrapped1.
+assert(wrapped1.ptr is &buffer1);
+assert(wrapped2.ptr is null);
+assert(wrapped1.ptr !is wrapped2.ptr);
+
+//buffer2 is not affected by the assignment.
+assert(buffer1 == [6, 7, 8, 9, 10]);
+assert(buffer2 == [11, 12, 13, 14, 15]);
+--------------------
+ +/
+ auto opAssign(RefRange rhs)
+ {
+ if(_range && rhs._range)
+ *_range = *rhs._range;
+ else
+ _range = rhs._range;
+
+ return this;
+ }
+
+ /++ +/
+ auto opAssign(typeof(null) rhs)
+ {
+ _range = null;
+ }
+
+
+ /++
+ A pointer to the wrapped range.
+ +/
+ @property inout(R*) ptr() @safe inout pure nothrow
@kyllingstad Collaborator

nothrow(?)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ {
+ return _range;
+ }
+
+
+ version(D_Ddoc)
+ {
+ /++ +/
+ @property auto front() {assert(0);}
+ /++ Ditto +/
+ @property auto front() const {assert(0);}
+ /++ Ditto +/
+ @property auto front(ElementType!R value) {assert(0);}
+ }
+ else
+ {
+ @property auto front()
+ {
+ return (*_range).front;
+ }
+
+ static if(is(typeof((*(cast(const R*)_range)).front))) @property ElementType!R front() const
+ {
+ return (*_range).front;
+ }
+
+ static if(is(typeof((*_range).front = (*_range).front))) @property auto front(ElementType!R value)
+ {
+ return (*_range).front = value;
+ }
+ }
+
+
+ version(D_Ddoc)
+ {
+ @property bool empty(); ///
+ @property bool empty() const; ///Ditto
+ }
+ else static if(isInfinite!R)
+ enum empty = false;
+ else
+ {
+ @property bool empty()
+ {
+ return (*_range).empty;
+ }
+
+ static if(is(typeof((*cast(const R*)_range).empty))) @property bool empty() const
+ {
+ return (*_range).empty;
+ }
+ }
+
+
+ /++ +/
+ @property void popFront()
+ {
+ return (*_range).popFront();
+ }
+
+
+ version(D_Ddoc)
+ {
+ /++ +/
+ @property auto save() {assert(0);}
+ /++ Ditto +/
+ @property auto save() const {assert(0);}
+ /++ Ditto +/
+ auto opSlice() {assert(0);}
+ /++ Ditto +/
+ auto opSlice() const {assert(0);}
+ }
+ else
+ {
+ private static void _testSave(R)(R* range)
+ {
+ (*range).save;
+ }
+
+ static if(isSafe!(_testSave!R))
+ {
+ @property auto save() @trusted
+ {
+ mixin(_genSave());
+ }
+
+ static if(is(typeof((*cast(const R*)_range).save))) @property auto save() @trusted const
+ {
+ mixin(_genSave());
+ }
+ }
+ else
+ {
+ @property auto save()
+ {
+ mixin(_genSave());
+ }
+
+ static if(is(typeof((*cast(const R*)_range).save))) @property auto save() const
+ {
+ mixin(_genSave());
+ }
+ }
+
+ auto opSlice()()
+ {
+ return save;
+ }
+
+ auto opSlice()() const
+ {
+ return save;
+ }
+
+ private static string _genSave() @safe pure nothrow
+ {
+ return `import std.conv;` ~
+ `alias typeof((*_range).save) S;` ~
+ `static assert(isForwardRange!S, S.stringof ~ " is not a forward range.");` ~
+ `auto mem = new void[S.sizeof];` ~
+ `emplace!S(mem, cast(S)(*_range).save);` ~
+ `return RefRange!S(cast(S*)mem.ptr);`;
+ }
+
+ static assert(isForwardRange!RefRange);
+ }
+
+
+ version(D_Ddoc)
+ {
+ /++
+ Only defined if $(D isBidirectionalRange!R) is $(D true).
+ +/
+ @property auto back() {assert(0);}
+ /++ Ditto +/
+ @property auto back() const {assert(0);}
+ /++ Ditto +/
+ @property auto back(ElementType!R value) {assert(0);}
+ }
+ else static if(isBidirectionalRange!R)
+ {
+ @property auto back()
+ {
+ return (*_range).back;
+ }
+
+ static if(is(typeof((*(cast(const R*)_range)).back))) @property ElementType!R back() const
+ {
+ return (*_range).back;
+ }
+
+ static if(is(typeof((*_range).back = (*_range).back))) @property auto back(ElementType!R value)
+ {
+ return (*_range).back = value;
+ }
+ }
+
+
+ /++ Ditto +/
+ static if(isBidirectionalRange!R) @property void popBack()
+ {
+ return (*_range).popBack();
+ }
+
+
+ version(D_Ddoc)
+ {
+ /++
+ Only defined if $(D isRandomAccesRange!R) is $(D true).
+ +/
+ @property auto ref opIndex(IndexType)(IndexType index) {assert(0);}
+
+ /++ Ditto +/
+ @property auto ref opIndex(IndexType)(IndexType index) const {assert(0);}
+ }
+ else static if(isRandomAccessRange!R)
+ {
+ @property auto ref opIndex(IndexType)(IndexType index)
+ if(is(typeof((*_range)[index])))
+ {
+ return (*_range)[index];
+ }
+
+ @property auto ref opIndex(IndexType)(IndexType index) const
+ if(is(typeof((*cast(const R*)_range)[index])))
+ {
+ return (*_range)[index];
+ }
+ }
+
+
+ /++
+ Only defined if $(D hasMobileElements!R) and $(D isForwardRange!R) are
+ $(D true).
+ +/
+ static if(hasMobileElements!R && isForwardRange!R) @property auto moveFront()
+ {
+ return (*_range).moveFront();
+ }
+
+
+ /++
+ Only defined if $(D hasMobileElements!R) and $(D isBidirectionalRange!R)
+ are $(D true).
+ +/
+ static if(hasMobileElements!R && isBidirectionalRange!R) @property auto moveBack()
+ {
+ return (*_range).moveBack();
+ }
+
+
+ /++
+ Only defined if $(D hasMobileElements!R) and $(D isRandomAccessRange!R)
+ are $(D true).
+ +/
+ static if(hasMobileElements!R && isRandomAccessRange!R) @property auto moveAt(IndexType)(IndexType index)
+ if(is(typeof((*_range).moveAt(index))))
+ {
+ return (*_range).moveAt(index);
+ }
+
+
+ version(D_Ddoc)
+ {
+ /++
+ Only defined if $(D hasLength!R) is $(D true).
+ +/
+ @property auto length() {assert(0);}
+
+ /++ Ditto +/
+ @property auto length() const {assert(0);}
+ }
+ else static if(hasLength!R)
+ {
+ @property auto length()
+ {
+ return (*_range).length;
+ }
+
+ static if(is(typeof((*cast(const R*)_range).length))) @property auto length() const
+ {
+ return (*_range).length;
+ }
+ }
+
+
+ version(D_Ddoc)
+ {
+ /++
+ Only defined if $(D hasSlicing!R) is $(D true).
+ +/
+ @property auto opSlice(IndexType1, IndexType2)
+ (IndexType1 begin, IndexType2 end) {assert(0);}
+
+ /++ Ditto +/
+ @property auto opSlice(IndexType1, IndexType2)
+ (IndexType1 begin, IndexType2 end) const {assert(0);}
+ }
+ else static if(hasSlicing!R)
+ {
+ @property auto opSlice(IndexType1, IndexType2)
+ (IndexType1 begin, IndexType2 end)
+ if(is(typeof((*_range)[begin .. end])))
+ {
+ mixin(_genOpSlice());
+ }
+
+ @property auto opSlice(IndexType1, IndexType2)
+ (IndexType1 begin, IndexType2 end) const
+ if(is(typeof((*cast(const R*)_range)[begin .. end])))
+ {
+ mixin(_genOpSlice());
+ }
+
+ private static string _genOpSlice() @safe pure nothrow
+ {
+ return `import std.conv;` ~
+ `alias typeof((*_range)[begin .. end]) S;` ~
+ `static assert(hasSlicing!S, S.stringof ~ " is not sliceable.");` ~
+ `auto mem = new void[S.sizeof];` ~
+ `emplace!S(mem, cast(S)(*_range)[begin .. end]);` ~
+ `return RefRange!S(cast(S*)mem.ptr);`;
+ }
+ }
+
+
+private:
+
+ R* _range;
+}
+
+//Verify Example.
+unittest
+{
+ import std.algorithm;
+ ubyte[] buffer = [1, 9, 45, 12, 22];
+ auto found1 = find(buffer, 45);
+ assert(found1 == [45, 12, 22]);
+ assert(buffer == [1, 9, 45, 12, 22]);
+
+ auto wrapped1 = refRange(&buffer);
+ auto found2 = find(wrapped1, 45);
+ assert(*found2.ptr == [45, 12, 22]);
+ assert(buffer == [45, 12, 22]);
+
+ auto found3 = find(wrapped1.save, 22);
+ assert(*found3.ptr == [22]);
+ assert(buffer == [45, 12, 22]);
+
+ string str = "hello world";
+ auto wrappedStr = refRange(&str);
+ assert(str.front == 'h');
+ str.popFrontN(5);
+ assert(str == " world");
+ assert(wrappedStr.front == ' ');
+ assert(*wrappedStr.ptr == " world");
+}
+
+//Verify opAssign Example.
+unittest
+{
+ ubyte[] buffer1 = [1, 2, 3, 4, 5];
+ ubyte[] buffer2 = [6, 7, 8, 9, 10];
+ auto wrapped1 = refRange(&buffer1);
+ auto wrapped2 = refRange(&buffer2);
+ assert(wrapped1.ptr is &buffer1);
+ assert(wrapped2.ptr is &buffer2);
+ assert(wrapped1.ptr !is wrapped2.ptr);
+ assert(buffer1 != buffer2);
+
+ wrapped1 = wrapped2;
+
+ //Everything points to the same stuff as before.
+ assert(wrapped1.ptr is &buffer1);
+ assert(wrapped2.ptr is &buffer2);
+ assert(wrapped1.ptr !is wrapped2.ptr);
+
+ //But buffer1 has changed due to the assignment.
+ assert(buffer1 == [6, 7, 8, 9, 10]);
+ assert(buffer2 == [6, 7, 8, 9, 10]);
+
+ buffer2 = [11, 12, 13, 14, 15];
+
+ //Everything points to the same stuff as before.
+ assert(wrapped1.ptr is &buffer1);
+ assert(wrapped2.ptr is &buffer2);
+ assert(wrapped1.ptr !is wrapped2.ptr);
+
+ //But buffer2 has changed due to the assignment.
+ assert(buffer1 == [6, 7, 8, 9, 10]);
+ assert(buffer2 == [11, 12, 13, 14, 15]);
+
+ wrapped2 = null;
+
+ //The pointer changed for wrapped2 but not wrapped1.
+ assert(wrapped1.ptr is &buffer1);
+ assert(wrapped2.ptr is null);
+ assert(wrapped1.ptr !is wrapped2.ptr);
+
+ //buffer2 is not affected by the assignment.
+ assert(buffer1 == [6, 7, 8, 9, 10]);
+ assert(buffer2 == [11, 12, 13, 14, 15]);
+}
+
+unittest
+{
+ import std.algorithm;
+ {
+ ubyte[] buffer = [1, 2, 3, 4, 5];
+ auto wrapper = refRange(&buffer);
+ auto p = wrapper.ptr;
+ auto f = wrapper.front;
+ wrapper.front = f;
+ auto e = wrapper.empty;
+ wrapper.popFront();
+ auto s = wrapper.save;
+ auto b = wrapper.back;
+ wrapper.back = b;
+ wrapper.popBack();
+ auto i = wrapper[0];
+ wrapper.moveFront();
+ wrapper.moveBack();
+ wrapper.moveAt(0);
+ auto l = wrapper.length;
+ auto sl = wrapper[0 .. 1];
+ }
+
+ {
+ ubyte[] buffer = [1, 2, 3, 4, 5];
+ const wrapper = refRange(&buffer);
+ const p = wrapper.ptr;
+ const f = wrapper.front;
+ const e = wrapper.empty;
+ const s = wrapper.save;
+ const b = wrapper.back;
+ const i = wrapper[0];
+ const l = wrapper.length;
+ const sl = wrapper[0 .. 1];
+ }
+
+ {
+ ubyte[] buffer = [1, 2, 3, 4, 5];
+ auto filtered = filter!"true"(buffer);
+ auto wrapper = refRange(&filtered);
+ auto p = wrapper.ptr;
+ auto f = wrapper.front;
+ wrapper.front = f;
+ auto e = wrapper.empty;
+ wrapper.popFront();
+ auto s = wrapper.save;
+ wrapper.moveFront();
+ }
+
+ {
+ ubyte[] buffer = [1, 2, 3, 4, 5];
+ auto filtered = filter!"true"(buffer);
+ const wrapper = refRange(&filtered);
+ const p = wrapper.ptr;
+
+ //Cannot currently be const. filter needs to be updated to handle const.
+ /+
+ const f = wrapper.front;
+ const e = wrapper.empty;
+ const s = wrapper.save;
+ +/
+ }
+
+ {
+ string str = "hello world";
+ auto wrapper = refRange(&str);
+ auto p = wrapper.ptr;
+ auto f = wrapper.front;
+ auto e = wrapper.empty;
+ wrapper.popFront();
+ auto s = wrapper.save;
+ auto b = wrapper.back;
+ wrapper.popBack();
+ }
+}
+
+//Test assignment.
+unittest
+{
+ ubyte[] buffer1 = [1, 2, 3, 4, 5];
+ ubyte[] buffer2 = [6, 7, 8, 9, 10];
+ RefRange!(ubyte[]) wrapper1;
+ RefRange!(ubyte[]) wrapper2 = refRange(&buffer2);
+ assert(wrapper1.ptr is null);
+ assert(wrapper2.ptr is &buffer2);
+
+ wrapper1 = refRange(&buffer1);
+ assert(wrapper1.ptr is &buffer1);
+
+ wrapper1 = wrapper2;
+ assert(wrapper1.ptr is &buffer1);
+ assert(buffer1 == buffer2);
+
+ wrapper1 = RefRange!(ubyte[]).init;
+ assert(wrapper1.ptr is null);
+ assert(wrapper2.ptr is &buffer2);
+ assert(buffer1 == buffer2);
+ assert(buffer1 == [6, 7, 8, 9, 10]);
+
+ wrapper2 = null;
+ assert(wrapper2.ptr is null);
+ assert(buffer2 == [6, 7, 8, 9, 10]);
+}
+
+unittest
+{
+ import std.algorithm;
+
+ //Test that ranges are properly consumed.
+ {
+ int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9];
+ auto wrapper = refRange(&arr);
+
+ assert(*find(wrapper, 41).ptr == [41, 3, 40, 4, 42, 9]);
+ assert(arr == [41, 3, 40, 4, 42, 9]);
+
+ assert(*drop(wrapper, 2).ptr == [40, 4, 42, 9]);
+ assert(arr == [40, 4, 42, 9]);
+
+ assert(equal(until(wrapper, 42), [40, 4]));
+ assert(arr == [42, 9]);
+
+ assert(find(wrapper, 12).empty);
+ assert(arr.empty);
+ }
+
+ {
+ string str = "Hello, world-like object.";
+ auto wrapper = refRange(&str);
+
+ assert(*find(wrapper, "l").ptr == "llo, world-like object.");
+ assert(str == "llo, world-like object.");
+
+ assert(equal(take(wrapper, 5), "llo, "));
+ assert(str == "world-like object.");
+ }
+
+ //Test that operating on saved ranges does not consume the original.
+ {
+ int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9];
+ auto wrapper = refRange(&arr);
+ auto saved = wrapper.save;
+ saved.popFrontN(3);
+ assert(*saved.ptr == [41, 3, 40, 4, 42, 9]);
+ assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]);
+ }
+
+ {
+ string str = "Hello, world-like object.";
+ auto wrapper = refRange(&str);
+ auto saved = wrapper.save;
+ saved.popFrontN(13);
+ assert(*saved.ptr == "like object.");
+ assert(str == "Hello, world-like object.");
+ }
+
+ //Test that functions which use save work properly.
+ {
+ int[] arr = [1, 42];
+ auto wrapper = refRange(&arr);
+ assert(equal(commonPrefix(wrapper, [1, 27]), [1]));
+ }
+
+ {
+ int[] arr = [4, 5, 6, 7, 1, 2, 3];
+ auto wrapper = refRange(&arr);
+ assert(bringToFront(wrapper[0 .. 4], wrapper[4 .. arr.length]) == 3);
+ assert(arr == [1, 2, 3, 4, 5, 6, 7]);
+ }
+
+ //Test bidirectional functions.
+ {
+ int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9];
+ auto wrapper = refRange(&arr);
+
+ assert(wrapper.back == 9);
+ assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]);
+
+ wrapper.popBack();
+ assert(arr == [1, 42, 2, 41, 3, 40, 4, 42]);
+ }
+
+ {
+ string str = "Hello, world-like object.";
+ auto wrapper = refRange(&str);
+
+ assert(wrapper.back == '.');
+ assert(str == "Hello, world-like object.");
+
+ wrapper.popBack();
+ assert(str == "Hello, world-like object");
+ }
+
+ //Test random access functions.
+ {
+ int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9];
+ auto wrapper = refRange(&arr);
+
+ assert(wrapper[2] == 2);
+ assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]);
+
+ assert(*wrapper[3 .. 6].ptr, [41, 3, 40]);
+ assert(arr == [1, 42, 2, 41, 3, 40, 4, 42, 9]);
+ }
+
+ //Test move functions.
+ {
+ int[] arr = [1, 42, 2, 41, 3, 40, 4, 42, 9];
+ auto wrapper = refRange(&arr);
+
+ auto t1 = wrapper.moveFront();
+ auto t2 = wrapper.moveBack();
+ wrapper.front = t2;
+ wrapper.back = t1;
+ assert(arr == [9, 42, 2, 41, 3, 40, 4, 42, 1]);
+
+ sort(wrapper.save);
+ assert(arr == [1, 2, 3, 4, 9, 40, 41, 42, 42]);
+ }
+}
+
+unittest
+{
+ struct S
+ {
+ @property int front() @safe const pure nothrow { return 0; }
+ enum bool empty = false;
+ void popFront() @safe pure nothrow { }
+ @property auto save() @safe pure nothrow { return this; }
+ }
+
+ S s;
+ auto wrapper = refRange(&s);
+ static assert(isInfinite!(typeof(wrapper)));
+}
+
+unittest
+{
+ class C
+ {
+ @property int front() @safe const pure nothrow { return 0; }
+ @property bool empty() @safe const pure nothrow { return false; }
+ void popFront() @safe pure nothrow { }
+ @property auto save() @safe pure nothrow { return this; }
+ }
+ static assert(isForwardRange!C);
+
+ auto c = new C;
+ auto cWrapper = refRange(&c);
+ static assert(is(typeof(cWrapper) == C));
+ assert(cWrapper is c);
+
+ struct S
+ {
+ @property int front() @safe const pure nothrow { return 0; }
+ @property bool empty() @safe const pure nothrow { return false; }
+ void popFront() @safe pure nothrow { }
+
+ int i = 27;
+ }
+ static assert(isInputRange!S);
+ static assert(!isForwardRange!S);
+
+ auto s = S(42);
+ auto sWrapper = refRange(&s);
+ static assert(is(typeof(sWrapper) == S));
+ assert(sWrapper == s);
+}
+
+/++
+ Helper function for constructing a $(LREF RefRange).
+
+ If the given range is not a forward range or it is a class type (and thus is
+ already a reference type), then the original range is returned rather than
+ a $(LREF RefRange).
+ +/
+auto refRange(R)(R* range)
+ if(isForwardRange!R && !is(R == class))
+{
+ return RefRange!R(range);
+}
+
+auto refRange(R)(R* range)
+ if((!isForwardRange!R && isInputRange!R) ||
+ is(R == class))
+{
+ return *range;
+}
Something went wrong with that request. Please try again.