Following the recent thread "front evaluated multiple time with joiner depending on where extra arg given", I'd like to propose the following addition in std.range :
The goal is to ensure that a given range's 'front' method is called only once per element, allowing one to handle safely side effects in 'front' methods
eg use case: lambdas with side effects given to a map/reduce/filter function, etc.
import std.traits;
import std.range;
struct CacheFront(R){
alias T=ElementType!R;
R a;
import util.traits;
enum isRef=mixin(isLvalue(q{a.front}));
static if(isRef)
T* ai;
else{
T ai;
bool isValid=false;
}
auto ref front(){
//TODO: could also depend on whether front is pure
static if(isRef){
if(ai)
return *ai;
else{
ai=&a.front;
return *ai;
}
}
else{
if(isValid){
return ai;
}
else{
isValid=true;
ai=a.front;
return ai;
}
}
}
void popFront(){
a.popFront;
static if(isRef)
ai=null;
else
isValid=false;
}
//forward other properties automatically:
alias a this;
}
auto cacheFront(R)(R a) if(isInputRange!R){
return CacheFront!R(a);
}
//helper function (should be in phobos' std.traits)
void requireLvalue(T)(ref T);
string isLvalue(string a){
return `__traits(compiles, requireLvalue(`~a~`))`;
}
void main(){
import std.algorithm;
import std.array;
{
//checks that it calls front only once per element
uint counter;
auto b=[1,2,3].map!((a){counter++; return [a];}).cacheFront.joiner.array;
assert(counter==3);
assert(b==[1,2,3]);
}
{
int counter=0;
auto b=[1,2,3].map!((a){counter++; return [a];}).joiner.array;
assert(counter==6);
}
{
//checks that it works with ref front
auto a0=[1,2,3];
auto ref fun0(ref int a){a=0; return a;}
auto b=a0. cacheFront.map!fun0.array;
assert(b==[0,0,0]);
assert(a0==[0,0,0]);
//checks that it forwards properties of range:
assert([1,2,3]. cacheFront.length==3);
}
}
And a side question: is there a way to denote that a lambda can return by ref?