diff options
Diffstat (limited to 'libphobos/src/std/range/interfaces.d')
-rw-r--r-- | libphobos/src/std/range/interfaces.d | 567 |
1 files changed, 567 insertions, 0 deletions
diff --git a/libphobos/src/std/range/interfaces.d b/libphobos/src/std/range/interfaces.d new file mode 100644 index 0000000..7207776 --- /dev/null +++ b/libphobos/src/std/range/interfaces.d @@ -0,0 +1,567 @@ +/** +This module is a submodule of $(MREF std, range). + +The main $(MREF std, range) module provides template-based tools for working with +ranges, but sometimes an object-based interface for ranges is needed, such as +when runtime polymorphism is required. For this purpose, this submodule +provides a number of object and $(D interface) definitions that can be used to +wrap around _range objects created by the $(MREF std, range) templates. + +$(SCRIPT inhibitQuickIndex = 1;) +$(BOOKTABLE , + $(TR $(TD $(LREF InputRange)) + $(TD Wrapper for input ranges. + )) + $(TR $(TD $(LREF InputAssignable)) + $(TD Wrapper for input ranges with assignable elements. + )) + $(TR $(TD $(LREF ForwardRange)) + $(TD Wrapper for forward ranges. + )) + $(TR $(TD $(LREF ForwardAssignable)) + $(TD Wrapper for forward ranges with assignable elements. + )) + $(TR $(TD $(LREF BidirectionalRange)) + $(TD Wrapper for bidirectional ranges. + )) + $(TR $(TD $(LREF BidirectionalAssignable)) + $(TD Wrapper for bidirectional ranges with assignable elements. + )) + $(TR $(TD $(LREF RandomAccessFinite)) + $(TD Wrapper for finite random-access ranges. + )) + $(TR $(TD $(LREF RandomAccessAssignable)) + $(TD Wrapper for finite random-access ranges with assignable elements. + )) + $(TR $(TD $(LREF RandomAccessInfinite)) + $(TD Wrapper for infinite random-access ranges. + )) + $(TR $(TD $(LREF OutputRange)) + $(TD Wrapper for output ranges. + )) + $(TR $(TD $(LREF OutputRangeObject)) + $(TD Class that implements the $(D OutputRange) interface and wraps the + $(D put) methods in virtual functions. + $(TR $(TD $(LREF outputRangeObject)) + Convenience function for creating an $(D OutputRangeObject) with a base + range of type R that accepts types E. + )) + $(TR $(TD $(LREF InputRangeObject)) + $(TD Class that implements the $(D InputRange) interface and wraps the + input _range methods in virtual functions. + )) + $(TR $(TD $(LREF inputRangeObject)) + $(TD Convenience function for creating an $(D InputRangeObject) + of the proper type. + )) + $(TR $(TD $(LREF MostDerivedInputRange)) + $(TD Returns the interface type that best matches the range.) + )) +) + + +Source: $(PHOBOSSRC std/range/_interfaces.d) + +License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). + +Authors: $(HTTP erdani.com, Andrei Alexandrescu), David Simcha, +and Jonathan M Davis. Credit for some of the ideas in building this module goes +to $(HTTP fantascienza.net/leonardo/so/, Leonardo Maffi). +*/ +module std.range.interfaces; + +import std.meta; +import std.range.primitives; +import std.traits; + +/**These interfaces are intended to provide virtual function-based wrappers + * around input ranges with element type E. This is useful where a well-defined + * binary interface is required, such as when a DLL function or virtual function + * needs to accept a generic range as a parameter. Note that + * $(REF_ALTTEXT isInputRange, isInputRange, std, range, primitives) + * and friends check for conformance to structural interfaces + * not for implementation of these $(D interface) types. + * + * Limitations: + * + * These interfaces are not capable of forwarding $(D ref) access to elements. + * + * Infiniteness of the wrapped range is not propagated. + * + * Length is not propagated in the case of non-random access ranges. + * + * See_Also: + * $(LREF inputRangeObject) + */ +interface InputRange(E) { + /// + @property E front(); + + /// + E moveFront(); + + /// + void popFront(); + + /// + @property bool empty(); + + /* Measurements of the benefits of using opApply instead of range primitives + * for foreach, using timings for iterating over an iota(100_000_000) range + * with an empty loop body, using the same hardware in each case: + * + * Bare Iota struct, range primitives: 278 milliseconds + * InputRangeObject, opApply: 436 milliseconds (1.57x penalty) + * InputRangeObject, range primitives: 877 milliseconds (3.15x penalty) + */ + + /**$(D foreach) iteration uses opApply, since one delegate call per loop + * iteration is faster than three virtual function calls. + */ + int opApply(scope int delegate(E)); + + /// Ditto + int opApply(scope int delegate(size_t, E)); + +} + +/// +@safe unittest +{ + import std.algorithm.iteration : map; + import std.range : iota; + + void useRange(InputRange!int range) { + // Function body. + } + + // Create a range type. + auto squares = map!"a * a"(iota(10)); + + // Wrap it in an interface. + auto squaresWrapped = inputRangeObject(squares); + + // Use it. + useRange(squaresWrapped); +} + +/**Interface for a forward range of type $(D E).*/ +interface ForwardRange(E) : InputRange!E { + /// + @property ForwardRange!E save(); +} + +/**Interface for a bidirectional range of type $(D E).*/ +interface BidirectionalRange(E) : ForwardRange!(E) { + /// + @property BidirectionalRange!E save(); + + /// + @property E back(); + + /// + E moveBack(); + + /// + void popBack(); +} + +/**Interface for a finite random access range of type $(D E).*/ +interface RandomAccessFinite(E) : BidirectionalRange!(E) { + /// + @property RandomAccessFinite!E save(); + + /// + E opIndex(size_t); + + /// + E moveAt(size_t); + + /// + @property size_t length(); + + /// + alias opDollar = length; + + // Can't support slicing until issues with requiring slicing for all + // finite random access ranges are fully resolved. + version (none) + { + /// + RandomAccessFinite!E opSlice(size_t, size_t); + } +} + +/**Interface for an infinite random access range of type $(D E).*/ +interface RandomAccessInfinite(E) : ForwardRange!E { + /// + E moveAt(size_t); + + /// + @property RandomAccessInfinite!E save(); + + /// + E opIndex(size_t); +} + +/**Adds assignable elements to InputRange.*/ +interface InputAssignable(E) : InputRange!E { + /// + @property void front(E newVal); + + alias front = InputRange!E.front; // overload base interface method +} + +@safe unittest +{ + static assert(isInputRange!(InputAssignable!int)); +} + +/**Adds assignable elements to ForwardRange.*/ +interface ForwardAssignable(E) : InputAssignable!E, ForwardRange!E { + /// + @property ForwardAssignable!E save(); +} + +/**Adds assignable elements to BidirectionalRange.*/ +interface BidirectionalAssignable(E) : ForwardAssignable!E, BidirectionalRange!E { + /// + @property BidirectionalAssignable!E save(); + + /// + @property void back(E newVal); +} + +/**Adds assignable elements to RandomAccessFinite.*/ +interface RandomFiniteAssignable(E) : RandomAccessFinite!E, BidirectionalAssignable!E { + /// + @property RandomFiniteAssignable!E save(); + + /// + void opIndexAssign(E val, size_t index); +} + +/**Interface for an output range of type $(D E). Usage is similar to the + * $(D InputRange) interface and descendants.*/ +interface OutputRange(E) { + /// + void put(E); +} + +@safe unittest +{ + // 6973 + static assert(isOutputRange!(OutputRange!int, int)); +} + + +// CTFE function that generates mixin code for one put() method for each +// type E. +private string putMethods(E...)() +{ + import std.conv : to; + + string ret; + + foreach (ti, Unused; E) + { + ret ~= "void put(E[" ~ to!string(ti) ~ "] e) { .put(_range, e); }"; + } + + return ret; +} + +/**Implements the $(D OutputRange) interface for all types E and wraps the + * $(D put) method for each type $(D E) in a virtual function. + */ +class OutputRangeObject(R, E...) : staticMap!(OutputRange, E) { + // @BUG 4689: There should be constraints on this template class, but + // DMD won't let me put them in. + private R _range; + + /// + this(R range) { + this._range = range; + } + + mixin(putMethods!E()); +} + + +/**Returns the interface type that best matches $(D R).*/ +template MostDerivedInputRange(R) +if (isInputRange!(Unqual!R)) +{ + private alias E = ElementType!R; + + static if (isRandomAccessRange!R) + { + static if (isInfinite!R) + { + alias MostDerivedInputRange = RandomAccessInfinite!E; + } + else static if (hasAssignableElements!R) + { + alias MostDerivedInputRange = RandomFiniteAssignable!E; + } + else + { + alias MostDerivedInputRange = RandomAccessFinite!E; + } + } + else static if (isBidirectionalRange!R) + { + static if (hasAssignableElements!R) + { + alias MostDerivedInputRange = BidirectionalAssignable!E; + } + else + { + alias MostDerivedInputRange = BidirectionalRange!E; + } + } + else static if (isForwardRange!R) + { + static if (hasAssignableElements!R) + { + alias MostDerivedInputRange = ForwardAssignable!E; + } + else + { + alias MostDerivedInputRange = ForwardRange!E; + } + } + else + { + static if (hasAssignableElements!R) + { + alias MostDerivedInputRange = InputAssignable!E; + } + else + { + alias MostDerivedInputRange = InputRange!E; + } + } +} + +/**Implements the most derived interface that $(D R) works with and wraps + * all relevant range primitives in virtual functions. If $(D R) is already + * derived from the $(D InputRange) interface, aliases itself away. + */ +template InputRangeObject(R) +if (isInputRange!(Unqual!R)) +{ + static if (is(R : InputRange!(ElementType!R))) + { + alias InputRangeObject = R; + } + else static if (!is(Unqual!R == R)) + { + alias InputRangeObject = InputRangeObject!(Unqual!R); + } + else + { + + /// + class InputRangeObject : MostDerivedInputRange!(R) { + private R _range; + private alias E = ElementType!R; + + this(R range) { + this._range = range; + } + + @property E front() { return _range.front; } + + E moveFront() { + return _range.moveFront(); + } + + void popFront() { _range.popFront(); } + @property bool empty() { return _range.empty; } + + static if (isForwardRange!R) + { + @property typeof(this) save() { + return new typeof(this)(_range.save); + } + } + + static if (hasAssignableElements!R) + { + @property void front(E newVal) { + _range.front = newVal; + } + } + + static if (isBidirectionalRange!R) + { + @property E back() { return _range.back; } + + E moveBack() { + return _range.moveBack(); + } + + void popBack() { return _range.popBack(); } + + static if (hasAssignableElements!R) + { + @property void back(E newVal) { + _range.back = newVal; + } + } + } + + static if (isRandomAccessRange!R) + { + E opIndex(size_t index) { + return _range[index]; + } + + E moveAt(size_t index) { + return _range.moveAt(index); + } + + static if (hasAssignableElements!R) + { + void opIndexAssign(E val, size_t index) { + _range[index] = val; + } + } + + static if (!isInfinite!R) + { + @property size_t length() { + return _range.length; + } + + alias opDollar = length; + + // Can't support slicing until all the issues with + // requiring slicing support for finite random access + // ranges are resolved. + version (none) + { + typeof(this) opSlice(size_t lower, size_t upper) { + return new typeof(this)(_range[lower .. upper]); + } + } + } + } + + // Optimization: One delegate call is faster than three virtual + // function calls. Use opApply for foreach syntax. + int opApply(scope int delegate(E) dg) { + int res; + + for (auto r = _range; !r.empty; r.popFront()) + { + res = dg(r.front); + if (res) break; + } + + return res; + } + + int opApply(scope int delegate(size_t, E) dg) { + int res; + + size_t i = 0; + for (auto r = _range; !r.empty; r.popFront()) + { + res = dg(i, r.front); + if (res) break; + i++; + } + + return res; + } + } + } +} + +/**Convenience function for creating an $(D InputRangeObject) of the proper type. + * See $(LREF InputRange) for an example. + */ +InputRangeObject!R inputRangeObject(R)(R range) +if (isInputRange!R) +{ + static if (is(R : InputRange!(ElementType!R))) + { + return range; + } + else + { + return new InputRangeObject!R(range); + } +} + +/**Convenience function for creating an $(D OutputRangeObject) with a base range + * of type $(D R) that accepts types $(D E). +*/ +template outputRangeObject(E...) { + + /// + OutputRangeObject!(R, E) outputRangeObject(R)(R range) { + return new OutputRangeObject!(R, E)(range); + } +} + +/// +@safe unittest +{ + import std.array; + auto app = appender!(uint[])(); + auto appWrapped = outputRangeObject!(uint, uint[])(app); + static assert(is(typeof(appWrapped) : OutputRange!(uint[]))); + static assert(is(typeof(appWrapped) : OutputRange!(uint))); +} + +@system unittest +{ + import std.algorithm.comparison : equal; + import std.array; + import std.internal.test.dummyrange; + + static void testEquality(R)(iInputRange r1, R r2) { + assert(equal(r1, r2)); + } + + auto arr = [1,2,3,4]; + RandomFiniteAssignable!int arrWrapped = inputRangeObject(arr); + static assert(isRandomAccessRange!(typeof(arrWrapped))); + // static assert(hasSlicing!(typeof(arrWrapped))); + static assert(hasLength!(typeof(arrWrapped))); + arrWrapped[0] = 0; + assert(arr[0] == 0); + assert(arr.moveFront() == 0); + assert(arr.moveBack() == 4); + assert(arr.moveAt(1) == 2); + + foreach (elem; arrWrapped) {} + foreach (i, elem; arrWrapped) {} + + assert(inputRangeObject(arrWrapped) is arrWrapped); + + foreach (DummyType; AllDummyRanges) + { + auto d = DummyType.init; + static assert(propagatesRangeType!(DummyType, + typeof(inputRangeObject(d)))); + static assert(propagatesRangeType!(DummyType, + MostDerivedInputRange!DummyType)); + InputRange!uint wrapped = inputRangeObject(d); + assert(equal(wrapped, d)); + } + + // Test output range stuff. + auto app = appender!(uint[])(); + auto appWrapped = outputRangeObject!(uint, uint[])(app); + static assert(is(typeof(appWrapped) : OutputRange!(uint[]))); + static assert(is(typeof(appWrapped) : OutputRange!(uint))); + + appWrapped.put(1); + appWrapped.put([2, 3]); + assert(app.data.length == 3); + assert(equal(app.data, [1,2,3])); +} |