std.range.primitives

This module is a submodule of std.range.

It defines the bidirectional and forward range primitives for arrays:

empty, front, back, popFront, popBack and save.

It provides basic range functionality by defining several templates for testing whether a given object is a range, and what kind of range it is:

It also provides number of templates that test for various range capabilities:

Finally, it includes some convenience functions for manipulating ranges:

Source: std/range/primitives.d

License

Boost License 1.0.

Authors

Andrei Alexandrescu, David Simcha, and Jonathan M Davis. Credit for some of the ideas

in building this module goes to

Leonardo Maffi.

Functions 24

private fnvoid doPut(R, E)(ref R r, auto ref E e)
fnvoid put(R, E)(ref R r, E e)Outputs `e` to `r`. The exact effect is dependent upon the two types. Several cases are accepted, as described below. The code snippets are attempted in order, and the first to compile "wins" and g...
private fnvoid putChar(R, E)(ref R r, E e) if (isSomeChar!E)
private fnvoid isLvalue(T)(T) if (0)
private fnvoid isLvalue(T)(ref T) if (1)
fnauto walkLength(Range)(Range range) if (isInputRange!Range && !isInfinite!Range)This is a best-effort implementation of `length` for any kind of range.
fnauto walkLength(Range)(Range range, const size_t upTo) if (isInputRange!Range)ditto
fnsize_t popFrontN(Range)(ref Range r, size_t n) if (isInputRange!Range)`popFrontN` eagerly advances `r` itself (not a copy) up to `n` times (by calling `r.popFront`). `popFrontN` takes `r` by `ref`, so it mutates the original range. Completes in 1 steps for ranges tha...
fnsize_t popBackN(Range)(ref Range r, size_t n) if (isBidirectionalRange!Range)ditto
fnvoid popFrontExactly(Range)(ref Range r, size_t n) if (isInputRange!Range)Eagerly advances `r` itself (not a copy) exactly `n` times (by calling `r.popFront`). `popFrontExactly` takes `r` by `ref`, so it mutates the original range. Completes in 1 steps for ranges that su...
fnvoid popBackExactly(Range)(ref Range r, size_t n) if (isBidirectionalRange!Range)ditto
fnElementType!R moveFront(R)(R r)Moves the front of `r` out and returns it.
fnElementType!R moveBack(R)(R r)Moves the back of `r` out and returns it. Leaves `r.back` in a destroyable state that does not allocate any resources (usually equal to its `.init` value).
fnElementType!R moveAt(R)(R r, size_t i)Moves element at index `i` of `r` out and returns it. Leaves r[i] in a destroyable state that does not allocate any resources (usually equal to its `.init` value).
fnbool empty(T)(auto ref scope T a) if (is(typeof(a.length) : size_t)) @propertyImplements the range interface primitive `empty` for types that obey hasLength property and for narrow strings. Due to the fact that nonmember functions can be called with the first argument using ...
fninout(T)[] save(T)(return scope inout(T)[] a) @property @safe pure nothrow @nogcImplements the range interface primitive `save` for built-in arrays. Due to the fact that nonmember functions can be called with the first argument using the dot notation, `array.save` is equivalen...
fnvoid popFront(T)(scope ref inout(T)[] a) if (!isAutodecodableString!(T[]) && !is(T[] == void[])) @safe pure nothrow @nogcImplements the range interface primitive `popFront` for built-in arrays. Due to the fact that nonmember functions can be called with the first argument using the dot notation, `array.popFront` is e...
fnvoid popFront(C)(scope ref inout(C)[] str) if (isAutodecodableString!(C[])) @trusted pure nothrowditto
fnvoid popBack(T)(scope ref inout(T)[] a) if (!isAutodecodableString!(T[]) && !is(T[] == void[])) @safe pure nothrow @nogcImplements the range interface primitive `popBack` for built-in arrays. Due to the fact that nonmember functions can be called with the first argument using the dot notation, `array.popBack` is equ...
fnvoid popBack(T)(scope ref inout(T)[] a) if (isAutodecodableString!(T[])) @safe pureditto
fninout(T) front(T)(return scope inout(T)[] a) if (!isAutodecodableString!(T[]) && !is(T[] == void[])) @property ref @safe pure nothrow @nogcImplements the range interface primitive `front` for built-in arrays. Due to the fact that nonmember functions can be called with the first argument using the dot notation, `array.front` is equival...
fndchar front(T)(scope const(T)[] a) if (isAutodecodableString!(T[])) @property @safe pureditto
fninout(T) back(T)(return scope inout(T)[] a) if (!isAutodecodableString!(T[]) && !is(T[] == void[])) @property ref @safe pure nothrow @nogcImplements the range interface primitive `back` for built-in arrays. Due to the fact that nonmember functions can be called with the first argument using the dot notation, `array.back` is equivalen...
fndchar back(T)(scope const(T)[] a) if (isAutodecodableString!(T[])) @property @safe pureditto

Variables 14

enumvarisInputRange = is(typeof(R.init) == R) && is(typeof((R r) { return r.empty; } (R.init)) == bool) && (is(typeof((return ref R r) => r.front)) || is(typeof(ref (return ref R r) => r.front))) && !is(typeof((R r) { return r.front; } (R.init)) == void) && is(typeof((R r) => r.popFront))

Returns true if R is an input range. An input range must define the primitives empty, popFront, and front. The following code should compile for any input range.

---- R r; // can define a range object if (r.empty) {} // can test for empty r.popFront(); // can invoke popFront() auto h = r.front; // can get the front of the range of non-void type ----

The following are rules of input ranges are assumed to hold true in all Phobos code. These rules are not checkable at compile-time, so not conforming to these rules when writing ranges or range based code will result in undefined behavior.

  • r.empty returns false if and only if there is more data

    available in the range.

  • r.empty evaluated multiple times, without calling

    r.popFront, or otherwise mutating the range object or the underlying data, yields the same result for every evaluation.

  • r.front returns the current element in the range.

    It may return by value or by reference.

  • r.front can be legally evaluated if and only if evaluating

    r.empty has, or would have, equaled false.

  • r.front evaluated multiple times, without calling

    r.popFront, or otherwise mutating the range object or the underlying data, yields the same result for every evaluation.

  • r.popFront advances to the next element in the range.
  • r.popFront can be called if and only if evaluating r.empty

    has, or would have, equaled false.

Also, note that Phobos code assumes that the primitives r.front and r.empty are 1 time complexity wise or "cheap" in terms of running time. statements in the documentation of range functions are made with this assumption.

See Also

The header of std.range for tutorials on ranges.

Parameters

Rtype to be tested
Eif present, the elements of the range must be spec/const3 to this type

Returns

true if R is an input range (possibly with element type E), false if not
enumvarisInputRange = .isInputRange!R && isQualifierConvertible!(ElementType!R, E)

ditto

enumvarisNativeOutputRange = is(typeof(doPut(lvalueOf!R, lvalueOf!E)))
enumvarisOutputRange = is(typeof(put(lvalueOf!R, lvalueOf!E)))

Returns true if R is an output range for elements of type E. An output range is defined functionally as a range that supports the operation put(r, e) as defined above.

See Also

The header of std.range for tutorials on ranges.
enumvarisForwardRange = isInputRange!R && is(typeof((R r) { return r.save; } (R.init)) == R)

Returns true if R is a forward range. A forward range is an input range r that can save "checkpoints" by saving r.save to another value of type R. Notable examples of input ranges that are not forward ranges are file/socket ranges; copying such a range will not save the position in the stream, and they most likely reuse an internal buffer as the entire stream does not sit in memory. Subsequently, advancing either the original or the copy will advance the stream, so the copies are not independent.

The following code should compile for any forward range.

---- static assert(isInputRange!R); R r1; auto s1 = r1.save; static assert(is(typeof(s1) == R)); ----

Saving a range is not duplicating it; in the example above, r1 and r2 still refer to the same underlying data. They just navigate that data independently.

The semantics of a forward range (not checkable during compilation) are the same as for an input range, with the additional requirement that backtracking must be possible by saving a copy of the range object with save and using it later.

save behaves in many ways like a copy constructor, and its implementation typically is done using copy construction.

The existence of a copy constructor, however, does not imply the range is a forward range. For example, a range that reads from a TTY consumes its input and cannot save its place and read it again, and so cannot be a forward range and cannot have a save function.

See Also

The header of std.range for tutorials on ranges.

Parameters

Rtype to be tested
Eif present, the elements of the range must be spec/const3 to this type

Returns

true if R is a forward range (possibly with element type E), false if not
enumvarisForwardRange = .isForwardRange!R && isQualifierConvertible!(ElementType!R, E)

ditto

enumvarisBidirectionalRange = isForwardRange!R && is(typeof((R r) => r.popBack)) && (is(typeof((return ref R r) => r.back)) || is(typeof(ref (return ref R r) => r.back))) && is(typeof(R.init.back.init) == ElementType!R)

Returns true if R is a bidirectional range. A bidirectional range is a forward range that also offers the primitives back and popBack. The following code should compile for any bidirectional range.

The semantics of a bidirectional range (not checkable during compilation) are assumed to be the following (r is an object of type R):

  • r.back returns (possibly a reference to) the last

    element in the range. Calling r.back is allowed only if calling r.empty has, or would have, returned false.

See Also

The header of std.range for tutorials on ranges.

Parameters

Rtype to be tested
Eif present, the elements of the range must be spec/const3 to this type

Returns

true if R is a bidirectional range (possibly with element type E), false if not
enumvarisBidirectionalRange = .isBidirectionalRange!R && isQualifierConvertible!(ElementType!R, E)

ditto

enumvarisRandomAccessRange = is(typeof(lvalueOf!R[1]) == ElementType!R) && !(isAutodecodableString!R && !isAggregateType!R) && isForwardRange!R && (isBidirectionalRange!R || isInfinite!R) && (hasLength!R || isInfinite!R) && (isInfinite!R || !is(typeof(lvalueOf!R[$ - 1])) || is(typeof(lvalueOf!R[$ - 1]) == ElementType!R))

Returns true if R is a random-access range. A random-access range is a bidirectional range that also offers the primitive opIndex, OR an infinite forward range that offers opIndex. In either case, the range must either offer length or be infinite. The following code should compile for any random-access range.

The semantics of a random-access range (not checkable during compilation) are assumed to be the following (r is an object of type R):

  • r.opIndex(n) returns a reference to the nth element in the range.

Although char[] and wchar[] (as well as their qualified versions including string and wstring) are arrays, isRandomAccessRange yields false for them because they use variable-length encodings (UTF-8 and UTF-16 respectively). These types are bidirectional ranges only.

See Also

The header of std.range for tutorials on ranges.

Parameters

Rtype to be tested
Eif present, the elements of the range must be spec/const3 to this type

Returns

true if R is a random-access range (possibly with element type E), false if not
enumvarisRandomAccessRange = .isRandomAccessRange!R && isQualifierConvertible!(ElementType!R, E)

ditto

enumvarhasMobileElements = isInputRange!R && is(typeof(moveFront(lvalueOf!R)) == ElementType!R) && (!isBidirectionalRange!R || is(typeof(moveBack(lvalueOf!R)) == ElementType!R)) && (!isRandomAccessRange!R || is(typeof(moveAt(lvalueOf!R, 0)) == ElementType!R))

Returns true iff R is an input range that supports the moveFront primitive, as well as moveBack and moveAt if it's a bidirectional or random access range. These may be explicitly implemented, or may work via the default behavior of the module level functions moveFront and friends. The following code should compile for any range with mobile elements.

---- alias E = ElementType!R; R r; static assert(isInputRange!R); static assert(is(typeof(moveFront(r)) == E)); static if (isBidirectionalRange!R) static assert(is(typeof(moveBack(r)) == E)); static if (isRandomAccessRange!R) static assert(is(typeof(moveAt(r, 0)) == E)); ----

enumvarhasAssignableElements = isInputRange!R && is(typeof(lvalueOf!R.front = lvalueOf!R.front)) && (!isBidirectionalRange!R || is(typeof(lvalueOf!R.back = lvalueOf!R.back))) && (!isRandomAccessRange!R || is(typeof(lvalueOf!R[0] = lvalueOf!R.front)))

Returns true if R is an input range and has mutable elements. The following code should compile for any range with assignable elements.

---- R r; static assert(isInputRange!R); r.front = r.front; static if (isBidirectionalRange!R) r.back = r.front; static if (isRandomAccessRange!R) r[0] = r.front; ----

enumvarhasLvalueElements = isInputRange!R && is(typeof(isLvalue(lvalueOf!R.front))) && (!isBidirectionalRange!R || is(typeof(isLvalue(lvalueOf!R.back)))) && (!isRandomAccessRange!R || is(typeof(isLvalue(lvalueOf!R[0]))))

Tests whether the range R has lvalue elements. These are defined as elements that can be passed by reference and have their address taken. The following code should compile for any range with lvalue elements. ---- void passByRef(ref ElementType!R stuff); ... static assert(isInputRange!R); passByRef(r.front); static if (isBidirectionalRange!R) passByRef(r.back); static if (isRandomAccessRange!R) passByRef(r[0]); ----

enumvarhasSlicing = isForwardRange!R && !(isAutodecodableString!R && !isAggregateType!R) && is(typeof((R r) { return r[1 .. 1].length; } (R.init)) == size_t) && (is(typeof(lvalueOf!R[1 .. 1]) == R) || isInfinite!R) && (!is(typeof(lvalueOf!R[0 .. $])) || is(typeof(lvalueOf!R[0 .. $]) == R)) && (!is(typeof(lvalueOf!R[0 .. $])) || isInfinite!R || is(typeof(lvalueOf!R[0 .. $ - 1]) == R)) && is(typeof((ref R r) { static assert(isForwardRange!(typeof(r[1 .. 2]))); }))

Returns true if R offers a slicing operator with integral boundaries that returns a forward range type.

For finite ranges, the result of opSlice must be of the same type as the original range type. If the range defines opDollar, then it must support subtraction.

For infinite ranges, when not using opDollar, the result of opSlice may be a forward range of any type. However, when using opDollar, the result of opSlice must be of the same type as the original range type.

The following expression must be true for hasSlicing to be true:

---- isForwardRange!R && !(isAutodecodableString!R && !isAggregateType!R) && is(typeof((R r) { return r[1 .. 1].length; } (R.init)) == size_t) && (is(typeof(lvalueOf!R[1 .. 1]) == R) || isInfinite!R) && (!is(typeof(lvalueOf!R[0 .. $])) || is(typeof(lvalueOf!R[0 .. $]) == R)) && (!is(typeof(lvalueOf!R[0 .. $])) || isInfinite!R || is(typeof(lvalueOf!R[0 .. $ - 1]) == R)) && is(typeof((ref R r) { static assert(isForwardRange!(typeof(r[1 .. 2]))); })); ----

Templates 5

tmplElementType(R)

The element type of R. R does not have to be a range. The element type is determined as the type yielded by r.front for an object r of type R. For example, ElementType!(T[]) is T if T[] isn't a narrow string; if it is, the element type is dchar. If R doesn't have front, ElementType!R is void.

tmplElementEncodingType(R)

The encoding element type of R. For narrow strings (char[], wchar[] and their qualified variants including string and wstring), ElementEncodingType is the character type of the string. For all other types, ElementEncodingType is the same as ElementType.

tmplhasSwappableElements(R)

Returns true if R is an input range and has swappable elements. The following code should compile for any range with swappable elements.

---- R r; static assert(isInputRange!R); swap(r.front, r.front); static if (isBidirectionalRange!R) swap(r.back, r.front); static if (isRandomAccessRange!R) swap(r[0], r.front); ----

tmplhasLength(R)

Yields true if R has a length member that returns a value of size_t type. R does not have to be a range. If R is a range, algorithms in the standard library are only guaranteed to support length with type size_t.

Note that length is an optional primitive as no range must implement it. Some ranges do not store their length explicitly, some cannot compute it without actually exhausting the range (e.g. socket streams), and some other ranges may be infinite.

Although narrow string types (char[], wchar[], and their qualified derivatives) do define a length property, hasLength yields false for them. This is because a narrow string's length does not reflect the number of characters, but instead the number of encoding units, and as such is not useful with range-oriented algorithms. To use strings as random-access ranges with length, use representation or byCodeUnit.

tmplisInfinite(R)

Returns true if R is an infinite input range. An infinite input range is an input range that has a statically-defined enumerated member called empty that is always false, for example:

---- struct MyInfiniteRange { enum bool empty = false; ... } ----