/** 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])); }