I got an idea. import std.range;
template isImmutableInputRange(R) { enum bool isImmutableInputRange = is(typeof( { R r; // can define a range object if (r.empty) {} // can test for empty auto r2 = r.nextFront(); // can invoke nextFront() auto h = r2.front; // can get the front of the range })); } template isImmutableForwardRange(R) { enum bool isImmutableForwardRange = isImmutableInputRange!R && is(typeof( { R r1; R r2 = r1.save; // can call "save" against a range object })); } template isImmutableBidirectionalRange(R) { enum bool isImmutableBidirectionalRange = isImmutableForwardRange!R && is(typeof(R.init.back()) == typeof(R.init.front())) && is(typeof({ R r; auto r2 = r.nextBack(); })); } void immutableForeach(R, E)(R r, scope void delegate(E) dg) if (isImmutableInputRange!R && is(ElementType!R : E)) { if (r.empty) return; dg(r.front); immutableForeach(r.nextFront(), dg); // tail recursion } void immutableForeachReverse(R, E)(R r, scope void delegate(E) dg) if (isImmutableBidirectionalRange!R && is(ElementType!R : E)) { if (r.empty) return; dg(r.back); immutableForeachReverse(r.nextBack(), dg); // tail recursion } const struct Range { int[] arr; @property int front() const { return arr[0]; } @property bool empty() const { return arr.length > 0; } const(Range) nextFront() const { return Range(arr[1..$]); } const(Range) save() const { return this; } @property int back() const { return arr[$-1]; } const(Range) nextBack() { return Range(arr[0..$-1]); } } static assert(isImmutableInputRange!Range); static assert(isImmutableForwardRange!Range); static assert(isImmutableBidirectionalRange!Range); void main() { const r = Range([1,2,3]); int i = 0; immutableForeach(r, (int v) { assert(v == ++i); }); int j = 3; immutableForeachReverse(r, (int v) { assert(v == j--); }); } Kenji Hara